@productmaker/mcp 0.7.0 → 0.9.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.
Files changed (3) hide show
  1. package/dist/http.js +113 -1
  2. package/dist/stdio.js +113 -1
  3. package/package.json +1 -1
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,38 @@ 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
+ z2.object({
971
+ kind: z2.literal("angle_review_approval"),
972
+ // All edits are optional — empty action body just approves the
973
+ // backend-generated draft as-is. Field shapes mirror ApproveAnglesRequest
974
+ // in @packages/types/wizard.ts (the only path-of-resolution endpoint is
975
+ // POST /v1/tasks/:taskId/approve-angles).
976
+ customAngle: z2.string().min(1).max(480).optional(),
977
+ painPoints: z2.string().max(3e3).optional(),
978
+ benefits: z2.string().max(3e3).optional(),
979
+ features: z2.array(z2.string().min(1).max(300)).max(10).optional()
980
+ })
981
+ ]);
982
+ var ResolvePendingActionInput = z2.object({
983
+ taskId: z2.string().min(1),
984
+ action: PendingActionPayload
985
+ }).strict();
986
+ var StatusForResolveSchema = z2.object({
987
+ pendingUserAction: z2.object({
988
+ kind: z2.string(),
989
+ candidateGrouping: z2.string().optional(),
990
+ selectedImageUrls: z2.array(z2.string()).optional(),
991
+ customAngleDraft: z2.string().optional(),
992
+ painPointsDraft: z2.string().optional(),
993
+ benefitsDraft: z2.string().optional(),
994
+ featuresDraft: z2.array(z2.string()).optional()
995
+ }).optional()
996
+ }).passthrough();
941
997
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
942
998
  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
999
  try {
@@ -976,7 +1032,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
976
1032
  throw translateError(e);
977
1033
  }
978
1034
  });
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) => {
1035
+ 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
1036
  try {
981
1037
  const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`), "fetcher");
982
1038
  const signal = extra?.signal;
@@ -1033,6 +1089,62 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
1033
1089
  throw translateError(e);
1034
1090
  }
1035
1091
  });
1092
+ 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\n\u2022 `angle_review_approval` \u2014 el usuario opto por `pauseForAngleReview: true` al crear el creativo (en `generate_image_creatives`, `generate_video_creative` o `generate_landing`). La IA ya gener\xF3 el \xE1ngulo + pain points + benefits + features y la tarea est\xE1 pausada esperando que el usuario apruebe o edite antes de renderizar. Lee `customAngleDraft`, `painPointsDraft`, `benefitsDraft`, `featuresDraft` del `pendingUserAction` para mostr\xE1rselos al usuario en lenguaje natural ("la IA propuso este \xE1ngulo: ... \xBFlo apruebas o cambias algo?"). Payload (TODOS opcionales \u2014 sin nada = aprobar tal cual; con campos = aprobar con ediciones): `customAngle` (override del \xE1ngulo), `painPoints`, `benefits`, `features` (array de 1-10 bullets). Llamar sin ning\xFAn campo es v\xE1lido y aprueba el draft sin cambios.\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) => {
1093
+ try {
1094
+ const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
1095
+ const status = StatusForResolveSchema.parse(raw);
1096
+ const pending = status.pendingUserAction;
1097
+ if (!pending) {
1098
+ throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
1099
+ }
1100
+ if (pending.kind !== input.action.kind) {
1101
+ 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.`);
1102
+ }
1103
+ switch (input.action.kind) {
1104
+ case "product_reference_confirmation": {
1105
+ const candidateGrouping = pending.candidateGrouping;
1106
+ const selectedImageUrls = pending.selectedImageUrls ?? [];
1107
+ if (!candidateGrouping || selectedImageUrls.length === 0) {
1108
+ throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
1109
+ }
1110
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
1111
+ productReferenceConfirmation: {
1112
+ candidateGrouping,
1113
+ userChoice: input.action.userChoice,
1114
+ // Server overwrites with its own server-side computation;
1115
+ // we send the matching MCP-mirror value so the wire body
1116
+ // is internally coherent if the server skips overwrite.
1117
+ selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
1118
+ }
1119
+ });
1120
+ return asText(r);
1121
+ }
1122
+ case "angle_review_approval": {
1123
+ const body = {};
1124
+ if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
1125
+ body.customAngle = pending.customAngleDraft;
1126
+ }
1127
+ if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
1128
+ if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
1129
+ if (pending.featuresDraft != null) body.features = [
1130
+ ...pending.featuresDraft
1131
+ ];
1132
+ if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
1133
+ if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
1134
+ if (input.action.benefits != null) body.benefits = input.action.benefits;
1135
+ if (input.action.features != null) body.features = input.action.features;
1136
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`, body);
1137
+ return asText(r);
1138
+ }
1139
+ default: {
1140
+ const _exhaustive = input.action;
1141
+ throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
1142
+ }
1143
+ }
1144
+ } catch (e) {
1145
+ throw translateError(e);
1146
+ }
1147
+ });
1036
1148
  }
1037
1149
  __name(registerTaskTools, "registerTaskTools");
1038
1150
 
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,38 @@ 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
+ z2.object({
927
+ kind: z2.literal("angle_review_approval"),
928
+ // All edits are optional — empty action body just approves the
929
+ // backend-generated draft as-is. Field shapes mirror ApproveAnglesRequest
930
+ // in @packages/types/wizard.ts (the only path-of-resolution endpoint is
931
+ // POST /v1/tasks/:taskId/approve-angles).
932
+ customAngle: z2.string().min(1).max(480).optional(),
933
+ painPoints: z2.string().max(3e3).optional(),
934
+ benefits: z2.string().max(3e3).optional(),
935
+ features: z2.array(z2.string().min(1).max(300)).max(10).optional()
936
+ })
937
+ ]);
938
+ var ResolvePendingActionInput = z2.object({
939
+ taskId: z2.string().min(1),
940
+ action: PendingActionPayload
941
+ }).strict();
942
+ var StatusForResolveSchema = z2.object({
943
+ pendingUserAction: z2.object({
944
+ kind: z2.string(),
945
+ candidateGrouping: z2.string().optional(),
946
+ selectedImageUrls: z2.array(z2.string()).optional(),
947
+ customAngleDraft: z2.string().optional(),
948
+ painPointsDraft: z2.string().optional(),
949
+ benefitsDraft: z2.string().optional(),
950
+ featuresDraft: z2.array(z2.string()).optional()
951
+ }).optional()
952
+ }).passthrough();
897
953
  function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
898
954
  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
955
  try {
@@ -932,7 +988,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
932
988
  throw translateError(e);
933
989
  }
934
990
  });
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) => {
991
+ 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
992
  try {
937
993
  const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`), "fetcher");
938
994
  const signal = extra?.signal;
@@ -989,6 +1045,62 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
989
1045
  throw translateError(e);
990
1046
  }
991
1047
  });
1048
+ 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\n\u2022 `angle_review_approval` \u2014 el usuario opto por `pauseForAngleReview: true` al crear el creativo (en `generate_image_creatives`, `generate_video_creative` o `generate_landing`). La IA ya gener\xF3 el \xE1ngulo + pain points + benefits + features y la tarea est\xE1 pausada esperando que el usuario apruebe o edite antes de renderizar. Lee `customAngleDraft`, `painPointsDraft`, `benefitsDraft`, `featuresDraft` del `pendingUserAction` para mostr\xE1rselos al usuario en lenguaje natural ("la IA propuso este \xE1ngulo: ... \xBFlo apruebas o cambias algo?"). Payload (TODOS opcionales \u2014 sin nada = aprobar tal cual; con campos = aprobar con ediciones): `customAngle` (override del \xE1ngulo), `painPoints`, `benefits`, `features` (array de 1-10 bullets). Llamar sin ning\xFAn campo es v\xE1lido y aprueba el draft sin cambios.\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) => {
1049
+ try {
1050
+ const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
1051
+ const status = StatusForResolveSchema.parse(raw);
1052
+ const pending = status.pendingUserAction;
1053
+ if (!pending) {
1054
+ throw new Error("No hay acci\xF3n pendiente para esta tarea \u2014 llama get_task_status primero para confirmar el estado.");
1055
+ }
1056
+ if (pending.kind !== input.action.kind) {
1057
+ 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.`);
1058
+ }
1059
+ switch (input.action.kind) {
1060
+ case "product_reference_confirmation": {
1061
+ const candidateGrouping = pending.candidateGrouping;
1062
+ const selectedImageUrls = pending.selectedImageUrls ?? [];
1063
+ if (!candidateGrouping || selectedImageUrls.length === 0) {
1064
+ throw new Error("Estado inconsistente: la tarea est\xE1 pausada pero falta candidateGrouping o selectedImageUrls.");
1065
+ }
1066
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/confirm-product-references`, {
1067
+ productReferenceConfirmation: {
1068
+ candidateGrouping,
1069
+ userChoice: input.action.userChoice,
1070
+ // Server overwrites with its own server-side computation;
1071
+ // we send the matching MCP-mirror value so the wire body
1072
+ // is internally coherent if the server skips overwrite.
1073
+ selectedImageUrlsHash: buildSelectedImageUrlsHash(selectedImageUrls)
1074
+ }
1075
+ });
1076
+ return asText(r);
1077
+ }
1078
+ case "angle_review_approval": {
1079
+ const body = {};
1080
+ if (pending.customAngleDraft && pending.customAngleDraft.length > 0) {
1081
+ body.customAngle = pending.customAngleDraft;
1082
+ }
1083
+ if (pending.painPointsDraft != null) body.painPoints = pending.painPointsDraft;
1084
+ if (pending.benefitsDraft != null) body.benefits = pending.benefitsDraft;
1085
+ if (pending.featuresDraft != null) body.features = [
1086
+ ...pending.featuresDraft
1087
+ ];
1088
+ if (input.action.customAngle != null) body.customAngle = input.action.customAngle;
1089
+ if (input.action.painPoints != null) body.painPoints = input.action.painPoints;
1090
+ if (input.action.benefits != null) body.benefits = input.action.benefits;
1091
+ if (input.action.features != null) body.features = input.action.features;
1092
+ const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/approve-angles`, body);
1093
+ return asText(r);
1094
+ }
1095
+ default: {
1096
+ const _exhaustive = input.action;
1097
+ throw new Error(`Unsupported pending action kind: ${String(_exhaustive.kind)}`);
1098
+ }
1099
+ }
1100
+ } catch (e) {
1101
+ throw translateError(e);
1102
+ }
1103
+ });
992
1104
  }
993
1105
  __name(registerTaskTools, "registerTaskTools");
994
1106
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.9.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.",