@productmaker/mcp 1.5.0 → 1.7.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
@@ -113,7 +113,7 @@ var ApiClient = class {
113
113
  };
114
114
 
115
115
  // src/index.ts
116
- var MCP_VERSION = "1.5.0";
116
+ var MCP_VERSION = "1.7.0";
117
117
 
118
118
  // src/tools/tasks.ts
119
119
  import { z as z2 } from "zod";
@@ -396,6 +396,15 @@ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
396
396
  import { z } from "zod";
397
397
  var Lang = z.string().min(2).max(10);
398
398
  var Country = z.string().min(2).max(3);
399
+ var LandingStrategyEnum = z.enum([
400
+ "auto",
401
+ "classic",
402
+ "pain_solution",
403
+ "social_proof",
404
+ "benefits_clarity",
405
+ "offer_value",
406
+ "lifestyle_identity"
407
+ ]);
399
408
  var VideoPresetEnum = z.enum([
400
409
  "auto",
401
410
  "ugc_dynamic",
@@ -584,7 +593,7 @@ function isRecord(v) {
584
593
  }
585
594
  __name(isRecord, "isRecord");
586
595
  function escapeMdLabel(s) {
587
- return s.replace(/[\[\]\(\)\{\}`<>\r\n\\*_]/g, " ").replace(/\s+/g, " ").trim();
596
+ return s.replace(/[[\](){}\x60<>\r\n\\*_]/g, " ").replace(/\s+/g, " ").trim();
588
597
  }
589
598
  __name(escapeMdLabel, "escapeMdLabel");
590
599
  function flatten(obj) {
@@ -1187,7 +1196,8 @@ var CreateProductTaskInput = z2.object({
1187
1196
  ])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
1188
1197
  competitorScanEnabled: z2.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
1189
1198
  brandId: z2.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
1190
- creativeDirection: z2.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.')
1199
+ creativeDirection: z2.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
1200
+ landingStrategy: LandingStrategyEnum.default("auto").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "auto", "classic" (estructura original probada), "pain_solution", "social_proof", "benefits_clarity", "offer_value" o "lifestyle_identity". Default "auto".')
1191
1201
  }).strict();
1192
1202
  var GetTaskStatusInput = z2.object({
1193
1203
  taskId: z2.string().min(1),
@@ -1216,7 +1226,18 @@ var PendingActionPayload = z2.discriminatedUnion("kind", [
1216
1226
  z2.object({
1217
1227
  kind: z2.literal("product_reference_confirmation"),
1218
1228
  userChoice: ProductReferenceUserChoiceEnum,
1219
- confirmedProductNames: z2.array(z2.string().min(1).max(120)).min(2).max(8).optional()
1229
+ confirmedProductNames: z2.array(z2.string().min(1).max(120)).min(2).max(8).optional(),
1230
+ // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
1231
+ // reachable via MCP. Valid ONLY together with userChoice='variants' (same
1232
+ // product in several colors): renders one shot per color and stitches them,
1233
+ // so every variant color is shown off. Ignored for any other userChoice.
1234
+ // We deliberately do NOT expose the raw `standard` tier on the MCP video
1235
+ // knobs (VideoTierEnum stays {basic}) because a general tier dial with no
1236
+ // cost surface caused a prod over-spend incident (2026-07-04); this flag is
1237
+ // safe because multi-scene IS the intended render for a color variant set,
1238
+ // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
1239
+ // confirm call; the backend re-gates it (variants + variant_set only).
1240
+ useMultiSceneShowcase: z2.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
1220
1241
  }),
1221
1242
  z2.object({
1222
1243
  kind: z2.literal("angle_review_approval"),
@@ -1234,6 +1255,10 @@ var ResolvePendingActionInput = z2.object({
1234
1255
  taskId: z2.string().min(1),
1235
1256
  action: PendingActionPayload
1236
1257
  }).strict();
1258
+ function resolveVariantConfirmVideoTier(action) {
1259
+ return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
1260
+ }
1261
+ __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
1237
1262
  var StatusForResolveSchema = z2.object({
1238
1263
  pendingUserAction: z2.object({
1239
1264
  kind: z2.string(),
@@ -1294,6 +1319,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
1294
1319
  "options.competitorScanEnabled": input.competitorScanEnabled,
1295
1320
  "options.brandId": input.brandId,
1296
1321
  "options.creativeDirection": input.creativeDirection,
1322
+ "options.landingStrategy": input.landingStrategy,
1297
1323
  url: input.productUrl
1298
1324
  });
1299
1325
  const r = await api.postMultipart("/v1/ingest", {
@@ -1368,7 +1394,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
1368
1394
  throw translateError(e);
1369
1395
  }
1370
1396
  });
1371
- 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`. Si el status trae `proposedProductNames` (lista propuesta de nombres), el combo es INCIERTO: mu\xE9strale los nombres al usuario y, si confirma que son productos distintos, manda `userChoice: "combo_distinct"` JUNTO CON `confirmedProductNames` (la lista confirmada o editada, 2-8 nombres) para separarlo en productos distintos; si el usuario dice que es un solo producto, manda `userChoice: "same_product"` (sin `confirmedProductNames`).\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) => {
1397
+ 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`. Si el status trae `proposedProductNames` (lista propuesta de nombres), el combo es INCIERTO: mu\xE9strale los nombres al usuario y, si confirma que son productos distintos, manda `userChoice: "combo_distinct"` JUNTO CON `confirmedProductNames` (la lista confirmada o editada, 2-8 nombres) para separarlo en productos distintos; si el usuario dice que es un solo producto, manda `userChoice: "same_product"` (sin `confirmedProductNames`). Cuando el producto son VARIANTES DE COLOR (mismo producto, distintos colores) puedes ofrecer un video "Multiescena" que muestra una toma por color: manda `userChoice: "variants"` JUNTO CON `useMultiSceneShowcase: true`. AVISA al usuario que ese modo se factura por escena (m\xE1s caro que el render normal de una sola toma) antes de activarlo; si prefiere el render normal, manda solo `userChoice: "variants"` sin el flag.\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) => {
1372
1398
  try {
1373
1399
  const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
1374
1400
  const status = StatusForResolveSchema.parse(raw);
@@ -1397,6 +1423,12 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
1397
1423
  },
1398
1424
  ...input.action.confirmedProductNames ? {
1399
1425
  confirmedProductNames: input.action.confirmedProductNames
1426
+ } : {},
1427
+ // Multi-scene color showcase → upgrade to STANDARD. Gated to the
1428
+ // variants choice client-side so the wire body stays coherent; the
1429
+ // backend re-gates on variant_set grouping.
1430
+ ...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
1431
+ videoTier: "standard"
1400
1432
  } : {}
1401
1433
  });
1402
1434
  return asText(r);
@@ -1518,7 +1550,8 @@ var LandingInput = z3.object({
1518
1550
  isCombo: CreativeRefs.isCombo,
1519
1551
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
1520
1552
  brandId: CreativeRefs.brandId,
1521
- creativeDirection: CreativeRefs.creativeDirection
1553
+ creativeDirection: CreativeRefs.creativeDirection,
1554
+ landingStrategy: LandingStrategyEnum.default("auto").describe('Opcional. Estructura de la landing cl\xE1sica: "auto" (la IA decide), "classic" (estructura original probada), "pain_solution" (problema\u2192soluci\xF3n), "social_proof" (confianza y evidencia), "benefits_clarity" (beneficios claros), "offer_value" (promoci\xF3n/valor) o "lifestyle_identity" (estilo de vida). Default "auto".')
1522
1555
  });
1523
1556
  function registerCreativeTools(server, api, { transport = "http" } = {}) {
1524
1557
  server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio.\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: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (m\xE1x 15), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nESTILO: v\xEDa MCP todos los videos se generan en estilo "Realista" (render \xFAnico de 15s con audio nativo). El estilo "Multiescena" (30s, multi-toma) NO est\xE1 disponible por MCP por su costo \u2014 si el usuario lo pide, dile que lo genere desde la web.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes.', VideoInput.shape, async (input) => {
@@ -1557,7 +1590,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
1557
1590
  throw translateError(e);
1558
1591
  }
1559
1592
  });
1560
- server.tool("generate_landing", 'Genera una landing page lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods 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: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection.', LandingInput.shape, async (input) => {
1593
+ server.tool("generate_landing", 'Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods 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: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection, landingStrategy.', LandingInput.shape, async (input) => {
1561
1594
  try {
1562
1595
  return await submitCreative(api, {
1563
1596
  path: "/v1/creatives/landing",
package/dist/stdio.js CHANGED
@@ -110,7 +110,7 @@ var ApiClient = class {
110
110
  };
111
111
 
112
112
  // src/index.ts
113
- var MCP_VERSION = "1.5.0";
113
+ var MCP_VERSION = "1.7.0";
114
114
 
115
115
  // src/tools/tasks.ts
116
116
  import { z as z2 } from "zod";
@@ -393,6 +393,15 @@ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
393
393
  import { z } from "zod";
394
394
  var Lang = z.string().min(2).max(10);
395
395
  var Country = z.string().min(2).max(3);
396
+ var LandingStrategyEnum = z.enum([
397
+ "auto",
398
+ "classic",
399
+ "pain_solution",
400
+ "social_proof",
401
+ "benefits_clarity",
402
+ "offer_value",
403
+ "lifestyle_identity"
404
+ ]);
396
405
  var VideoPresetEnum = z.enum([
397
406
  "auto",
398
407
  "ugc_dynamic",
@@ -581,7 +590,7 @@ function isRecord(v) {
581
590
  }
582
591
  __name(isRecord, "isRecord");
583
592
  function escapeMdLabel(s) {
584
- return s.replace(/[\[\]\(\)\{\}`<>\r\n\\*_]/g, " ").replace(/\s+/g, " ").trim();
593
+ return s.replace(/[[\](){}\x60<>\r\n\\*_]/g, " ").replace(/\s+/g, " ").trim();
585
594
  }
586
595
  __name(escapeMdLabel, "escapeMdLabel");
587
596
  function flatten(obj) {
@@ -1143,7 +1152,8 @@ var CreateProductTaskInput = z2.object({
1143
1152
  ])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
1144
1153
  competitorScanEnabled: z2.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
1145
1154
  brandId: z2.string().uuid().optional().describe("Opcional. ID de una marca del usuario; si se env\xEDa, el sistema usa su voz, colores y persona para enriquecer los outputs generados."),
1146
- creativeDirection: z2.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.')
1155
+ creativeDirection: z2.string().max(500).optional().describe('Opcional. Direcci\xF3n creativa en texto libre (colores, mood, estilo) que aplica a los outputs de IMAGEN y LANDING (no al video \u2014 el estilo/ambiente del video se dirige con scriptNotes). M\xC1XIMA PRIORIDAD: anula marca, colores y estilo por defecto. Ej. "amarillo y azul, minimalista". M\xE1x 500 caracteres.'),
1156
+ landingStrategy: LandingStrategyEnum.default("auto").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "auto", "classic" (estructura original probada), "pain_solution", "social_proof", "benefits_clarity", "offer_value" o "lifestyle_identity". Default "auto".')
1147
1157
  }).strict();
1148
1158
  var GetTaskStatusInput = z2.object({
1149
1159
  taskId: z2.string().min(1),
@@ -1172,7 +1182,18 @@ var PendingActionPayload = z2.discriminatedUnion("kind", [
1172
1182
  z2.object({
1173
1183
  kind: z2.literal("product_reference_confirmation"),
1174
1184
  userChoice: ProductReferenceUserChoiceEnum,
1175
- confirmedProductNames: z2.array(z2.string().min(1).max(120)).min(2).max(8).optional()
1185
+ confirmedProductNames: z2.array(z2.string().min(1).max(120)).min(2).max(8).optional(),
1186
+ // SCOPED multi-scene opt-in — the ONLY way STANDARD ("Multiescena") is
1187
+ // reachable via MCP. Valid ONLY together with userChoice='variants' (same
1188
+ // product in several colors): renders one shot per color and stitches them,
1189
+ // so every variant color is shown off. Ignored for any other userChoice.
1190
+ // We deliberately do NOT expose the raw `standard` tier on the MCP video
1191
+ // knobs (VideoTierEnum stays {basic}) because a general tier dial with no
1192
+ // cost surface caused a prod over-spend incident (2026-07-04); this flag is
1193
+ // safe because multi-scene IS the intended render for a color variant set,
1194
+ // not a blind quality upsell. Maps to `videoTier: 'standard'` on the
1195
+ // confirm call; the backend re-gates it (variants + variant_set only).
1196
+ useMultiSceneShowcase: z2.boolean().optional().describe('Opcional (SOLO con userChoice="variants"). true = render Multiescena: una toma por color, mostrando todas las variantes, hasta 30s. Se factura POR ESCENA (m\xE1s caro que el render normal de una sola toma) \u2014 AV\xCDSALE del mayor costo al usuario antes de activarlo. Omitir = render est\xE1ndar de una sola toma. Se ignora si userChoice no es "variants".')
1176
1197
  }),
1177
1198
  z2.object({
1178
1199
  kind: z2.literal("angle_review_approval"),
@@ -1190,6 +1211,10 @@ var ResolvePendingActionInput = z2.object({
1190
1211
  taskId: z2.string().min(1),
1191
1212
  action: PendingActionPayload
1192
1213
  }).strict();
1214
+ function resolveVariantConfirmVideoTier(action) {
1215
+ return action.useMultiSceneShowcase && action.userChoice === "variants" ? "standard" : void 0;
1216
+ }
1217
+ __name(resolveVariantConfirmVideoTier, "resolveVariantConfirmVideoTier");
1193
1218
  var StatusForResolveSchema = z2.object({
1194
1219
  pendingUserAction: z2.object({
1195
1220
  kind: z2.string(),
@@ -1250,6 +1275,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
1250
1275
  "options.competitorScanEnabled": input.competitorScanEnabled,
1251
1276
  "options.brandId": input.brandId,
1252
1277
  "options.creativeDirection": input.creativeDirection,
1278
+ "options.landingStrategy": input.landingStrategy,
1253
1279
  url: input.productUrl
1254
1280
  });
1255
1281
  const r = await api.postMultipart("/v1/ingest", {
@@ -1324,7 +1350,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
1324
1350
  throw translateError(e);
1325
1351
  }
1326
1352
  });
1327
- 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`. Si el status trae `proposedProductNames` (lista propuesta de nombres), el combo es INCIERTO: mu\xE9strale los nombres al usuario y, si confirma que son productos distintos, manda `userChoice: "combo_distinct"` JUNTO CON `confirmedProductNames` (la lista confirmada o editada, 2-8 nombres) para separarlo en productos distintos; si el usuario dice que es un solo producto, manda `userChoice: "same_product"` (sin `confirmedProductNames`).\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) => {
1353
+ 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`. Si el status trae `proposedProductNames` (lista propuesta de nombres), el combo es INCIERTO: mu\xE9strale los nombres al usuario y, si confirma que son productos distintos, manda `userChoice: "combo_distinct"` JUNTO CON `confirmedProductNames` (la lista confirmada o editada, 2-8 nombres) para separarlo en productos distintos; si el usuario dice que es un solo producto, manda `userChoice: "same_product"` (sin `confirmedProductNames`). Cuando el producto son VARIANTES DE COLOR (mismo producto, distintos colores) puedes ofrecer un video "Multiescena" que muestra una toma por color: manda `userChoice: "variants"` JUNTO CON `useMultiSceneShowcase: true`. AVISA al usuario que ese modo se factura por escena (m\xE1s caro que el render normal de una sola toma) antes de activarlo; si prefiere el render normal, manda solo `userChoice: "variants"` sin el flag.\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) => {
1328
1354
  try {
1329
1355
  const raw = await api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`);
1330
1356
  const status = StatusForResolveSchema.parse(raw);
@@ -1353,6 +1379,12 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
1353
1379
  },
1354
1380
  ...input.action.confirmedProductNames ? {
1355
1381
  confirmedProductNames: input.action.confirmedProductNames
1382
+ } : {},
1383
+ // Multi-scene color showcase → upgrade to STANDARD. Gated to the
1384
+ // variants choice client-side so the wire body stays coherent; the
1385
+ // backend re-gates on variant_set grouping.
1386
+ ...resolveVariantConfirmVideoTier(input.action) === "standard" ? {
1387
+ videoTier: "standard"
1356
1388
  } : {}
1357
1389
  });
1358
1390
  return asText(r);
@@ -1474,7 +1506,8 @@ var LandingInput = z3.object({
1474
1506
  isCombo: CreativeRefs.isCombo,
1475
1507
  competitorScanEnabled: CreativeRefs.competitorScanEnabled,
1476
1508
  brandId: CreativeRefs.brandId,
1477
- creativeDirection: CreativeRefs.creativeDirection
1509
+ creativeDirection: CreativeRefs.creativeDirection,
1510
+ landingStrategy: LandingStrategyEnum.default("auto").describe('Opcional. Estructura de la landing cl\xE1sica: "auto" (la IA decide), "classic" (estructura original probada), "pain_solution" (problema\u2192soluci\xF3n), "social_proof" (confianza y evidencia), "benefits_clarity" (beneficios claros), "offer_value" (promoci\xF3n/valor) o "lifestyle_identity" (estilo de vida). Default "auto".')
1478
1511
  });
1479
1512
  function registerCreativeTools(server, api, { transport = "http" } = {}) {
1480
1513
  server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio.\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: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (m\xE1x 15), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nESTILO: v\xEDa MCP todos los videos se generan en estilo "Realista" (render \xFAnico de 15s con audio nativo). El estilo "Multiescena" (30s, multi-toma) NO est\xE1 disponible por MCP por su costo \u2014 si el usuario lo pide, dile que lo genere desde la web.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes.', VideoInput.shape, async (input) => {
@@ -1513,7 +1546,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
1513
1546
  throw translateError(e);
1514
1547
  }
1515
1548
  });
1516
- server.tool("generate_landing", 'Genera una landing page lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods 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: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection.', LandingInput.shape, async (input) => {
1549
+ server.tool("generate_landing", 'Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods 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: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection, landingStrategy.', LandingInput.shape, async (input) => {
1517
1550
  try {
1518
1551
  return await submitCreative(api, {
1519
1552
  path: "/v1/creatives/landing",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "1.5.0",
3
+ "version": "1.7.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.",
@@ -60,7 +60,7 @@
60
60
  "msw": "^2.4.0",
61
61
  "tsup": "^8.3.0",
62
62
  "tsx": "^4.16.0",
63
- "typescript": "^5.4.5",
63
+ "typescript": "^7.0.2",
64
64
  "vitest": "^2.1.9",
65
65
  "@packages/types": "0.1.0"
66
66
  },