@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 +40 -7
- package/dist/stdio.js +40 -7
- package/package.json +2 -2
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.
|
|
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(/[
|
|
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.
|
|
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(/[
|
|
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.
|
|
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": "^
|
|
63
|
+
"typescript": "^7.0.2",
|
|
64
64
|
"vitest": "^2.1.9",
|
|
65
65
|
"@packages/types": "0.1.0"
|
|
66
66
|
},
|