@productmaker/mcp 0.7.0 → 0.8.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/dist/http.js CHANGED
@@ -611,6 +611,30 @@ var VideoModelEnum = z.enum([
611
611
  "grok",
612
612
  "kling"
613
613
  ]);
614
+ var ProductReferenceUserChoiceEnum = z.enum([
615
+ "same_product",
616
+ "variants",
617
+ "different_products",
618
+ "combo_distinct"
619
+ ]);
620
+ var ProductReferenceCandidateGroupingEnum = z.enum([
621
+ "same_product_same_variant",
622
+ "same_product_different_variants",
623
+ "different_products",
624
+ "unclear"
625
+ ]);
626
+ function buildSelectedImageUrlsHash(urls) {
627
+ const input = [
628
+ ...urls
629
+ ].sort().join("|");
630
+ let hash = 2166136261;
631
+ for (let index = 0; index < input.length; index += 1) {
632
+ hash ^= input.charCodeAt(index);
633
+ hash = Math.imul(hash, 16777619) >>> 0;
634
+ }
635
+ return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
636
+ }
637
+ __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
614
638
  var ImageCreativeStyleEnum = z.enum([
615
639
  "studio",
616
640
  "floating",
@@ -938,6 +962,23 @@ var EditTaskDraftInput = z2.object({
938
962
  scriptNotes: z2.string().max(2e3).optional()
939
963
  })
940
964
  });
965
+ var PendingActionPayload = z2.discriminatedUnion("kind", [
966
+ z2.object({
967
+ kind: z2.literal("product_reference_confirmation"),
968
+ userChoice: ProductReferenceUserChoiceEnum
969
+ })
970
+ ]);
971
+ var ResolvePendingActionInput = z2.object({
972
+ taskId: z2.string().min(1),
973
+ action: PendingActionPayload
974
+ }).strict();
975
+ var StatusForResolveSchema = z2.object({
976
+ pendingUserAction: z2.object({
977
+ kind: z2.string(),
978
+ candidateGrouping: z2.string().optional(),
979
+ selectedImageUrls: z2.array(z2.string()).optional()
980
+ }).optional()
981
+ }).passthrough();
941
982
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
942
983
  server.tool("create_product_task", 'Crea una tarea de producto desde una o varias fotos. Por defecto genera los 3 outputs (landing + video + image creatives). Devuelve un taskId.\n\nINPUTS REQUERIDOS: images (1-5 fotos del MISMO producto; la primera es la hero), language, country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto del chat: preg\xFAntale expl\xEDcitamente, incluso si el usuario est\xE1 en LATAM. Si te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nSi el usuario tiene varias fotos del MISMO producto (frente, lateral, detalle, uso), incl\xFAyelas todas en images[] hasta 5. Si tiene fotos de productos DIFERENTES, son llamadas separadas (una tarea por producto).\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos):\n- videoPreset (tipo/narrativa del video \u2014 \xFAnico campo de estilo)\n- videoTier (estilo: basic="Realista"/standard="Multiescena")\n- multiAngleVideos (booleano)\n- angleNarratives (override per-angle de la narrativa, avanzado)\n- outputs (subconjunto de {landing,video,image})\n- productUrl\n\nGUION: para revisar/editar el guion del video antes de renderizar, usa `generate_video_creative` con `pauseForVideoScriptReview: true` en vez de este tool. Despu\xE9s de la pausa: get_video_script para leer, approve_video_script para aprobar con ediciones.\n\nUsa SOLO valores declarados en los enums. Si el usuario pide algo no soportado (ej. "estilo anime", "modelo X"), p\xEDdele una opci\xF3n v\xE1lida del enum correspondiente.', CreateProductTaskInput.shape, async (input) => {
943
984
  try {
@@ -976,7 +1017,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
976
1017
  throw translateError(e);
977
1018
  }
978
1019
  });
979
- server.tool("get_task_status", "Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase o llegue al timeout (max 120s). Cuando la tarea queda en `done`, la respuesta incluye un segundo bloque markdown con previews inline de las im\xE1genes, links descargables al video MP4 y un enlace a Product Maker para ver el resultado completo. Use this to check on a task progress; pass waitSeconds for a near-synchronous response.", GetTaskStatusInput.shape, async (input, extra) => {
1020
+ server.tool("get_task_status", 'Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase o llegue al timeout (max 120s). Cuando la tarea queda en `done`, la respuesta incluye un segundo bloque markdown con previews inline de las im\xE1genes, links descargables al video MP4 y un enlace a Product Maker para ver el resultado completo.\n\nIMPORTANTE \u2014 `pendingUserAction`: si est\xE1 presente, el pipeline est\xE1 PARADO esperando que el usuario decida algo. Mira el `kind` (hoy: `"product_reference_confirmation"` \u2014 el clasificador no est\xE1 seguro si las fotos son variantes del mismo producto o productos distintos), mu\xE9strale al usuario `targetProductLabel` + `reason` en lenguaje natural, recoge su respuesta, y llama `resolve_pending_action` con `action: { kind: <mismo kind>, ... }` (las `options` v\xE1lidas vienen en el mismo objeto). Sin esto la tarea NUNCA avanza por m\xE1s que esperes. Use this to check on a task progress; pass waitSeconds for a near-synchronous response.', GetTaskStatusInput.shape, async (input, extra) => {
980
1021
  try {
981
1022
  const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`), "fetcher");
982
1023
  const signal = extra?.signal;
@@ -1033,6 +1074,45 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
1033
1074
  throw translateError(e);
1034
1075
  }
1035
1076
  });
1077
+ server.tool("resolve_pending_action", 'Resuelve una acci\xF3n que el pipeline est\xE1 esperando del usuario. La llamas cuando `get_task_status` devuelve `pendingUserAction` (sin esto la tarea NUNCA avanza por m\xE1s que esperes). El `action.kind` que mandas DEBE coincidir con el `pendingUserAction.kind` que viste en el status \u2014 es el contrato de simetr\xEDa que mantiene este tool como \xFAnica superficie de resoluci\xF3n de gates.\n\nKINDS soportados hoy:\n\n\u2022 `product_reference_confirmation` \u2014 el clasificador no est\xE1 seguro si las fotos son variantes del mismo producto o productos distintos. Payload: `userChoice` \u2208 { "same_product" (mismo producto, fotos son s\xF3lo \xE1ngulos distintos), "variants" (mismo producto, distintos colores/tallas/sabores), "combo_distinct" (son 2 productos distintos vendidos juntos como combo \u2014 marca la tarea como combo), "different_products" (productos distintos y el usuario quiere generar s\xF3lo uno \u2014 el backend bloquea hasta que selecciones im\xE1genes de un solo producto; normalmente NO es la respuesta correcta para destrabar) }. PREG\xDANTALE al usuario antes de invocar \u2014 `pendingUserAction.targetProductLabel` y `reason` te dan contexto para formularle la pregunta en lenguaje natural. Usa SOLO valores presentes en `pendingUserAction.options`.\n\nFuturos kinds (no implementados a\xFAn) se agregar\xE1n como nuevas variantes del `action` discriminated union \u2014 el flujo "lee kind del status \u2192 manda mismo kind aqu\xED" sigue igual.', ResolvePendingActionInput.shape, async (input) => {
1078
+ try {
1079
+ const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
1080
+ const status = StatusForResolveSchema.parse(raw);
1081
+ const pending = status.pendingUserAction;
1082
+ if (!pending) {
1083
+ throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
1084
+ }
1085
+ if (pending.kind !== input.action.kind) {
1086
+ throw new Error(`La tarea espera una acci\xF3n de tipo "${pending.kind}" pero mandaste "${input.action.kind}". Reusa el mismo kind que viste en pendingUserAction.`);
1087
+ }
1088
+ switch (input.action.kind) {
1089
+ case "product_reference_confirmation": {
1090
+ const candidateGrouping = pending.candidateGrouping;
1091
+ const selectedImageUrls = pending.selectedImageUrls ?? [];
1092
+ if (!candidateGrouping || selectedImageUrls.length === 0) {
1093
+ throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
1094
+ }
1095
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
1096
+ productReferenceConfirmation: {
1097
+ candidateGrouping,
1098
+ userChoice: input.action.userChoice,
1099
+ // Server overwrites with its own server-side computation;
1100
+ // we send the matching MCP-mirror value so the wire body
1101
+ // is internally coherent if the server skips overwrite.
1102
+ selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
1103
+ }
1104
+ });
1105
+ return asText(r);
1106
+ }
1107
+ default: {
1108
+ const _exhaustive = input.action.kind;
1109
+ throw new Error(`Unsupported pending action kind: ${String(_exhaustive)}`);
1110
+ }
1111
+ }
1112
+ } catch (e) {
1113
+ throw translateError(e);
1114
+ }
1115
+ });
1036
1116
  }
1037
1117
  __name(registerTaskTools, "registerTaskTools");
1038
1118
 
package/dist/stdio.js CHANGED
@@ -608,6 +608,30 @@ var VideoModelEnum = z.enum([
608
608
  "grok",
609
609
  "kling"
610
610
  ]);
611
+ var ProductReferenceUserChoiceEnum = z.enum([
612
+ "same_product",
613
+ "variants",
614
+ "different_products",
615
+ "combo_distinct"
616
+ ]);
617
+ var ProductReferenceCandidateGroupingEnum = z.enum([
618
+ "same_product_same_variant",
619
+ "same_product_different_variants",
620
+ "different_products",
621
+ "unclear"
622
+ ]);
623
+ function buildSelectedImageUrlsHash(urls) {
624
+ const input = [
625
+ ...urls
626
+ ].sort().join("|");
627
+ let hash = 2166136261;
628
+ for (let index = 0; index < input.length; index += 1) {
629
+ hash ^= input.charCodeAt(index);
630
+ hash = Math.imul(hash, 16777619) >>> 0;
631
+ }
632
+ return `fnv1a:${hash.toString(16).padStart(8, "0")}:${urls.length}`;
633
+ }
634
+ __name(buildSelectedImageUrlsHash, "buildSelectedImageUrlsHash");
611
635
  var ImageCreativeStyleEnum = z.enum([
612
636
  "studio",
613
637
  "floating",
@@ -894,6 +918,23 @@ var EditTaskDraftInput = z2.object({
894
918
  scriptNotes: z2.string().max(2e3).optional()
895
919
  })
896
920
  });
921
+ var PendingActionPayload = z2.discriminatedUnion("kind", [
922
+ z2.object({
923
+ kind: z2.literal("product_reference_confirmation"),
924
+ userChoice: ProductReferenceUserChoiceEnum
925
+ })
926
+ ]);
927
+ var ResolvePendingActionInput = z2.object({
928
+ taskId: z2.string().min(1),
929
+ action: PendingActionPayload
930
+ }).strict();
931
+ var StatusForResolveSchema = z2.object({
932
+ pendingUserAction: z2.object({
933
+ kind: z2.string(),
934
+ candidateGrouping: z2.string().optional(),
935
+ selectedImageUrls: z2.array(z2.string()).optional()
936
+ }).optional()
937
+ }).passthrough();
897
938
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
898
939
  server.tool("create_product_task", 'Crea una tarea de producto desde una o varias fotos. Por defecto genera los 3 outputs (landing + video + image creatives). Devuelve un taskId.\n\nINPUTS REQUERIDOS: images (1-5 fotos del MISMO producto; la primera es la hero), language, country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto del chat: preg\xFAntale expl\xEDcitamente, incluso si el usuario est\xE1 en LATAM. Si te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nSi el usuario tiene varias fotos del MISMO producto (frente, lateral, detalle, uso), incl\xFAyelas todas en images[] hasta 5. Si tiene fotos de productos DIFERENTES, son llamadas separadas (una tarea por producto).\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos):\n- videoPreset (tipo/narrativa del video \u2014 \xFAnico campo de estilo)\n- videoTier (estilo: basic="Realista"/standard="Multiescena")\n- multiAngleVideos (booleano)\n- angleNarratives (override per-angle de la narrativa, avanzado)\n- outputs (subconjunto de {landing,video,image})\n- productUrl\n\nGUION: para revisar/editar el guion del video antes de renderizar, usa `generate_video_creative` con `pauseForVideoScriptReview: true` en vez de este tool. Despu\xE9s de la pausa: get_video_script para leer, approve_video_script para aprobar con ediciones.\n\nUsa SOLO valores declarados en los enums. Si el usuario pide algo no soportado (ej. "estilo anime", "modelo X"), p\xEDdele una opci\xF3n v\xE1lida del enum correspondiente.', CreateProductTaskInput.shape, async (input) => {
899
940
  try {
@@ -932,7 +973,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
932
973
  throw translateError(e);
933
974
  }
934
975
  });
935
- server.tool("get_task_status", "Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase o llegue al timeout (max 120s). Cuando la tarea queda en `done`, la respuesta incluye un segundo bloque markdown con previews inline de las im\xE1genes, links descargables al video MP4 y un enlace a Product Maker para ver el resultado completo. Use this to check on a task progress; pass waitSeconds for a near-synchronous response.", GetTaskStatusInput.shape, async (input, extra) => {
976
+ server.tool("get_task_status", 'Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase o llegue al timeout (max 120s). Cuando la tarea queda en `done`, la respuesta incluye un segundo bloque markdown con previews inline de las im\xE1genes, links descargables al video MP4 y un enlace a Product Maker para ver el resultado completo.\n\nIMPORTANTE \u2014 `pendingUserAction`: si est\xE1 presente, el pipeline est\xE1 PARADO esperando que el usuario decida algo. Mira el `kind` (hoy: `"product_reference_confirmation"` \u2014 el clasificador no est\xE1 seguro si las fotos son variantes del mismo producto o productos distintos), mu\xE9strale al usuario `targetProductLabel` + `reason` en lenguaje natural, recoge su respuesta, y llama `resolve_pending_action` con `action: { kind: <mismo kind>, ... }` (las `options` v\xE1lidas vienen en el mismo objeto). Sin esto la tarea NUNCA avanza por m\xE1s que esperes. Use this to check on a task progress; pass waitSeconds for a near-synchronous response.', GetTaskStatusInput.shape, async (input, extra) => {
936
977
  try {
937
978
  const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`), "fetcher");
938
979
  const signal = extra?.signal;
@@ -989,6 +1030,45 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
989
1030
  throw translateError(e);
990
1031
  }
991
1032
  });
1033
+ server.tool("resolve_pending_action", 'Resuelve una acci\xF3n que el pipeline est\xE1 esperando del usuario. La llamas cuando `get_task_status` devuelve `pendingUserAction` (sin esto la tarea NUNCA avanza por m\xE1s que esperes). El `action.kind` que mandas DEBE coincidir con el `pendingUserAction.kind` que viste en el status \u2014 es el contrato de simetr\xEDa que mantiene este tool como \xFAnica superficie de resoluci\xF3n de gates.\n\nKINDS soportados hoy:\n\n\u2022 `product_reference_confirmation` \u2014 el clasificador no est\xE1 seguro si las fotos son variantes del mismo producto o productos distintos. Payload: `userChoice` \u2208 { "same_product" (mismo producto, fotos son s\xF3lo \xE1ngulos distintos), "variants" (mismo producto, distintos colores/tallas/sabores), "combo_distinct" (son 2 productos distintos vendidos juntos como combo \u2014 marca la tarea como combo), "different_products" (productos distintos y el usuario quiere generar s\xF3lo uno \u2014 el backend bloquea hasta que selecciones im\xE1genes de un solo producto; normalmente NO es la respuesta correcta para destrabar) }. PREG\xDANTALE al usuario antes de invocar \u2014 `pendingUserAction.targetProductLabel` y `reason` te dan contexto para formularle la pregunta en lenguaje natural. Usa SOLO valores presentes en `pendingUserAction.options`.\n\nFuturos kinds (no implementados a\xFAn) se agregar\xE1n como nuevas variantes del `action` discriminated union \u2014 el flujo "lee kind del status \u2192 manda mismo kind aqu\xED" sigue igual.', ResolvePendingActionInput.shape, async (input) => {
1034
+ try {
1035
+ const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
1036
+ const status = StatusForResolveSchema.parse(raw);
1037
+ const pending = status.pendingUserAction;
1038
+ if (!pending) {
1039
+ throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
1040
+ }
1041
+ if (pending.kind !== input.action.kind) {
1042
+ throw new Error(`La tarea espera una acci\xF3n de tipo "${pending.kind}" pero mandaste "${input.action.kind}". Reusa el mismo kind que viste en pendingUserAction.`);
1043
+ }
1044
+ switch (input.action.kind) {
1045
+ case "product_reference_confirmation": {
1046
+ const candidateGrouping = pending.candidateGrouping;
1047
+ const selectedImageUrls = pending.selectedImageUrls ?? [];
1048
+ if (!candidateGrouping || selectedImageUrls.length === 0) {
1049
+ throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
1050
+ }
1051
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
1052
+ productReferenceConfirmation: {
1053
+ candidateGrouping,
1054
+ userChoice: input.action.userChoice,
1055
+ // Server overwrites with its own server-side computation;
1056
+ // we send the matching MCP-mirror value so the wire body
1057
+ // is internally coherent if the server skips overwrite.
1058
+ selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
1059
+ }
1060
+ });
1061
+ return asText(r);
1062
+ }
1063
+ default: {
1064
+ const _exhaustive = input.action.kind;
1065
+ throw new Error(`Unsupported pending action kind: ${String(_exhaustive)}`);
1066
+ }
1067
+ }
1068
+ } catch (e) {
1069
+ throw translateError(e);
1070
+ }
1071
+ });
992
1072
  }
993
1073
  __name(registerTaskTools, "registerTaskTools");
994
1074
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Product Maker MCP server — connect Claude Desktop, Claude Code, ChatGPT, or Gemini to Product Maker AI to generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from your AI assistant.",