@sudomock/mcp 2.7.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -51
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -196
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* SudoMock MCP Server
|
|
4
4
|
*
|
|
5
5
|
* Local stdio MCP server for Claude Desktop, Claude Code, Cursor, and VS Code.
|
|
6
|
-
* Renders photorealistic product mockups from Photoshop PSD templates
|
|
6
|
+
* Renders photorealistic product mockups from Photoshop PSD templates and
|
|
7
|
+
* product photos via the SudoMock API.
|
|
7
8
|
*
|
|
8
9
|
* Auth: SUDOMOCK_API_KEY environment variable
|
|
9
10
|
* Transport: stdio (local process)
|
|
@@ -172,7 +173,7 @@ export function formatJobAccepted(result) {
|
|
|
172
173
|
};
|
|
173
174
|
return JSON.stringify(summary, null, 2);
|
|
174
175
|
}
|
|
175
|
-
export const TERMINAL_JOB_STATUSES = new Set(["succeeded", "failed"]);
|
|
176
|
+
export const TERMINAL_JOB_STATUSES = new Set(["succeeded", "failed", "cancelled"]);
|
|
176
177
|
/** GET /api/v1/jobs/{job_id} -- owner-scoped job status snapshot. */
|
|
177
178
|
async function getJob(jobId, timeout = DEFAULT_TIMEOUT) {
|
|
178
179
|
const result = (await apiRequest({
|
|
@@ -583,6 +584,9 @@ function publicWebhookEndpoint(result) {
|
|
|
583
584
|
secret: endpoint.secret ?? null,
|
|
584
585
|
description: endpoint.description ?? null,
|
|
585
586
|
event_types: Array.isArray(endpoint.event_types) ? endpoint.event_types : [],
|
|
587
|
+
// Which spelling of the photo-mockup events this endpoint receives
|
|
588
|
+
// ('legacy' or 'current'); the payload's kind follows the same pin.
|
|
589
|
+
event_naming: endpoint.event_naming ?? null,
|
|
586
590
|
enabled: endpoint.enabled === true,
|
|
587
591
|
created_at: endpoint.created_at ?? null,
|
|
588
592
|
updated_at: endpoint.updated_at ?? null,
|
|
@@ -670,9 +674,9 @@ const server = new McpServer({
|
|
|
670
674
|
});
|
|
671
675
|
instrumentToolCalls(server);
|
|
672
676
|
// ---------------------------------------------------------------------------
|
|
673
|
-
// Tool 1:
|
|
677
|
+
// Tool 1: list_psd_mockups
|
|
674
678
|
// ---------------------------------------------------------------------------
|
|
675
|
-
server.tool("
|
|
679
|
+
server.tool("list_psd_mockups", "List your uploaded PSD mockup templates with UUIDs, names, and thumbnails. Use returned UUIDs with render_psd_mockup or get_psd_mockup.", {
|
|
676
680
|
limit: z.number().min(1).max(100).default(20).describe("Results per page (1-100, default 20)"),
|
|
677
681
|
offset: z.number().min(0).default(0).describe("Pagination offset (default 0)"),
|
|
678
682
|
name: z.string().optional().describe("Filter by name (case-insensitive substring match)"),
|
|
@@ -683,7 +687,7 @@ server.tool("list_mockups", "List your uploaded mockup templates with UUIDs, nam
|
|
|
683
687
|
}, async ({ limit, offset, name, created_after, created_before, sort_by, sort_order }) => {
|
|
684
688
|
const result = await apiRequest({
|
|
685
689
|
method: "GET",
|
|
686
|
-
path: "/api/v1/mockups",
|
|
690
|
+
path: "/api/v1/psd-mockups",
|
|
687
691
|
params: { limit, offset, name, created_after, created_before, sort: sort_by, order: sort_order },
|
|
688
692
|
});
|
|
689
693
|
return {
|
|
@@ -694,14 +698,14 @@ server.tool("list_mockups", "List your uploaded mockup templates with UUIDs, nam
|
|
|
694
698
|
};
|
|
695
699
|
});
|
|
696
700
|
// ---------------------------------------------------------------------------
|
|
697
|
-
// Tool 2:
|
|
701
|
+
// Tool 2: get_psd_mockup
|
|
698
702
|
// ---------------------------------------------------------------------------
|
|
699
|
-
server.tool("
|
|
700
|
-
mockup_uuid: z.string().describe("The UUID of the mockup to inspect"),
|
|
703
|
+
server.tool("get_psd_mockup", "Get full details of a PSD mockup: smart object UUIDs, layer names, dimensions, positions, blend modes, editable text layers, and thumbnail URLs.", {
|
|
704
|
+
mockup_uuid: z.string().describe("The UUID of the PSD mockup to inspect"),
|
|
701
705
|
}, async ({ mockup_uuid }) => {
|
|
702
706
|
const result = await apiRequest({
|
|
703
707
|
method: "GET",
|
|
704
|
-
path: `/api/v1/mockups/${mockup_uuid}`,
|
|
708
|
+
path: `/api/v1/psd-mockups/${mockup_uuid}`,
|
|
705
709
|
});
|
|
706
710
|
return {
|
|
707
711
|
content: [{
|
|
@@ -711,15 +715,15 @@ server.tool("get_mockup_details", "Get full details of a mockup: smart object UU
|
|
|
711
715
|
};
|
|
712
716
|
});
|
|
713
717
|
// ---------------------------------------------------------------------------
|
|
714
|
-
// Tool 3:
|
|
718
|
+
// Tool 3: update_psd_mockup
|
|
715
719
|
// ---------------------------------------------------------------------------
|
|
716
|
-
server.tool("
|
|
717
|
-
mockup_uuid: z.string().describe("The UUID of the mockup to rename"),
|
|
720
|
+
server.tool("update_psd_mockup", "Rename a PSD mockup template.", {
|
|
721
|
+
mockup_uuid: z.string().describe("The UUID of the PSD mockup to rename"),
|
|
718
722
|
name: z.string().describe("New display name for the mockup"),
|
|
719
723
|
}, async ({ mockup_uuid, name }) => {
|
|
720
724
|
const result = await apiRequest({
|
|
721
725
|
method: "PATCH",
|
|
722
|
-
path: `/api/v1/mockups/${mockup_uuid}`,
|
|
726
|
+
path: `/api/v1/psd-mockups/${mockup_uuid}`,
|
|
723
727
|
body: { name },
|
|
724
728
|
});
|
|
725
729
|
return {
|
|
@@ -730,19 +734,19 @@ server.tool("update_mockup", "Rename a mockup template.", {
|
|
|
730
734
|
};
|
|
731
735
|
});
|
|
732
736
|
// ---------------------------------------------------------------------------
|
|
733
|
-
// Tool 4:
|
|
737
|
+
// Tool 4: delete_psd_mockup
|
|
734
738
|
// ---------------------------------------------------------------------------
|
|
735
|
-
server.tool("
|
|
736
|
-
mockup_uuid: z.string().describe("The UUID of the mockup to delete"),
|
|
739
|
+
server.tool("delete_psd_mockup", "Permanently delete a PSD mockup template. Cannot be undone.", {
|
|
740
|
+
mockup_uuid: z.string().describe("The UUID of the PSD mockup to delete"),
|
|
737
741
|
}, async ({ mockup_uuid }) => {
|
|
738
742
|
await apiRequest({
|
|
739
743
|
method: "DELETE",
|
|
740
|
-
path: `/api/v1/mockups/${mockup_uuid}`,
|
|
744
|
+
path: `/api/v1/psd-mockups/${mockup_uuid}`,
|
|
741
745
|
});
|
|
742
746
|
return { content: [{ type: "text", text: `Mockup ${mockup_uuid} deleted successfully.` }] };
|
|
743
747
|
});
|
|
744
748
|
// ---------------------------------------------------------------------------
|
|
745
|
-
// Tool 5:
|
|
749
|
+
// Tool 5: render_psd_mockup
|
|
746
750
|
// ---------------------------------------------------------------------------
|
|
747
751
|
const smartObjectInputSchema = z
|
|
748
752
|
.object({
|
|
@@ -807,7 +811,7 @@ const textLayerInputSchema = z
|
|
|
807
811
|
uuid: z
|
|
808
812
|
.string()
|
|
809
813
|
.regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
|
|
810
|
-
.describe("UUID from
|
|
814
|
+
.describe("UUID from get_psd_mockup text_layers"),
|
|
811
815
|
text: z.string().min(1).max(500).optional().describe("Replacement text for a single-style layer"),
|
|
812
816
|
segments: z
|
|
813
817
|
.array(z.object({
|
|
@@ -854,8 +858,8 @@ const textLayerInputSchema = z
|
|
|
854
858
|
ctx.addIssue({ code: "custom", message: "Segment indexes must be unique" });
|
|
855
859
|
}
|
|
856
860
|
});
|
|
857
|
-
server.tool("
|
|
858
|
-
mockup_uuid: z.string().describe("UUID of the mockup template (from
|
|
861
|
+
server.tool("render_psd_mockup", "Render a PSD mockup with artwork, editable text, or both. Supports one or multiple smart objects and preserves the template's authored appearance. Returns the rendered image URL. Costs 1 credit. Use list_psd_mockups and get_psd_mockup to find target UUIDs.", {
|
|
862
|
+
mockup_uuid: z.string().describe("UUID of the PSD mockup template (from list_psd_mockups)"),
|
|
859
863
|
smart_object_uuid: z.string().optional().describe("UUID of one smart object layer. Provide with artwork_url, or use smart_objects for one or more entries."),
|
|
860
864
|
artwork_url: z.string().optional().describe("Public artwork URL for smart_object_uuid. Provide both singular fields, or use smart_objects."),
|
|
861
865
|
smart_objects: z
|
|
@@ -868,7 +872,7 @@ server.tool("render_mockup", "Render a PSD mockup with artwork, editable text, o
|
|
|
868
872
|
.min(1)
|
|
869
873
|
.max(50)
|
|
870
874
|
.optional()
|
|
871
|
-
.describe("Editable text overrides from
|
|
875
|
+
.describe("Editable text overrides from get_psd_mockup. Each entry needs exactly one of text or segments. May be used alone or with smart objects."),
|
|
872
876
|
fit: z.enum(["fill", "contain", "cover"]).default("fill").describe("How singular artwork_url fills its smart object area"),
|
|
873
877
|
image_format: z.enum(["webp", "png", "jpg"]).default("webp").describe("Output format"),
|
|
874
878
|
image_size: z.number().min(100).max(10000).default(2048).describe("Output width in pixels (default 2048)"),
|
|
@@ -998,11 +1002,11 @@ server.tool("remove_background", "Remove the background from any image and retur
|
|
|
998
1002
|
};
|
|
999
1003
|
});
|
|
1000
1004
|
// ---------------------------------------------------------------------------
|
|
1001
|
-
// Tool:
|
|
1005
|
+
// Tool: create_photo_mockup
|
|
1002
1006
|
// ---------------------------------------------------------------------------
|
|
1003
|
-
server.tool("
|
|
1007
|
+
server.tool("create_photo_mockup", "Create a reusable photo mockup from a public image URL of a product. Returns the mockup_id and its render targets synchronously. Costs 25 credits. If the image is unsuitable, the 25 credits are refunded automatically. Set is_async=true to queue instead and receive a job_id to poll with get_job or wait_for_job. Use the dashboard for visual fine-tuning.", {
|
|
1004
1008
|
source_url: z.string().describe("Public HTTPS URL of the product image"),
|
|
1005
|
-
name: z.string().optional().describe("Optional display name for the
|
|
1009
|
+
name: z.string().optional().describe("Optional display name for the photo mockup"),
|
|
1006
1010
|
idempotency_key: z.string().min(1).max(255).optional().describe("Optional retry-stable key for this create request"),
|
|
1007
1011
|
is_async: z
|
|
1008
1012
|
.boolean()
|
|
@@ -1016,7 +1020,7 @@ server.tool("create_2d_mockup", "Create a reusable 2D mockup from a public image
|
|
|
1016
1020
|
body.is_async = true;
|
|
1017
1021
|
const result = (await apiRequest({
|
|
1018
1022
|
method: "POST",
|
|
1019
|
-
path: "/api/v1/
|
|
1023
|
+
path: "/api/v1/photo-mockups",
|
|
1020
1024
|
body,
|
|
1021
1025
|
headers: { "Idempotency-Key": idempotency_key ?? randomUUID() },
|
|
1022
1026
|
}));
|
|
@@ -1050,15 +1054,16 @@ server.tool("create_2d_mockup", "Create a reusable 2D mockup from a public image
|
|
|
1050
1054
|
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
1051
1055
|
});
|
|
1052
1056
|
// ---------------------------------------------------------------------------
|
|
1053
|
-
//
|
|
1057
|
+
// Tool: render_photo_mockup
|
|
1054
1058
|
// ---------------------------------------------------------------------------
|
|
1055
|
-
// A render names one target and nothing else
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
//
|
|
1059
|
-
//
|
|
1060
|
-
|
|
1061
|
-
|
|
1059
|
+
// A render names one target and nothing else: exactly one of print_area_uuid
|
|
1060
|
+
// or surface_uuid. A product surface is sized by a coverage percentage, a
|
|
1061
|
+
// print area by a fit, and either by an exact width + height. One tool holds
|
|
1062
|
+
// both dials, the same shape the hosted server exposes, so the dial that
|
|
1063
|
+
// belongs to the other kind of target is refused here by name instead of
|
|
1064
|
+
// being a field the caller could never reach.
|
|
1065
|
+
const PHOTO_RENDER_SHARED = {
|
|
1066
|
+
mockup_id: z.string().describe("UUID of the photo mockup (mockup_id from list_photo_mockups, get_photo_mockup or create_photo_mockup)."),
|
|
1062
1067
|
artwork_url: z.string().describe("Public URL of the artwork image (PNG/JPG/WebP) to place on the mockup"),
|
|
1063
1068
|
remove_background: z.boolean().default(false).describe("Remove the artwork's background before placing it. Adds 25 credits per artwork."),
|
|
1064
1069
|
opacity: z.number().min(0).max(100).default(100).describe("Artwork opacity percentage (0-100)"),
|
|
@@ -1104,72 +1109,80 @@ const TWO_D_SHARED = {
|
|
|
1104
1109
|
.default(false)
|
|
1105
1110
|
.describe("Queue the render instead of waiting. When true the API returns 202 with a job_id immediately -- poll with get_job, or call wait_for_job to block until it finishes and hands back result_url. Default false returns print_files + render_uuid synchronously (200). Use for long renders or when running many in parallel."),
|
|
1106
1111
|
};
|
|
1107
|
-
// The
|
|
1108
|
-
// and the sentence that sends them to the one that works.
|
|
1112
|
+
// The sentences a refused option is answered with.
|
|
1109
1113
|
//
|
|
1110
|
-
//
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
1113
|
-
//
|
|
1114
|
-
//
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1118
|
-
// The wording is the API's own, so the caller reads the same reason whichever
|
|
1114
|
+
// The API answers each of these with a 422, and refusing them here as well is
|
|
1115
|
+
// deliberate: a client that can put a shape on the wire the API is guaranteed
|
|
1116
|
+
// to reject has spent the caller's round trip telling them something it
|
|
1117
|
+
// already knew. The consumer on this side is a language model: an option it
|
|
1118
|
+
// wrote disappearing in silence teaches it nothing and it writes the same
|
|
1119
|
+
// option again, while one named sentence teaches it in a single turn. The
|
|
1120
|
+
// wording is the API's own, so the caller reads the same reason whichever
|
|
1119
1121
|
// side answers first.
|
|
1122
|
+
const ONE_TARGET = "Provide exactly one of print_area_uuid or surface_uuid";
|
|
1123
|
+
const FIT_ON_SURFACE = "A surface covers the whole product, so fit has nothing to fit against. Send coverage to choose how much of the surface the artwork spans, or width and height to give it an exact size, or render a print area instead.";
|
|
1124
|
+
const COVERAGE_ON_PRINT_AREA = "A print area is sized by fit, or by an explicit width and height, so coverage has no meaning on one. Send fit or a width and height, or render the product surface instead.";
|
|
1120
1125
|
const RETIRED_SCALE = "scale has been retired and nothing replaces the name. Send width and height together to draw the artwork at an exact size; a single multiplier could not express two independent axes.";
|
|
1121
|
-
const TWO_D_REFUSED_OPTIONS = {
|
|
1122
|
-
surface: {
|
|
1123
|
-
fit: "A surface covers the whole product, so fit has nothing to fit against. Send coverage to choose how much of the surface the artwork spans, or width and height to give it an exact size, or render a print area instead.",
|
|
1124
|
-
scale: RETIRED_SCALE,
|
|
1125
|
-
},
|
|
1126
|
-
print_area: {
|
|
1127
|
-
coverage: "A print area is sized by fit, or by an explicit width and height, so coverage has no meaning on one. Send fit or a width and height, or render the product surface instead.",
|
|
1128
|
-
scale: RETIRED_SCALE,
|
|
1129
|
-
},
|
|
1130
|
-
};
|
|
1131
1126
|
/**
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1137
|
-
* keyed on the argument being written rather than on the value it holds, so an
|
|
1138
|
-
* explicitly written null is caught as well: writing it is the caller naming
|
|
1139
|
-
* the option.
|
|
1140
|
-
*
|
|
1141
|
-
* An option with no sentence of its own still gets refused rather than dropped.
|
|
1142
|
-
* A name we never published is either a typo or an option the caller believes
|
|
1143
|
-
* exists, and both are worth one line of reply.
|
|
1127
|
+
* The tool's input. Strict on purpose: an argument a tool never declared is
|
|
1128
|
+
* dropped before the handler ever sees it, so the retired `scale` used to come
|
|
1129
|
+
* back as a successful render with the caller's option gone and nothing said
|
|
1130
|
+
* about it. A name we never published is either a typo or an option the
|
|
1131
|
+
* caller believes exists, and both are worth one line of reply.
|
|
1144
1132
|
*/
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1133
|
+
const renderPhotoMockupInput = z.strictObject({
|
|
1134
|
+
...PHOTO_RENDER_SHARED,
|
|
1135
|
+
print_area_uuid: z
|
|
1136
|
+
.string()
|
|
1137
|
+
.optional()
|
|
1138
|
+
.describe("UUID of a saved print area from get_photo_mockup's print_areas[] (its print_area_id): a bounded zone somebody drew on the product, such as a chest logo. Omit when surface_uuid is used."),
|
|
1139
|
+
surface_uuid: z
|
|
1140
|
+
.string()
|
|
1141
|
+
.optional()
|
|
1142
|
+
.describe("UUID of a product surface from get_photo_mockup's surfaces[]: a whole printable product, for an all-over print. Omit when print_area_uuid is used."),
|
|
1143
|
+
coverage: z
|
|
1144
|
+
.number()
|
|
1145
|
+
.min(10)
|
|
1146
|
+
.max(100)
|
|
1147
|
+
.optional()
|
|
1148
|
+
.describe("How much of the surface the artwork spans, as a percentage (10-100). Belongs to surface_uuid; sending it with print_area_uuid is refused. Omit to span the whole surface, which is what an all-over print usually wants. Send width and height instead to give the artwork an exact size."),
|
|
1149
|
+
fit: z
|
|
1150
|
+
.enum(["contain", "fill", "cover"])
|
|
1151
|
+
.optional()
|
|
1152
|
+
.describe("How the artwork meets the print area, which it always fills edge to edge: 'contain' keeps the proportions and fits inside (the default), 'fill' stretches to the edges, 'cover' fills and crops the overflow. Belongs to print_area_uuid; sending it with surface_uuid is refused. Leave it out to get 'contain'. To sit inside the area with room around it, send width and height instead."),
|
|
1153
|
+
// A percentage cannot express a box whose proportions differ from the
|
|
1154
|
+
// surface's, which is exactly what an artwork resized on a canvas is, so
|
|
1155
|
+
// the exact box belongs to both kinds of target.
|
|
1156
|
+
width: z
|
|
1157
|
+
.number()
|
|
1158
|
+
.min(1)
|
|
1159
|
+
.max(30000)
|
|
1160
|
+
.optional()
|
|
1161
|
+
.describe("Artwork width in pixels, drawn at that exact size instead of by coverage or fit. Send together with height, and without coverage or fit. Width and height are independent, so any aspect ratio is allowed - stretching on one axis only is a supported placement."),
|
|
1162
|
+
height: z
|
|
1163
|
+
.number()
|
|
1164
|
+
.min(1)
|
|
1165
|
+
.max(30000)
|
|
1166
|
+
.optional()
|
|
1167
|
+
.describe("Artwork height in pixels. Send together with width. Sending only one of the two is rejected rather than silently completed, so the aspect ratio is never guessed for you."),
|
|
1168
|
+
}, {
|
|
1169
|
+
error: (issue) => {
|
|
1170
|
+
if (issue.code !== "unrecognized_keys")
|
|
1171
|
+
return undefined;
|
|
1172
|
+
return issue.keys
|
|
1173
|
+
.map((key) => key === "scale"
|
|
1174
|
+
? RETIRED_SCALE
|
|
1175
|
+
: `${key} is not an option on this tool. Send only the options it lists.`)
|
|
1176
|
+
.join(" ");
|
|
1177
|
+
},
|
|
1178
|
+
});
|
|
1158
1179
|
/**
|
|
1159
1180
|
* Refuse a placement that answers the sizing question more than once, or half.
|
|
1160
1181
|
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
*
|
|
1165
|
-
* This is not the thing that was taken out of here. A default invents a value
|
|
1166
|
-
* the caller never typed and quietly changes the render when it drifts from
|
|
1167
|
-
* the renderer's; a refusal invents nothing, and if it ever drifts it drifts
|
|
1168
|
-
* into refusing out loud, with the API still refusing behind it. It also
|
|
1169
|
-
* cannot drift unnoticed: the contract test reads the shared placement wire
|
|
1170
|
-
* fixture and asserts every shape listed there as rejected is still
|
|
1171
|
-
* unreachable from every tool, and the wording below is the wording the API
|
|
1172
|
-
* answers with, so a caller reads the same reason from either side.
|
|
1182
|
+
* A default invents a value the caller never typed and quietly changes the
|
|
1183
|
+
* render when it drifts from the renderer's; a refusal invents nothing, and if
|
|
1184
|
+
* it ever drifts it drifts into refusing out loud, with the API still refusing
|
|
1185
|
+
* behind it.
|
|
1173
1186
|
*
|
|
1174
1187
|
* `relativeSizing` is whichever proportional option belongs to this kind of
|
|
1175
1188
|
* target -- `coverage` on a surface, `fit` on a print area.
|
|
@@ -1187,16 +1200,15 @@ function assertOneSizingAnswer(placement, relativeSizing) {
|
|
|
1187
1200
|
/**
|
|
1188
1201
|
* Send one render and hand back what the caller can act on.
|
|
1189
1202
|
*
|
|
1190
|
-
* `target` carries the address field, `placement` the dials that
|
|
1191
|
-
* kind of target. Neither is inspected here: the
|
|
1192
|
-
* settled
|
|
1203
|
+
* `target` carries the address field, `placement` the sizing dials that
|
|
1204
|
+
* belong to that kind of target. Neither is inspected here: the handler has
|
|
1205
|
+
* already settled which target this is and which dials it may carry.
|
|
1193
1206
|
*/
|
|
1194
|
-
async function
|
|
1195
|
-
// Anchoring belongs to both kinds of target; only sizing is split
|
|
1196
|
-
// caller
|
|
1197
|
-
//
|
|
1198
|
-
//
|
|
1199
|
-
// caller reached for placement.
|
|
1207
|
+
async function renderPhotoMockup(args, target, placement) {
|
|
1208
|
+
// Anchoring belongs to both kinds of target; only sizing is split. What the
|
|
1209
|
+
// caller did not name is absent, and a placement nobody touched is absent
|
|
1210
|
+
// entirely rather than sent as an empty object -- an empty object still
|
|
1211
|
+
// claims the caller reached for placement.
|
|
1200
1212
|
const sent = {
|
|
1201
1213
|
...(args.position === undefined ? {} : { position: args.position }),
|
|
1202
1214
|
...(args.offset_x === undefined ? {} : { offset_x: args.offset_x }),
|
|
@@ -1229,12 +1241,13 @@ async function renderTwoD(args, target, placement) {
|
|
|
1229
1241
|
body.is_async = true;
|
|
1230
1242
|
const result = await apiRequest({
|
|
1231
1243
|
method: "POST",
|
|
1232
|
-
path: `/api/v1/
|
|
1244
|
+
path: `/api/v1/photo-mockups/${args.mockup_id}/render`,
|
|
1233
1245
|
body,
|
|
1234
1246
|
timeout: RENDER_TIMEOUT,
|
|
1235
1247
|
});
|
|
1236
|
-
// is_async=true -> 202 + job_id (kind "
|
|
1237
|
-
// Reuse get_job / wait_for_job to reach the
|
|
1248
|
+
// is_async=true -> 202 + job_id (kind "photo_mockup_render" on this path);
|
|
1249
|
+
// hand back the poll contract. Reuse get_job / wait_for_job to reach the
|
|
1250
|
+
// terminal job (result_url).
|
|
1238
1251
|
if (args.is_async) {
|
|
1239
1252
|
return { content: [{ type: "text", text: formatJobAccepted(result) }] };
|
|
1240
1253
|
}
|
|
@@ -1254,71 +1267,23 @@ async function renderTwoD(args, target, placement) {
|
|
|
1254
1267
|
};
|
|
1255
1268
|
return { content: [{ type: "text", text: JSON.stringify(rendered, null, 2) }] };
|
|
1256
1269
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
server.registerTool("render_2d_surface", {
|
|
1261
|
-
description: "Render artwork across a whole product surface -- an all-over print. Every printable product in the photo is a surface with its own surface_uuid, listed by get_2d_mockup. Returns print_files (each with an export_path) and a render_uuid. Costs 5 credits. Use the dashboard for visual fine-tuning.",
|
|
1262
|
-
inputSchema: twoDRenderInput("surface", {
|
|
1263
|
-
...TWO_D_SHARED,
|
|
1264
|
-
surface_uuid: z.string().describe("UUID of a product surface from get_2d_mockup's surfaces[]."),
|
|
1265
|
-
coverage: z
|
|
1266
|
-
.number()
|
|
1267
|
-
.min(10)
|
|
1268
|
-
.max(100)
|
|
1269
|
-
.optional()
|
|
1270
|
-
.describe("How much of the surface the artwork spans, as a percentage (10-100). Omit to span the whole surface, which is what an all-over print usually wants. Send width and height instead to give the artwork an exact size."),
|
|
1271
|
-
// A percentage cannot express a box whose proportions differ from the
|
|
1272
|
-
// surface's, which is exactly what an artwork resized on a canvas is.
|
|
1273
|
-
width: z
|
|
1274
|
-
.number()
|
|
1275
|
-
.min(1)
|
|
1276
|
-
.max(30000)
|
|
1277
|
-
.optional()
|
|
1278
|
-
.describe("Artwork width in pixels, drawn at that exact size rather than as a percentage of the surface. Send together with height, and without coverage."),
|
|
1279
|
-
height: z
|
|
1280
|
-
.number()
|
|
1281
|
-
.min(1)
|
|
1282
|
-
.max(30000)
|
|
1283
|
-
.optional()
|
|
1284
|
-
.describe("Artwork height in pixels. Send together with width, and without coverage. The two axes are independent, so any aspect ratio is allowed."),
|
|
1285
|
-
}),
|
|
1270
|
+
server.registerTool("render_photo_mockup", {
|
|
1271
|
+
description: "Render artwork onto a saved photo mockup. Name exactly one target: a print_area_uuid (a bounded zone somebody drew on the product, such as a chest logo; sized by fit or by width + height) or a surface_uuid (a whole printable product, for an all-over print; sized by coverage or by width + height). Read both from get_photo_mockup. Returns print_files (each with an export_path) and a render_uuid. Costs 5 credits. Use the dashboard for visual fine-tuning.",
|
|
1272
|
+
inputSchema: renderPhotoMockupInput,
|
|
1286
1273
|
}, async (args) => {
|
|
1274
|
+
const hasPrintArea = args.print_area_uuid !== undefined;
|
|
1275
|
+
const hasSurface = args.surface_uuid !== undefined;
|
|
1276
|
+
if (hasPrintArea === hasSurface)
|
|
1277
|
+
throw new Error(ONE_TARGET);
|
|
1278
|
+
// The dial of the other kind of target is refused by name. Refusing is
|
|
1279
|
+
// keyed on the argument being written rather than on the value it holds:
|
|
1280
|
+
// writing it is the caller naming the option.
|
|
1281
|
+
if (hasSurface && args.fit !== undefined)
|
|
1282
|
+
throw new Error(FIT_ON_SURFACE);
|
|
1283
|
+
if (hasPrintArea && args.coverage !== undefined)
|
|
1284
|
+
throw new Error(COVERAGE_ON_PRINT_AREA);
|
|
1287
1285
|
const sizing = {
|
|
1288
1286
|
...(args.coverage === undefined ? {} : { coverage: args.coverage }),
|
|
1289
|
-
...(args.width === undefined ? {} : { width: args.width }),
|
|
1290
|
-
...(args.height === undefined ? {} : { height: args.height }),
|
|
1291
|
-
};
|
|
1292
|
-
assertOneSizingAnswer(sizing, "coverage");
|
|
1293
|
-
return renderTwoD(args, { surface_uuid: args.surface_uuid }, sizing);
|
|
1294
|
-
});
|
|
1295
|
-
// ---------------------------------------------------------------------------
|
|
1296
|
-
// Tool: render_2d_print_area
|
|
1297
|
-
// ---------------------------------------------------------------------------
|
|
1298
|
-
server.registerTool("render_2d_print_area", {
|
|
1299
|
-
description: "Render artwork onto one saved print area -- a bounded zone somebody drew on the product, such as a chest logo. Read the print_area_id values from get_2d_mockup. Returns print_files (each with an export_path) and a render_uuid. Costs 5 credits. Use the dashboard for visual fine-tuning.",
|
|
1300
|
-
inputSchema: twoDRenderInput("print_area", {
|
|
1301
|
-
...TWO_D_SHARED,
|
|
1302
|
-
print_area_uuid: z.string().describe("UUID of a saved print area from get_2d_mockup's print_areas[] (its print_area_id)."),
|
|
1303
|
-
fit: z
|
|
1304
|
-
.enum(["contain", "fill", "cover"])
|
|
1305
|
-
.optional()
|
|
1306
|
-
.describe("How the artwork meets the print area, which it always fills edge to edge: 'contain' keeps the proportions and fits inside (the default), 'fill' stretches to the edges, 'cover' fills and crops the overflow. Leave it out to get 'contain'. To sit inside the area with room around it, send width and height instead."),
|
|
1307
|
-
width: z
|
|
1308
|
-
.number()
|
|
1309
|
-
.min(1)
|
|
1310
|
-
.max(30000)
|
|
1311
|
-
.optional()
|
|
1312
|
-
.describe("Artwork width in print-area pixels. Send together with height, and without fit. Width and height are independent, so any aspect ratio is allowed - stretching on one axis only is a supported placement."),
|
|
1313
|
-
height: z
|
|
1314
|
-
.number()
|
|
1315
|
-
.min(1)
|
|
1316
|
-
.max(30000)
|
|
1317
|
-
.optional()
|
|
1318
|
-
.describe("Artwork height in print-area pixels. Send together with width. Sending only one of the two is rejected rather than silently completed, so the aspect ratio is never guessed for you."),
|
|
1319
|
-
}),
|
|
1320
|
-
}, async (args) => {
|
|
1321
|
-
const sizing = {
|
|
1322
1287
|
...(args.fit === undefined ? {} : { fit: args.fit }),
|
|
1323
1288
|
...(args.width === undefined ? {} : { width: args.width }),
|
|
1324
1289
|
...(args.height === undefined ? {} : { height: args.height }),
|
|
@@ -1327,20 +1292,20 @@ server.registerTool("render_2d_print_area", {
|
|
|
1327
1292
|
// the half instead would render a size the caller never asked for with no
|
|
1328
1293
|
// error to notice, and forwarding it spends a round trip to be told what
|
|
1329
1294
|
// the shape already said.
|
|
1330
|
-
assertOneSizingAnswer(sizing, "fit");
|
|
1331
|
-
return
|
|
1295
|
+
assertOneSizingAnswer(sizing, hasSurface ? "coverage" : "fit");
|
|
1296
|
+
return renderPhotoMockup(args, hasSurface ? { surface_uuid: args.surface_uuid } : { uuid: args.print_area_uuid }, sizing);
|
|
1332
1297
|
});
|
|
1333
1298
|
// ---------------------------------------------------------------------------
|
|
1334
|
-
// Tool:
|
|
1299
|
+
// Tool: list_photo_mockups
|
|
1335
1300
|
// ---------------------------------------------------------------------------
|
|
1336
|
-
server.tool("
|
|
1301
|
+
server.tool("list_photo_mockups", "List your saved photo mockup templates, made from product photos with no PSD. Returns each mockup's mockup_id, name, status, thumbnail, dimensions, and print_areas. Use the mockup_id with get_photo_mockup to read the print-area and surface UUIDs a render needs. Costs 0 credits.", {
|
|
1337
1302
|
limit: z.number().min(1).max(100).default(20).describe("Results per page (1-100, default 20)"),
|
|
1338
1303
|
offset: z.number().min(0).default(0).describe("Pagination offset (default 0)"),
|
|
1339
1304
|
customizable_only: z.boolean().default(false).describe("Return only mockups ready for shopper customization"),
|
|
1340
1305
|
}, async ({ limit, offset, customizable_only }) => {
|
|
1341
1306
|
const result = await apiRequest({
|
|
1342
1307
|
method: "GET",
|
|
1343
|
-
path: "/api/v1/
|
|
1308
|
+
path: "/api/v1/photo-mockups",
|
|
1344
1309
|
params: { limit, offset, customizable_only },
|
|
1345
1310
|
});
|
|
1346
1311
|
return {
|
|
@@ -1351,14 +1316,14 @@ server.tool("list_2d_mockups", "List your saved SudoAI 2D mockup templates (no P
|
|
|
1351
1316
|
};
|
|
1352
1317
|
});
|
|
1353
1318
|
// ---------------------------------------------------------------------------
|
|
1354
|
-
// Tool:
|
|
1319
|
+
// Tool: get_photo_mockup
|
|
1355
1320
|
// ---------------------------------------------------------------------------
|
|
1356
|
-
server.tool("
|
|
1357
|
-
mockup_id: z.string().describe("UUID of the
|
|
1321
|
+
server.tool("get_photo_mockup", "Get one photo mockup's full details: the saved print_areas[] somebody drew on it, and the surfaces[] -- one entry per printable product in the photo. Pass a print_area_id as print_area_uuid, or a surfaces[].surface_uuid, to render_photo_mockup. Costs 0 credits.", {
|
|
1322
|
+
mockup_id: z.string().describe("UUID of the photo mockup (mockup_id from list_photo_mockups)"),
|
|
1358
1323
|
}, async ({ mockup_id }) => {
|
|
1359
1324
|
const result = await apiRequest({
|
|
1360
1325
|
method: "GET",
|
|
1361
|
-
path: `/api/v1/
|
|
1326
|
+
path: `/api/v1/photo-mockups/${mockup_id}`,
|
|
1362
1327
|
});
|
|
1363
1328
|
return {
|
|
1364
1329
|
content: [{
|
|
@@ -1368,10 +1333,10 @@ server.tool("get_2d_mockup", "Get one SudoAI 2D mockup's full details: the saved
|
|
|
1368
1333
|
};
|
|
1369
1334
|
});
|
|
1370
1335
|
// ---------------------------------------------------------------------------
|
|
1371
|
-
// Tool:
|
|
1336
|
+
// Tool: update_photo_mockup_print_areas
|
|
1372
1337
|
// ---------------------------------------------------------------------------
|
|
1373
|
-
server.tool("
|
|
1374
|
-
mockup_id: z.string().describe("UUID of the
|
|
1338
|
+
server.tool("update_photo_mockup_print_areas", "Replace a photo mockup's print areas with up to 8 four-point quads and return the updated geometry. An empty list is accepted only for verified full product surfaces. Costs 0 credits.", {
|
|
1339
|
+
mockup_id: z.string().describe("UUID of the photo mockup to update"),
|
|
1375
1340
|
print_areas: z
|
|
1376
1341
|
.array(z.object({
|
|
1377
1342
|
points: z
|
|
@@ -1386,7 +1351,7 @@ server.tool("update_2d_print_areas", "Replace a 2D mockup's print areas with up
|
|
|
1386
1351
|
}, async ({ mockup_id, print_areas }) => {
|
|
1387
1352
|
const result = await apiRequest({
|
|
1388
1353
|
method: "PUT",
|
|
1389
|
-
path: `/api/v1/
|
|
1354
|
+
path: `/api/v1/photo-mockups/${mockup_id}/print-areas`,
|
|
1390
1355
|
body: { print_areas },
|
|
1391
1356
|
});
|
|
1392
1357
|
const envelope = asRecord(result);
|
|
@@ -1403,14 +1368,14 @@ server.tool("update_2d_print_areas", "Replace a 2D mockup's print areas with up
|
|
|
1403
1368
|
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
1404
1369
|
});
|
|
1405
1370
|
// ---------------------------------------------------------------------------
|
|
1406
|
-
// Tool:
|
|
1371
|
+
// Tool: delete_photo_mockup
|
|
1407
1372
|
// ---------------------------------------------------------------------------
|
|
1408
|
-
server.tool("
|
|
1409
|
-
mockup_id: z.string().describe("UUID of the
|
|
1373
|
+
server.tool("delete_photo_mockup", "Permanently delete a photo mockup template and all of its data. Cannot be undone. Costs 0 credits.", {
|
|
1374
|
+
mockup_id: z.string().describe("UUID of the photo mockup to delete (mockup_id from list_photo_mockups)"),
|
|
1410
1375
|
}, async ({ mockup_id }) => {
|
|
1411
1376
|
const result = await apiRequest({
|
|
1412
1377
|
method: "DELETE",
|
|
1413
|
-
path: `/api/v1/
|
|
1378
|
+
path: `/api/v1/photo-mockups/${mockup_id}`,
|
|
1414
1379
|
});
|
|
1415
1380
|
const envelope = asRecord(result);
|
|
1416
1381
|
const data = asRecord(envelope.data ?? result);
|
|
@@ -1458,7 +1423,7 @@ server.tool("upload_psd", "Upload a Photoshop PSD/PSB file as a new mockup templ
|
|
|
1458
1423
|
// ---------------------------------------------------------------------------
|
|
1459
1424
|
// Tool: get_job
|
|
1460
1425
|
// ---------------------------------------------------------------------------
|
|
1461
|
-
server.tool("get_job", "Get the current status of any async render, video, upload, or 2D job by its job_id. Returns status (queued|running|succeeded|failed), completed-result details and credits charged, or an error if failed. To block until done, use wait_for_job instead.", {
|
|
1426
|
+
server.tool("get_job", "Get the current status of any async render, video, upload, or photo mockup (2D) job by its job_id. Returns status (queued|dispatched|running|succeeded|failed|cancelled), completed-result details and credits charged, or an error if failed. To block until done, use wait_for_job instead.", {
|
|
1462
1427
|
job_id: z.string().describe("The job_id returned by any async submission"),
|
|
1463
1428
|
}, async ({ job_id }) => {
|
|
1464
1429
|
const result = await getJob(job_id);
|
|
@@ -1467,8 +1432,11 @@ server.tool("get_job", "Get the current status of any async render, video, uploa
|
|
|
1467
1432
|
// ---------------------------------------------------------------------------
|
|
1468
1433
|
// Tool: list_jobs
|
|
1469
1434
|
// ---------------------------------------------------------------------------
|
|
1470
|
-
server.tool("list_jobs", "List your async jobs, including PSD renders, videos, uploads, and 2D creation/renders, newest first. Use this when you do not already hold a job_id. Pass the returned next_cursor to fetch the next page.", {
|
|
1471
|
-
kind: z
|
|
1435
|
+
server.tool("list_jobs", "List your async jobs, including PSD renders, videos, uploads, and photo mockup (2D) creation/renders, newest first. Use this when you do not already hold a job_id. Pass the returned next_cursor to fetch the next page.", {
|
|
1436
|
+
kind: z
|
|
1437
|
+
.enum(["video", "render", "upload", "2d_create", "2d_render", "photo_mockup_create", "photo_mockup_render"])
|
|
1438
|
+
.optional()
|
|
1439
|
+
.describe("Filter by job kind. A photo mockup job has two spellings of its kind (photo_mockup_create / photo_mockup_render, or the legacy 2d_create / 2d_render); either spelling selects both. Omit for all kinds."),
|
|
1472
1440
|
mockup_uuid: z.string().optional().describe("Filter by source mockup UUID (e.g. one mockup's videos). Raw-image videos are never returned by this filter."),
|
|
1473
1441
|
limit: z.number().int().min(1).max(50).default(20).describe("Max jobs per page (1-50, default 20)"),
|
|
1474
1442
|
cursor: z.string().optional().describe("Opaque keyset cursor from a prior page's next_cursor"),
|
|
@@ -1487,7 +1455,7 @@ server.tool("list_jobs", "List your async jobs, including PSD renders, videos, u
|
|
|
1487
1455
|
// ---------------------------------------------------------------------------
|
|
1488
1456
|
// Tool: wait_for_job
|
|
1489
1457
|
// ---------------------------------------------------------------------------
|
|
1490
|
-
server.tool("wait_for_job", "Poll any async render, video, upload, or 2D job until it succeeds or
|
|
1458
|
+
server.tool("wait_for_job", "Poll any async render, video, upload, or photo mockup (2D) job until it succeeds, fails, or is cancelled, then return the final result and credits charged. Blocks while polling.", {
|
|
1491
1459
|
job_id: z.string().describe("The job_id to wait on (from an async submission or render_video)"),
|
|
1492
1460
|
poll_interval_seconds: z
|
|
1493
1461
|
.number()
|
|
@@ -1525,8 +1493,8 @@ server.tool("wait_for_job", "Poll any async render, video, upload, or 2D job unt
|
|
|
1525
1493
|
// Tool: render_video
|
|
1526
1494
|
// ---------------------------------------------------------------------------
|
|
1527
1495
|
server.tool("render_video", "Create a short AI video from either a mockup with artwork or a public image URL. Supply exactly one input mode. Always async: returns a job_id immediately for get_job or wait_for_job. Credit cost depends on clip length, audio, and the automatically selected quality. Unsupported durations are rejected.", {
|
|
1528
|
-
mockup_uuid: z.string().optional().describe("RENDER MODE: UUID of the mockup to animate (from
|
|
1529
|
-
smart_object_uuid: z.string().optional().describe("RENDER MODE: UUID of the smart object layer to place artwork on (from
|
|
1496
|
+
mockup_uuid: z.string().optional().describe("RENDER MODE: UUID of the PSD mockup to animate (from list_psd_mockups or upload_psd). Required in render mode. In raw-image mode it is an optional association (groups the clip under that mockup's 'Past clips')."),
|
|
1497
|
+
smart_object_uuid: z.string().optional().describe("RENDER MODE: UUID of the smart object layer to place artwork on (from get_psd_mockup). Required in render mode; omit in raw-image mode."),
|
|
1530
1498
|
artwork_url: z.string().optional().describe("RENDER MODE: public URL of the artwork image (PNG/JPG/WebP) to place on the mockup before animating. Provide this OR artwork_base64. Omit in raw-image mode."),
|
|
1531
1499
|
artwork_base64: z.string().optional().describe("RENDER MODE: raw base64-encoded artwork bytes (no data: prefix). Provide this OR artwork_url."),
|
|
1532
1500
|
artwork_content_type: z.enum(["image/png", "image/jpeg", "image/webp", "image/gif"]).optional().describe("MIME type for artwork_base64 (defaults to image/png if omitted)."),
|
|
@@ -1633,11 +1601,23 @@ server.tool("get_account", "Get your account info: subscription plan, remaining
|
|
|
1633
1601
|
// HMAC-SHA256 over `${timestamp}.${rawBody}` with the endpoint's secret) and
|
|
1634
1602
|
// "X-SudoMock-Timestamp: <unix-seconds>". Verify in constant time and reject if
|
|
1635
1603
|
// |now - timestamp| > 300s (User-Agent SudoMock-Webhook/1.0). Standard job
|
|
1636
|
-
// events and the typed
|
|
1604
|
+
// events and the typed photo mockup create/render events have separate
|
|
1605
|
+
// documented bodies.
|
|
1637
1606
|
//
|
|
1638
1607
|
// Event types: render.succeeded, render.failed, upload.succeeded,
|
|
1639
|
-
// video.succeeded, video.failed,
|
|
1640
|
-
//
|
|
1608
|
+
// video.succeeded, video.failed, photo_mockup.ready, photo_mockup.rejected,
|
|
1609
|
+
// photo_mockup.failed, photo_mockup_render.succeeded,
|
|
1610
|
+
// photo_mockup_render.failed, webhook.test.
|
|
1611
|
+
//
|
|
1612
|
+
// The five photo mockup events also have a legacy spelling (2d_mockup.ready,
|
|
1613
|
+
// 2d_mockup.rejected, 2d_mockup.failed, 2d_render.succeeded,
|
|
1614
|
+
// 2d_render.failed). Both spellings are subscribable; which one a delivery
|
|
1615
|
+
// carries is the endpoint's `event_naming` pin ('current' = family names,
|
|
1616
|
+
// 'legacy' = 2d_* names), and the payload's `kind` follows the same pin.
|
|
1617
|
+
// Endpoints created before the pin existed are 'legacy'; new endpoints default
|
|
1618
|
+
// to 'current' on the API side. The pin is set at create and changed at update;
|
|
1619
|
+
// a re-pin sent alone re-spells the stored subscription list to match. The list
|
|
1620
|
+
// below is in the API's own order.
|
|
1641
1621
|
// ---------------------------------------------------------------------------
|
|
1642
1622
|
const WEBHOOK_EVENT_TYPES = [
|
|
1643
1623
|
"render.succeeded",
|
|
@@ -1650,17 +1630,29 @@ const WEBHOOK_EVENT_TYPES = [
|
|
|
1650
1630
|
"2d_mockup.failed",
|
|
1651
1631
|
"2d_render.succeeded",
|
|
1652
1632
|
"2d_render.failed",
|
|
1633
|
+
"photo_mockup.ready",
|
|
1634
|
+
"photo_mockup.rejected",
|
|
1635
|
+
"photo_mockup.failed",
|
|
1636
|
+
"photo_mockup_render.succeeded",
|
|
1637
|
+
"photo_mockup_render.failed",
|
|
1653
1638
|
"webhook.test",
|
|
1654
1639
|
];
|
|
1640
|
+
const WEBHOOK_EVENT_NAMINGS = ["legacy", "current"];
|
|
1655
1641
|
server.tool("create_webhook_endpoint", "Register a webhook endpoint that SudoMock calls when async jobs finish. The signing secret is returned IN FULL exactly once here -- store it to verify the HMAC carried in the X-SudoMock-Signature header (with X-SudoMock-Timestamp) on incoming deliveries. URL must be https and publicly routable.", {
|
|
1656
1642
|
url: z.string().describe("https endpoint URL to receive POST deliveries (publicly routable; private/loopback hosts are rejected)"),
|
|
1657
1643
|
event_types: z
|
|
1658
1644
|
.array(z.enum(WEBHOOK_EVENT_TYPES))
|
|
1659
1645
|
.default([])
|
|
1660
|
-
.describe("Event types to subscribe to. Supports render, upload, video,
|
|
1646
|
+
.describe("Event types to subscribe to. Supports render, upload, video, photo mockup create (photo_mockup.*), photo mockup render (photo_mockup_render.*), and webhook.test events; the legacy 2d_mockup.* / 2d_render.* spellings of the photo mockup events are accepted too. Pass an empty array (the default) to subscribe to ALL events."),
|
|
1647
|
+
event_naming: z
|
|
1648
|
+
.enum(WEBHOOK_EVENT_NAMINGS)
|
|
1649
|
+
.optional()
|
|
1650
|
+
.describe("Which spelling of the photo mockup events this endpoint receives: 'current' (photo_mockup.*, photo_mockup_render.*, kind photo_mockup_create/photo_mockup_render) or 'legacy' (2d_mockup.*, 2d_render.*, kind 2d_create/2d_render). Omit to take the API default ('current'). Pick 'legacy' only for a receiver written against the old names."),
|
|
1661
1651
|
description: z.string().max(255).optional().describe("Optional human-readable label for this endpoint"),
|
|
1662
|
-
}, async ({ url, event_types, description }) => {
|
|
1652
|
+
}, async ({ url, event_types, event_naming, description }) => {
|
|
1663
1653
|
const body = { url, event_types };
|
|
1654
|
+
if (event_naming !== undefined)
|
|
1655
|
+
body.event_naming = event_naming;
|
|
1664
1656
|
if (description)
|
|
1665
1657
|
body.description = description;
|
|
1666
1658
|
const result = await apiRequest({
|
|
@@ -1683,7 +1675,7 @@ server.tool("list_webhook_endpoints", "List your registered webhook endpoints (i
|
|
|
1683
1675
|
const output = Array.isArray(result) ? result.map(publicWebhookEndpoint) : [];
|
|
1684
1676
|
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
1685
1677
|
});
|
|
1686
|
-
server.tool("update_webhook_endpoint", "Update a webhook endpoint in place: change its url, description, subscribed event_types, or enable/disable it (enabled:false pauses deliveries without losing the signing secret). All fields optional -- only the ones you pass are changed. The secret is NOT rotated or returned here.", {
|
|
1678
|
+
server.tool("update_webhook_endpoint", "Update a webhook endpoint in place: change its url, description, subscribed event_types, its event_naming pin, or enable/disable it (enabled:false pauses deliveries without losing the signing secret). All fields optional -- only the ones you pass are changed. The secret is NOT rotated or returned here.", {
|
|
1687
1679
|
endpoint_id: z.string().describe("The id of the webhook endpoint to update (from list_webhook_endpoints)"),
|
|
1688
1680
|
url: z.string().optional().describe("New https endpoint URL (publicly routable; private/loopback hosts are rejected)"),
|
|
1689
1681
|
description: z.string().max(255).optional().describe("New human-readable label"),
|
|
@@ -1691,8 +1683,12 @@ server.tool("update_webhook_endpoint", "Update a webhook endpoint in place: chan
|
|
|
1691
1683
|
.array(z.enum(WEBHOOK_EVENT_TYPES))
|
|
1692
1684
|
.optional()
|
|
1693
1685
|
.describe("Replacement list of subscribed event types. Pass an empty array to subscribe to ALL events."),
|
|
1686
|
+
event_naming: z
|
|
1687
|
+
.enum(WEBHOOK_EVENT_NAMINGS)
|
|
1688
|
+
.optional()
|
|
1689
|
+
.describe("Re-pin which spelling of the photo mockup events this endpoint receives: 'current' (photo_mockup.*, photo_mockup_render.*, kind photo_mockup_create/photo_mockup_render) or 'legacy' (2d_mockup.*, 2d_render.*, kind 2d_create/2d_render). Omit to keep the endpoint's current pin. Sent on its own, the re-pin re-spells the stored subscription list to match. Move an endpoint to 'current' only once its receiver handles the new names."),
|
|
1694
1690
|
enabled: z.boolean().optional().describe("Set false to pause deliveries (secret preserved), true to resume"),
|
|
1695
|
-
}, async ({ endpoint_id, url, description, event_types, enabled }) => {
|
|
1691
|
+
}, async ({ endpoint_id, url, description, event_types, event_naming, enabled }) => {
|
|
1696
1692
|
const body = {};
|
|
1697
1693
|
if (url !== undefined)
|
|
1698
1694
|
body.url = url;
|
|
@@ -1700,6 +1696,8 @@ server.tool("update_webhook_endpoint", "Update a webhook endpoint in place: chan
|
|
|
1700
1696
|
body.description = description;
|
|
1701
1697
|
if (event_types !== undefined)
|
|
1702
1698
|
body.event_types = event_types;
|
|
1699
|
+
if (event_naming !== undefined)
|
|
1700
|
+
body.event_naming = event_naming;
|
|
1703
1701
|
if (enabled !== undefined)
|
|
1704
1702
|
body.enabled = enabled;
|
|
1705
1703
|
const result = await apiRequest({
|