@productmaker/mcp 0.16.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/http.js +24 -25
- package/dist/stdio.js +16 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,11 +41,22 @@ Verify with `claude mcp list`. Use `--scope user` for a global install.
|
|
|
41
41
|
No install needed — connect to the hosted endpoint:
|
|
42
42
|
|
|
43
43
|
- **Server URL:** `https://mcp.productmaker.app/mcp`
|
|
44
|
-
- **Header:** `Authorization: Bearer pm_live_...`
|
|
44
|
+
- **Header:** `Authorization: Bearer pm_live_...` *(or use the URL-only form below)*
|
|
45
45
|
|
|
46
46
|
In claude.ai: *Settings → Integrations → Add MCP server*.
|
|
47
47
|
In ChatGPT: *Settings → Connectors → Add custom connector*.
|
|
48
48
|
|
|
49
|
+
### Claude Desktop (Add custom connector — URL only)
|
|
50
|
+
|
|
51
|
+
Claude Desktop's *Settings → Connectors → Add custom connector* modal only
|
|
52
|
+
accepts a URL. Paste your key as a query parameter:
|
|
53
|
+
|
|
54
|
+
- **Name:** Product Maker
|
|
55
|
+
- **Remote MCP server URL:** `https://mcp.productmaker.app/mcp?apikey=pm_live_...`
|
|
56
|
+
|
|
57
|
+
That's it — no JSON file, no Node install. If you prefer to keep the key out
|
|
58
|
+
of the URL, use Claude Code (CLI) or the stdio install above instead.
|
|
59
|
+
|
|
49
60
|
### Get your API key
|
|
50
61
|
|
|
51
62
|
Sign up at [productmaker.app](https://productmaker.app) and create a key (prefix `pm_live_`) at [productmaker.app/account/api-keys](https://productmaker.app/account/api-keys). Full setup walkthrough: [productmaker.app/install](https://productmaker.app/install).
|
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 = "0.
|
|
116
|
+
var MCP_VERSION = "0.17.0";
|
|
117
117
|
|
|
118
118
|
// src/tools/tasks.ts
|
|
119
119
|
import { z as z2 } from "zod";
|
|
@@ -474,12 +474,10 @@ function resolveVideoPreset(preset) {
|
|
|
474
474
|
}
|
|
475
475
|
__name(resolveVideoPreset, "resolveVideoPreset");
|
|
476
476
|
var VideoTierEnum = z.enum([
|
|
477
|
-
"basic"
|
|
478
|
-
"standard"
|
|
477
|
+
"basic"
|
|
479
478
|
]);
|
|
480
|
-
function resolveVideoTier(
|
|
481
|
-
|
|
482
|
-
return tier === "basic" ? "single_call" : "standard";
|
|
479
|
+
function resolveVideoTier(_tier) {
|
|
480
|
+
return "single_call";
|
|
483
481
|
}
|
|
484
482
|
__name(resolveVideoTier, "resolveVideoTier");
|
|
485
483
|
var VideoResolutionEnum = z.enum([
|
|
@@ -1172,9 +1170,9 @@ var CreateProductTaskInput = z2.object({
|
|
|
1172
1170
|
price: z2.string().optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
|
|
1173
1171
|
offer: z2.string().optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
|
|
1174
1172
|
productContext: z2.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
|
|
1175
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Opciones: "auto", "ugc_dynamic" (UGC testimonial), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones,
|
|
1176
|
-
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video
|
|
1177
|
-
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render
|
|
1173
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). V\xEDa MCP todo video renderiza a 15s (single_call), as\xED que ning\xFAn preset requiere 30s aqu\xED. Opciones: "auto", "ugc_dynamic" (UGC testimonial), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones, comprimido a 15s v\xEDa MCP \u2014 para el ritmo completo de 30s el usuario debe usar la web), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n, comprimido a 15s v\xEDa MCP \u2014 para el ritmo completo de 30s el usuario debe usar la web), "ugc_demo_review" (demo en uso), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla), "product_showcase" (solo producto, sin actor), "wearable_showcase" (ropa/accesorios puestos). Si el usuario pide algo fuera de esta lista (ej. "estilo anime"), p\xEDdele una opci\xF3n v\xE1lida.'),
|
|
1174
|
+
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. \xDAnico valor disponible v\xEDa MCP: "basic" = "Realista": render de 15s con audio nativo, el m\xE1s natural. El estilo "Multiescena" (multi-toma, 30s) NO est\xE1 disponible v\xEDa MCP por su costo (se factura por escena) \u2014 si el usuario lo pide, dile que lo genere desde la web. Omitir el campo equivale a "basic".'),
|
|
1175
|
+
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez (~el doble de precio). Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
|
|
1178
1176
|
multiAngleVideos: z2.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
|
|
1179
1177
|
angleNarratives: NarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional, override avanzado). Usa los mismos identificadores de narrativa que videoPreset (ej. "animated_problem_pixar", "review", "ugc_skeptic_confession"). angleNarratives[i] fuerza la narrativa del \xE1ngulo en la posici\xF3n i; si la lista es m\xE1s corta que la cantidad de \xE1ngulos, las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para que haya varios \xE1ngulos. Si se omite, todas las narrativas se auto-seleccionan seg\xFAn el producto.'),
|
|
1180
1178
|
outputs: z2.array(z2.enum([
|
|
@@ -1259,7 +1257,7 @@ function formatOutputList(outputs) {
|
|
|
1259
1257
|
}
|
|
1260
1258
|
__name(formatOutputList, "formatOutputList");
|
|
1261
1259
|
function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
|
|
1262
|
-
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) EN UN MISMO TASK. 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\nANTES de invocar, DILE CLARAMENTE al usuario qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task. Ejemplo: "Voy a generar landing + video en un mismo task; esto consume cr\xE9ditos de ambos outputs". Si el usuario s\xF3lo quiere un output, usa generate_landing / generate_video_creative / generate_image_creatives en vez de este tool.\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-
|
|
1260
|
+
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) EN UN MISMO TASK. 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\nANTES de invocar, DILE CLARAMENTE al usuario qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task. Ejemplo: "Voy a generar landing + video en un mismo task; esto consume cr\xE9ditos de ambos outputs". Si el usuario s\xF3lo quiere un output, usa generate_landing / generate_video_creative / generate_image_creatives en vez de este tool.\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- multiAngleVideos (booleano)\n- angleNarratives (override per-angle de la narrativa, avanzado)\n- outputs (subconjunto de {landing,video,image}); omitido = ["landing","video","image"] en un mismo task\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) => {
|
|
1263
1261
|
try {
|
|
1264
1262
|
const imgs = await resolveImages(input.images, {
|
|
1265
1263
|
transport
|
|
@@ -1281,11 +1279,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1281
1279
|
"options.videoStyle": resolved?.videoStyle,
|
|
1282
1280
|
"options.narrativeStyle": resolved?.narrativeStyle,
|
|
1283
1281
|
"options.videoTier": wireTier,
|
|
1284
|
-
|
|
1285
|
-
// other tier the backend derives it. Forward it ONLY for single_call so
|
|
1286
|
-
// the MCP never presents a "user choice where there is none" (the web
|
|
1287
|
-
// has no resolution knob for standard) — for non-single_call it's dropped.
|
|
1288
|
-
"options.videoResolution": wireTier === "single_call" ? input.videoResolution : void 0,
|
|
1282
|
+
"options.videoResolution": input.videoResolution,
|
|
1289
1283
|
"options.multiAngleVideos": input.multiAngleVideos,
|
|
1290
1284
|
"options.angleNarratives": input.angleNarratives,
|
|
1291
1285
|
"options.selectedOutputs": input.outputs,
|
|
@@ -1450,10 +1444,10 @@ var VideoInput = z3.object({
|
|
|
1450
1444
|
"16:9",
|
|
1451
1445
|
"Auto"
|
|
1452
1446
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
1453
|
-
durationSeconds: z3.number().int().min(5).max(
|
|
1454
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Opciones: "auto", "ugc_dynamic" (UGC cl\xE1sico testimonial multi-hook), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones numeradas,
|
|
1455
|
-
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video
|
|
1456
|
-
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render
|
|
1447
|
+
durationSeconds: z3.number().int().min(5).max(15).optional().describe("Opcional. Duraci\xF3n del video en segundos (5-15). Default 15s. Para hooks cortos usa 8. Duraciones mayores a 15s (video Multiescena de 30s) NO est\xE1n disponibles v\xEDa MCP \u2014 ind\xEDcale al usuario que las genere desde la web."),
|
|
1448
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). V\xEDa MCP todo video renderiza a m\xE1x. 15s (ver durationSeconds), as\xED que ning\xFAn preset requiere 30s aqu\xED. Opciones: "auto", "ugc_dynamic" (UGC cl\xE1sico testimonial multi-hook), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones numeradas, comprimido a 15s v\xEDa MCP \u2014 para el ritmo completo de 30s el usuario debe usar la web), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n, comprimido a 15s v\xEDa MCP \u2014 para el ritmo completo de 30s el usuario debe usar la web), "ugc_demo_review" (demo en uso a lo largo de d\xEDas), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D estilo Pixar), "pixar_sassy" (producto que habla con personalidad), "product_showcase" (sin actor, solo producto), "wearable_showcase" (ropa/accesorios puestos).'),
|
|
1449
|
+
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. \xDAnico valor disponible v\xEDa MCP: "basic" = "Realista": render de 15s con audio nativo, el m\xE1s natural. El estilo "Multiescena" (multi-toma, 30s) NO est\xE1 disponible v\xEDa MCP por su costo (se factura por escena) \u2014 si el usuario lo pide, dile que lo genere desde la web. Omitir el campo equivale a "basic".'),
|
|
1450
|
+
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "720p" = m\xE1xima nitidez (RECOMENDADO, default si se omite); "480p" = render m\xE1s econ\xF3mico. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 720p).'),
|
|
1457
1451
|
features: z3.array(z3.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
1458
1452
|
price: CreativeRefs.price,
|
|
1459
1453
|
offer: CreativeRefs.offer,
|
|
@@ -1506,11 +1500,8 @@ var LandingInput = z3.object({
|
|
|
1506
1500
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1507
1501
|
});
|
|
1508
1502
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
1509
|
-
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, videoPreset (tipo/narrativa del video),
|
|
1503
|
+
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.\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) => {
|
|
1510
1504
|
try {
|
|
1511
|
-
if (input.videoTier === "basic" && (input.durationSeconds ?? 15) > 15) {
|
|
1512
|
-
throw new Error('videoTier="basic" s\xF3lo admite durationSeconds \u2264 15. Para 30s usa "standard".');
|
|
1513
|
-
}
|
|
1514
1505
|
const resolved = resolveVideoPreset(input.videoPreset);
|
|
1515
1506
|
const wireTier = resolveVideoTier(input.videoTier);
|
|
1516
1507
|
const { videoTier: _publicTier, videoResolution: _vr, ...rest } = input;
|
|
@@ -1521,8 +1512,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
1521
1512
|
wirePayload.videoStyle = resolved.videoStyle;
|
|
1522
1513
|
wirePayload.narrativeStyle = resolved.narrativeStyle;
|
|
1523
1514
|
}
|
|
1524
|
-
|
|
1525
|
-
if (
|
|
1515
|
+
wirePayload.videoTier = wireTier;
|
|
1516
|
+
if (input.videoResolution) wirePayload.videoResolution = input.videoResolution;
|
|
1526
1517
|
return await submitCreative(api, {
|
|
1527
1518
|
path: "/v1/creatives/videos",
|
|
1528
1519
|
estimatedSeconds: 300,
|
|
@@ -1978,6 +1969,12 @@ function firstHeader(value) {
|
|
|
1978
1969
|
return value;
|
|
1979
1970
|
}
|
|
1980
1971
|
__name(firstHeader, "firstHeader");
|
|
1972
|
+
function firstQuery(value) {
|
|
1973
|
+
if (typeof value === "string") return value;
|
|
1974
|
+
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
1975
|
+
return void 0;
|
|
1976
|
+
}
|
|
1977
|
+
__name(firstQuery, "firstQuery");
|
|
1981
1978
|
function extractApiKey(req) {
|
|
1982
1979
|
const auth = firstHeader(req.headers["authorization"]);
|
|
1983
1980
|
if (auth && auth.startsWith(`Bearer ${PM_API_KEY_PREFIX}`)) {
|
|
@@ -1985,6 +1982,8 @@ function extractApiKey(req) {
|
|
|
1985
1982
|
}
|
|
1986
1983
|
const x = firstHeader(req.headers["x-api-key"]);
|
|
1987
1984
|
if (x && x.startsWith(PM_API_KEY_PREFIX)) return x.trim();
|
|
1985
|
+
const q = firstQuery(req.query["apikey"]);
|
|
1986
|
+
if (q && q.startsWith(PM_API_KEY_PREFIX)) return q.trim();
|
|
1988
1987
|
return null;
|
|
1989
1988
|
}
|
|
1990
1989
|
__name(extractApiKey, "extractApiKey");
|
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 = "0.
|
|
113
|
+
var MCP_VERSION = "0.17.0";
|
|
114
114
|
|
|
115
115
|
// src/tools/tasks.ts
|
|
116
116
|
import { z as z2 } from "zod";
|
|
@@ -471,12 +471,10 @@ function resolveVideoPreset(preset) {
|
|
|
471
471
|
}
|
|
472
472
|
__name(resolveVideoPreset, "resolveVideoPreset");
|
|
473
473
|
var VideoTierEnum = z.enum([
|
|
474
|
-
"basic"
|
|
475
|
-
"standard"
|
|
474
|
+
"basic"
|
|
476
475
|
]);
|
|
477
|
-
function resolveVideoTier(
|
|
478
|
-
|
|
479
|
-
return tier === "basic" ? "single_call" : "standard";
|
|
476
|
+
function resolveVideoTier(_tier) {
|
|
477
|
+
return "single_call";
|
|
480
478
|
}
|
|
481
479
|
__name(resolveVideoTier, "resolveVideoTier");
|
|
482
480
|
var VideoResolutionEnum = z.enum([
|
|
@@ -1128,9 +1126,9 @@ var CreateProductTaskInput = z2.object({
|
|
|
1128
1126
|
price: z2.string().optional().describe('Precio que se mostrar\xE1 en la landing y video. Formato libre \u2014 ej. "$89.900", "USD 29.99", "MXN 599". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si te dice "t\xFA elige", om\xEDtelo y el backend sugiere uno.'),
|
|
1129
1127
|
offer: z2.string().optional().describe('Oferta o promoci\xF3n a destacar. Ej. "2x1", "Env\xEDo gratis hoy", "50% OFF". Opcional pero MUY IMPACTANTE: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar (es v\xE1lido que diga "sin oferta"). Si te dice "t\xFA elige", om\xEDtelo.'),
|
|
1130
1128
|
productContext: z2.string().max(3e3).optional().describe("Opcional. Informacion adicional del producto que la IA debe usar como contexto prioritario: uso real, comprador, restricciones, diferenciadores o direccion creativa. No reemplaza la investigacion de competencia; si hay competencia, guia el primer angulo y los outputs no deben contradecirlo."),
|
|
1131
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Opciones: "auto", "ugc_dynamic" (UGC testimonial), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones,
|
|
1132
|
-
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video
|
|
1133
|
-
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render
|
|
1129
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). V\xEDa MCP todo video renderiza a 15s (single_call), as\xED que ning\xFAn preset requiere 30s aqu\xED. Opciones: "auto", "ugc_dynamic" (UGC testimonial), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones, comprimido a 15s v\xEDa MCP \u2014 para el ritmo completo de 30s el usuario debe usar la web), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n, comprimido a 15s v\xEDa MCP \u2014 para el ritmo completo de 30s el usuario debe usar la web), "ugc_demo_review" (demo en uso), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D), "pixar_sassy" (producto que habla), "product_showcase" (solo producto, sin actor), "wearable_showcase" (ropa/accesorios puestos). Si el usuario pide algo fuera de esta lista (ej. "estilo anime"), p\xEDdele una opci\xF3n v\xE1lida.'),
|
|
1130
|
+
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. \xDAnico valor disponible v\xEDa MCP: "basic" = "Realista": render de 15s con audio nativo, el m\xE1s natural. El estilo "Multiescena" (multi-toma, 30s) NO est\xE1 disponible v\xEDa MCP por su costo (se factura por escena) \u2014 si el usuario lo pide, dile que lo genere desde la web. Omitir el campo equivale a "basic".'),
|
|
1131
|
+
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "480p" = excelente calidad y m\xE1s econ\xF3mico (RECOMENDADO, default si se omite); "720p" = m\xE1xima nitidez (~el doble de precio). Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 480p).'),
|
|
1134
1132
|
multiAngleVideos: z2.boolean().optional().describe("Opcional. Si true, genera m\xFAltiples \xE1ngulos de venta en paralelo (3-5 videos). Si false/omitido, genera 1 video del \xE1ngulo principal. Default false."),
|
|
1135
1133
|
angleNarratives: NarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional, override avanzado). Usa los mismos identificadores de narrativa que videoPreset (ej. "animated_problem_pixar", "review", "ugc_skeptic_confession"). angleNarratives[i] fuerza la narrativa del \xE1ngulo en la posici\xF3n i; si la lista es m\xE1s corta que la cantidad de \xE1ngulos, las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para que haya varios \xE1ngulos. Si se omite, todas las narrativas se auto-seleccionan seg\xFAn el producto.'),
|
|
1136
1134
|
outputs: z2.array(z2.enum([
|
|
@@ -1215,7 +1213,7 @@ function formatOutputList(outputs) {
|
|
|
1215
1213
|
}
|
|
1216
1214
|
__name(formatOutputList, "formatOutputList");
|
|
1217
1215
|
function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http" } = {}) {
|
|
1218
|
-
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) EN UN MISMO TASK. 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\nANTES de invocar, DILE CLARAMENTE al usuario qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task. Ejemplo: "Voy a generar landing + video en un mismo task; esto consume cr\xE9ditos de ambos outputs". Si el usuario s\xF3lo quiere un output, usa generate_landing / generate_video_creative / generate_image_creatives en vez de este tool.\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-
|
|
1216
|
+
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) EN UN MISMO TASK. 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\nANTES de invocar, DILE CLARAMENTE al usuario qu\xE9 outputs vas a generar y que `create_product_task` los genera juntos en un mismo task. Ejemplo: "Voy a generar landing + video en un mismo task; esto consume cr\xE9ditos de ambos outputs". Si el usuario s\xF3lo quiere un output, usa generate_landing / generate_video_creative / generate_image_creatives en vez de este tool.\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- multiAngleVideos (booleano)\n- angleNarratives (override per-angle de la narrativa, avanzado)\n- outputs (subconjunto de {landing,video,image}); omitido = ["landing","video","image"] en un mismo task\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) => {
|
|
1219
1217
|
try {
|
|
1220
1218
|
const imgs = await resolveImages(input.images, {
|
|
1221
1219
|
transport
|
|
@@ -1237,11 +1235,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1237
1235
|
"options.videoStyle": resolved?.videoStyle,
|
|
1238
1236
|
"options.narrativeStyle": resolved?.narrativeStyle,
|
|
1239
1237
|
"options.videoTier": wireTier,
|
|
1240
|
-
|
|
1241
|
-
// other tier the backend derives it. Forward it ONLY for single_call so
|
|
1242
|
-
// the MCP never presents a "user choice where there is none" (the web
|
|
1243
|
-
// has no resolution knob for standard) — for non-single_call it's dropped.
|
|
1244
|
-
"options.videoResolution": wireTier === "single_call" ? input.videoResolution : void 0,
|
|
1238
|
+
"options.videoResolution": input.videoResolution,
|
|
1245
1239
|
"options.multiAngleVideos": input.multiAngleVideos,
|
|
1246
1240
|
"options.angleNarratives": input.angleNarratives,
|
|
1247
1241
|
"options.selectedOutputs": input.outputs,
|
|
@@ -1406,10 +1400,10 @@ var VideoInput = z3.object({
|
|
|
1406
1400
|
"16:9",
|
|
1407
1401
|
"Auto"
|
|
1408
1402
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
1409
|
-
durationSeconds: z3.number().int().min(5).max(
|
|
1410
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). Opciones: "auto", "ugc_dynamic" (UGC cl\xE1sico testimonial multi-hook), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones numeradas,
|
|
1411
|
-
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video
|
|
1412
|
-
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render
|
|
1403
|
+
durationSeconds: z3.number().int().min(5).max(15).optional().describe("Opcional. Duraci\xF3n del video en segundos (5-15). Default 15s. Para hooks cortos usa 8. Duraciones mayores a 15s (video Multiescena de 30s) NO est\xE1n disponibles v\xEDa MCP \u2014 ind\xEDcale al usuario que las genere desde la web."),
|
|
1404
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo/narrativa del video \u2014 \xFAnico campo de "estilo" que el usuario controla (en la web es el \xFAnico dropdown). Default "auto" (la IA escoge tras analizar el producto). V\xEDa MCP todo video renderiza a m\xE1x. 15s (ver durationSeconds), as\xED que ning\xFAn preset requiere 30s aqu\xED. Opciones: "auto", "ugc_dynamic" (UGC cl\xE1sico testimonial multi-hook), "ugc_unboxing_chaos" (unboxing agresivo), "ugc_curiosity_reveal" (hook con producto oculto), "ugc_listicle_3" (3 razones numeradas, comprimido a 15s v\xEDa MCP \u2014 para el ritmo completo de 30s el usuario debe usar la web), "ugc_skeptic_confession" (esc\xE9ptico\u2192conversi\xF3n, comprimido a 15s v\xEDa MCP \u2014 para el ritmo completo de 30s el usuario debe usar la web), "ugc_demo_review" (demo en uso a lo largo de d\xEDas), "problem_solution" (problema\u2192producto\u2192CTA), "pixar_problem" (problema animado 3D estilo Pixar), "pixar_sassy" (producto que habla con personalidad), "product_showcase" (sin actor, solo producto), "wearable_showcase" (ropa/accesorios puestos).'),
|
|
1405
|
+
videoTier: VideoTierEnum.optional().describe('Opcional. Estilo del video. \xDAnico valor disponible v\xEDa MCP: "basic" = "Realista": render de 15s con audio nativo, el m\xE1s natural. El estilo "Multiescena" (multi-toma, 30s) NO est\xE1 disponible v\xEDa MCP por su costo (se factura por escena) \u2014 si el usuario lo pide, dile que lo genere desde la web. Omitir el campo equivale a "basic".'),
|
|
1406
|
+
videoResolution: VideoResolutionEnum.optional().describe('Opcional. Resoluci\xF3n del render. En la web se pregunta "\xBFResoluci\xF3n del video?". "720p" = m\xE1xima nitidez (RECOMENDADO, default si se omite); "480p" = render m\xE1s econ\xF3mico. Si el usuario no menciona resoluci\xF3n, OMITE este campo (el backend usa 720p).'),
|
|
1413
1407
|
features: z3.array(z3.string()).optional().describe('Opcional. Lista de features/beneficios clave a mencionar. Ej. ["resistente al agua", "bater\xEDa 24h", "carga inal\xE1mbrica"].'),
|
|
1414
1408
|
price: CreativeRefs.price,
|
|
1415
1409
|
offer: CreativeRefs.offer,
|
|
@@ -1462,11 +1456,8 @@ var LandingInput = z3.object({
|
|
|
1462
1456
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1463
1457
|
});
|
|
1464
1458
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
1465
|
-
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, videoPreset (tipo/narrativa del video),
|
|
1459
|
+
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.\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) => {
|
|
1466
1460
|
try {
|
|
1467
|
-
if (input.videoTier === "basic" && (input.durationSeconds ?? 15) > 15) {
|
|
1468
|
-
throw new Error('videoTier="basic" s\xF3lo admite durationSeconds \u2264 15. Para 30s usa "standard".');
|
|
1469
|
-
}
|
|
1470
1461
|
const resolved = resolveVideoPreset(input.videoPreset);
|
|
1471
1462
|
const wireTier = resolveVideoTier(input.videoTier);
|
|
1472
1463
|
const { videoTier: _publicTier, videoResolution: _vr, ...rest } = input;
|
|
@@ -1477,8 +1468,8 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
1477
1468
|
wirePayload.videoStyle = resolved.videoStyle;
|
|
1478
1469
|
wirePayload.narrativeStyle = resolved.narrativeStyle;
|
|
1479
1470
|
}
|
|
1480
|
-
|
|
1481
|
-
if (
|
|
1471
|
+
wirePayload.videoTier = wireTier;
|
|
1472
|
+
if (input.videoResolution) wirePayload.videoResolution = input.videoResolution;
|
|
1482
1473
|
return await submitCreative(api, {
|
|
1483
1474
|
path: "/v1/creatives/videos",
|
|
1484
1475
|
estimatedSeconds: 300,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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.",
|