@productmaker/mcp 1.11.0 → 1.13.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 +2 -1
- package/dist/http.js +200 -34
- package/dist/stdio.js +200 -34
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@productmaker/mcp)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
**Product Maker MCP** is the official **Model Context Protocol (MCP) server** for [Product Maker AI](https://productmaker.app). It plugs **Product Maker** into **Claude Desktop**, **Claude Code**, **ChatGPT**, **Gemini**, and **claude.ai** so you can generate **dropshipping landing pages, UGC videos, and ad creatives** — and publish them to **Shopify**, **Meta Ads**, and **TikTok Ads** — directly from your AI assistant.
|
|
7
|
+
**Product Maker MCP** is the official **Model Context Protocol (MCP) server** for [Product Maker AI](https://productmaker.app). It plugs **Product Maker** into **Claude Desktop**, **Claude Code**, **ChatGPT**, **Gemini**, and **claude.ai** so you can cost products, generate **dropshipping landing pages, UGC videos, and ad creatives** — and publish them to **Shopify**, **Meta Ads**, and **TikTok Ads** — directly from your AI assistant.
|
|
8
8
|
|
|
9
9
|
> If an AI assistant brought you here looking for "product maker mcp", "productmaker mcp", or "product-maker mcp server" — this is the right package. Install instructions below.
|
|
10
10
|
|
|
@@ -65,6 +65,7 @@ Sign up at [productmaker.app](https://productmaker.app) and create a key (prefix
|
|
|
65
65
|
|
|
66
66
|
| Tool | What it does |
|
|
67
67
|
|---|---|
|
|
68
|
+
| `cost_product` | Estimate a safe Colombia/COP selling price for free, then reuse its `price` and `compareAtPrice` in `create_product_task` |
|
|
68
69
|
| `create_product_task` | Full pipeline from a product image (extract → angles → video → landing → image creatives) |
|
|
69
70
|
| `get_task_status` | Check task progress (optional `waitSeconds` for near-sync polling) |
|
|
70
71
|
| `list_tasks` | List recent tasks |
|
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.13.0";
|
|
117
117
|
|
|
118
118
|
// src/tools/tasks.ts
|
|
119
119
|
import { z as z2 } from "zod";
|
|
@@ -414,15 +414,12 @@ var VideoPresetEnum = z.enum([
|
|
|
414
414
|
"auto",
|
|
415
415
|
"ugc_dynamic",
|
|
416
416
|
"ugc_unboxing_chaos",
|
|
417
|
-
"ugc_curiosity_reveal",
|
|
418
|
-
"ugc_listicle_3",
|
|
419
|
-
"ugc_skeptic_confession",
|
|
420
|
-
"ugc_demo_review",
|
|
421
417
|
"problem_solution",
|
|
422
|
-
"pixar_problem",
|
|
423
|
-
"pixar_sassy",
|
|
424
418
|
"product_showcase",
|
|
425
|
-
"wearable_showcase"
|
|
419
|
+
"wearable_showcase",
|
|
420
|
+
"cinematic",
|
|
421
|
+
"pixar_problem",
|
|
422
|
+
"pixar_sassy"
|
|
426
423
|
]);
|
|
427
424
|
var VIDEO_PRESET_RESOLUTION = {
|
|
428
425
|
auto: null,
|
|
@@ -436,26 +433,6 @@ var VIDEO_PRESET_RESOLUTION = {
|
|
|
436
433
|
narrativeStyle: "ugc_unboxing_chaos",
|
|
437
434
|
videoModel: "veo"
|
|
438
435
|
},
|
|
439
|
-
ugc_curiosity_reveal: {
|
|
440
|
-
videoStyle: "ai_dynamic",
|
|
441
|
-
narrativeStyle: "ugc_curiosity_reveal",
|
|
442
|
-
videoModel: "veo"
|
|
443
|
-
},
|
|
444
|
-
ugc_listicle_3: {
|
|
445
|
-
videoStyle: "ai_dynamic",
|
|
446
|
-
narrativeStyle: "ugc_listicle_3",
|
|
447
|
-
videoModel: "kling"
|
|
448
|
-
},
|
|
449
|
-
ugc_skeptic_confession: {
|
|
450
|
-
videoStyle: "ai_dynamic",
|
|
451
|
-
narrativeStyle: "ugc_skeptic_confession",
|
|
452
|
-
videoModel: "veo"
|
|
453
|
-
},
|
|
454
|
-
ugc_demo_review: {
|
|
455
|
-
videoStyle: "ai_dynamic",
|
|
456
|
-
narrativeStyle: "ugc_demo_review",
|
|
457
|
-
videoModel: "kling"
|
|
458
|
-
},
|
|
459
436
|
problem_solution: {
|
|
460
437
|
videoStyle: "ai_dynamic",
|
|
461
438
|
narrativeStyle: "problem_product_cta",
|
|
@@ -480,6 +457,11 @@ var VIDEO_PRESET_RESOLUTION = {
|
|
|
480
457
|
videoStyle: "ugc",
|
|
481
458
|
narrativeStyle: "wearable_showcase",
|
|
482
459
|
videoModel: "veo"
|
|
460
|
+
},
|
|
461
|
+
cinematic: {
|
|
462
|
+
videoStyle: "product_only",
|
|
463
|
+
narrativeStyle: "cinematic",
|
|
464
|
+
videoModel: "grok"
|
|
483
465
|
}
|
|
484
466
|
};
|
|
485
467
|
function resolveVideoPreset(preset) {
|
|
@@ -508,6 +490,7 @@ var NarrativeStyleEnum = z.enum([
|
|
|
508
490
|
"review",
|
|
509
491
|
"problem_product_cta",
|
|
510
492
|
"product_showcase",
|
|
493
|
+
"cinematic",
|
|
511
494
|
"metaphor_product_cta",
|
|
512
495
|
"wearable_showcase",
|
|
513
496
|
"animated_problem_pixar",
|
|
@@ -518,6 +501,16 @@ var NarrativeStyleEnum = z.enum([
|
|
|
518
501
|
"ugc_skeptic_confession",
|
|
519
502
|
"ugc_demo_review"
|
|
520
503
|
]);
|
|
504
|
+
var PublicNarrativeStyleEnum = z.enum([
|
|
505
|
+
"review",
|
|
506
|
+
"ugc_unboxing_chaos",
|
|
507
|
+
"problem_product_cta",
|
|
508
|
+
"product_showcase",
|
|
509
|
+
"wearable_showcase",
|
|
510
|
+
"cinematic",
|
|
511
|
+
"animated_problem_pixar",
|
|
512
|
+
"sassy_product_pixar"
|
|
513
|
+
]);
|
|
521
514
|
var VideoModelEnum = z.enum([
|
|
522
515
|
"veo",
|
|
523
516
|
"wan",
|
|
@@ -1199,11 +1192,11 @@ var CreateProductTaskInput = z2.object({
|
|
|
1199
1192
|
paymentMethods: z2.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
|
|
1200
1193
|
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."),
|
|
1201
1194
|
productLabel: z2.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
|
|
1202
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo
|
|
1195
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo de video. Default "auto" (la IA escoge tras analizar el producto). Opciones actuales: "auto", "ugc_dynamic" (UGC Review), "ugc_unboxing_chaos" (Unboxing), "problem_solution" (problema\u2192soluci\xF3n), "product_showcase" (Producto en acci\xF3n: una prueba funcional), "wearable_showcase" (modelaje con la prenda/accesorio puesto), "cinematic" (historia visual premium), "pixar_problem" (problema animado Pixar), "pixar_sassy" (producto que habla Pixar). V\xEDa MCP todo video renderiza a 15s single_call. Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
|
|
1203
1196
|
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".'),
|
|
1204
1197
|
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).'),
|
|
1205
1198
|
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."),
|
|
1206
|
-
angleNarratives:
|
|
1199
|
+
angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_unboxing_chaos", "problem_product_cta", "product_showcase", "wearable_showcase", "cinematic", "animated_problem_pixar", "sassy_product_pixar". angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para varios \xE1ngulos.'),
|
|
1207
1200
|
outputs: z2.array(z2.enum([
|
|
1208
1201
|
"landing",
|
|
1209
1202
|
"video",
|
|
@@ -1212,7 +1205,7 @@ var CreateProductTaskInput = z2.object({
|
|
|
1212
1205
|
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."),
|
|
1213
1206
|
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."),
|
|
1214
1207
|
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.'),
|
|
1215
|
-
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof", "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
|
|
1208
|
+
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
|
|
1216
1209
|
landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
|
|
1217
1210
|
}).strict();
|
|
1218
1211
|
var GetTaskStatusInput = z2.object({
|
|
@@ -1516,7 +1509,7 @@ var VideoInput = z3.object({
|
|
|
1516
1509
|
"Auto"
|
|
1517
1510
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
1518
1511
|
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."),
|
|
1519
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo
|
|
1512
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo de video. Default "auto". Opciones actuales: "auto", "ugc_dynamic" (UGC Review), "ugc_unboxing_chaos" (Unboxing), "problem_solution" (problema\u2192soluci\xF3n), "product_showcase" (Producto en acci\xF3n: una prueba funcional), "wearable_showcase" (modelaje con la prenda/accesorio puesto), "cinematic" (historia visual premium), "pixar_problem" (problema animado Pixar), "pixar_sassy" (producto que habla Pixar). V\xEDa MCP todo video renderiza a m\xE1x. 15s.'),
|
|
1520
1513
|
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".'),
|
|
1521
1514
|
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).'),
|
|
1522
1515
|
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"].'),
|
|
@@ -1578,7 +1571,7 @@ var LandingInput = z3.object({
|
|
|
1578
1571
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
1579
1572
|
brandId: CreativeRefs.brandId,
|
|
1580
1573
|
creativeDirection: CreativeRefs.creativeDirection,
|
|
1581
|
-
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution" (problema\u2192soluci\xF3n), "social_proof" (
|
|
1574
|
+
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution" (problema\u2192soluci\xF3n), "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity" (beneficios claros), "offer_value" (promoci\xF3n/valor) o "lifestyle_identity" (estilo de vida). Default "classic".'),
|
|
1582
1575
|
landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
|
|
1583
1576
|
});
|
|
1584
1577
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
@@ -1653,7 +1646,7 @@ var ApproveVideoScriptInput = z4.object({
|
|
|
1653
1646
|
var RegenerateVideoScriptInput = z4.object({
|
|
1654
1647
|
taskId: z4.string().min(1).describe("ID de la tarea."),
|
|
1655
1648
|
angleKey: z4.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
|
|
1656
|
-
narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion
|
|
1649
|
+
narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion. Incluye valores hist\xF3ricos porque esta herramienta tambi\xE9n regenera tareas antiguas. REQUERIDO \u2014 el backend NO acepta "mantener la actual"; siempre tienes que elegir una. Si el usuario quiere re-rolear con la misma narrativa, pasa la que ya tiene el \xE1ngulo (la ves en get_video_script o en get_task_status).')
|
|
1657
1650
|
}).strict();
|
|
1658
1651
|
function registerScriptReviewTools(server, api) {
|
|
1659
1652
|
server.tool("get_video_script", 'Lee el guion del video pendiente de revisi\xF3n. S\xF3lo funciona cuando la tarea fue creada con `pauseForVideoScriptReview: true` (via generate_video_creative). Devuelve { status, angleKey, perClipSeconds?, scenes: [{ index, dialogue, maxWords, minWords?, beatId? }], error? }. El campo `error` (cuando viene con status="ready") es el motivo legible por el que un intento previo de aprobaci\xF3n fue rechazado (p.ej. el guion no cab\xEDa en el tiempo y el \xE1ngulo volvi\xF3 a "ready"); mu\xE9straselo al usuario.\n\nCU\xC1NDO LLAMAR: no existe un status top-level "awaiting_script_review" en get_task_status. Si la tarea fue creada con pauseForVideoScriptReview=true, polea get_task_status hasta que `status === "processing"` y a partir de ah\xED llama este tool peri\xF3dicamente \u2014 la respuesta de get_video_script trae su propio campo `status` que es la se\xF1al real:\n- "ready" \u2192 el guion est\xE1 listo; mu\xE9strale al usuario cada `scenes[].dialogue` por `index` con su tope `maxWords`, recoge ediciones, llama approve_video_script.\n- "not_ready" \u2192 el guion a\xFAn se est\xE1 generando; espera 5\u201310s y reintenta este tool.\n- "approved" \u2192 ya fue aprobado; en multi-angle puede ser que ese \xE1ngulo ya est\xE9 hecho \u2014 vuelve a llamar con otro angleKey.\n- "skipped" \u2192 el usuario opt\xF3 por no revisar en una pausa previa.\n- "error" \u2192 el backend no pudo generar el guion; usa `regenerate_video_script` para re-intentar (es la ruta de recuperaci\xF3n v\xE1lida \u2014 el guion no se aprueba en estado "error").\n\nMULTI-ANGLE: si la tarea tiene varios \xE1ngulos, omitir angleKey siempre devuelve "angle_0" literal \u2014 itera expl\xEDcitamente por "angle_0", "angle_1", ... hasta encontrar uno con status="ready".', GetVideoScriptInput.shape, async (input) => {
|
|
@@ -2024,6 +2017,178 @@ function renderTikTokStatusMarkdown(r) {
|
|
|
2024
2017
|
}
|
|
2025
2018
|
__name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
|
|
2026
2019
|
|
|
2020
|
+
// ../../packages/product-economics/src/product-costing.ts
|
|
2021
|
+
import { z as z8 } from "zod";
|
|
2022
|
+
var PRODUCT_PROFIT_GUIDE_COP = 1e4;
|
|
2023
|
+
var ProductCostingInputSchema = z8.object({
|
|
2024
|
+
supplierCostCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
|
|
2025
|
+
shippingCostCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
|
|
2026
|
+
returnRate: z8.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
2027
|
+
cancellationRate: z8.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
2028
|
+
adsManagerCpaCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
|
|
2029
|
+
desiredMargin: z8.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
|
|
2030
|
+
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
2031
|
+
if (returnRate + cancellationRate >= 1) {
|
|
2032
|
+
context.addIssue({
|
|
2033
|
+
code: z8.ZodIssueCode.custom,
|
|
2034
|
+
message: "delivery_rate_positive",
|
|
2035
|
+
path: [
|
|
2036
|
+
"cancellationRate"
|
|
2037
|
+
]
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
var PRODUCT_COSTING_DEFAULTS = {
|
|
2042
|
+
supplierCostCop: 19990,
|
|
2043
|
+
shippingCostCop: 19e3,
|
|
2044
|
+
returnRate: 0.25,
|
|
2045
|
+
cancellationRate: 0.15,
|
|
2046
|
+
adsManagerCpaCop: 15e3,
|
|
2047
|
+
desiredMargin: 0.1
|
|
2048
|
+
};
|
|
2049
|
+
function assertPositiveFinite(value) {
|
|
2050
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
2051
|
+
throw new RangeError("Commercial pricing requires a positive finite value.");
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
__name(assertPositiveFinite, "assertPositiveFinite");
|
|
2055
|
+
function roundToCommercial9990(value) {
|
|
2056
|
+
assertPositiveFinite(value);
|
|
2057
|
+
const tier = Math.max(Math.floor((value + 10) / 1e4 + 0.5), 1);
|
|
2058
|
+
return tier * 1e4 - 10;
|
|
2059
|
+
}
|
|
2060
|
+
__name(roundToCommercial9990, "roundToCommercial9990");
|
|
2061
|
+
function ceilToCommercial9990(value) {
|
|
2062
|
+
assertPositiveFinite(value);
|
|
2063
|
+
const tier = Math.max(Math.ceil((value + 10) / 1e4 - 1e-10), 1);
|
|
2064
|
+
return tier * 1e4 - 10;
|
|
2065
|
+
}
|
|
2066
|
+
__name(ceilToCommercial9990, "ceilToCommercial9990");
|
|
2067
|
+
function calculateProductCosting(rawInput) {
|
|
2068
|
+
const input = ProductCostingInputSchema.parse(rawInput);
|
|
2069
|
+
const deliveryRate = 1 - input.returnRate - input.cancellationRate;
|
|
2070
|
+
const adjustedShippingCostCop = input.shippingCostCop / (1 - input.returnRate);
|
|
2071
|
+
const adjustedCpaCop = input.adsManagerCpaCop / deliveryRate;
|
|
2072
|
+
const totalCostCop = input.supplierCostCop + adjustedShippingCostCop + adjustedCpaCop;
|
|
2073
|
+
const suggestedPriceCop = totalCostCop / (1 - input.desiredMargin);
|
|
2074
|
+
const baseCommercialPriceCop = roundToCommercial9990(suggestedPriceCop);
|
|
2075
|
+
const minimumSafePriceCop = Math.max(suggestedPriceCop, totalCostCop + PRODUCT_PROFIT_GUIDE_COP);
|
|
2076
|
+
const commercialPriceCop = ceilToCommercial9990(minimumSafePriceCop);
|
|
2077
|
+
const comparisonPriceCop = commercialPriceCop * 2;
|
|
2078
|
+
const estimatedProfitCop = commercialPriceCop - totalCostCop;
|
|
2079
|
+
const actualMargin = estimatedProfitCop / commercialPriceCop;
|
|
2080
|
+
const breakEvenAdsManagerCpaCop = Math.max(0, (commercialPriceCop - input.supplierCostCop - adjustedShippingCostCop) * deliveryRate);
|
|
2081
|
+
return {
|
|
2082
|
+
deliveryRate,
|
|
2083
|
+
adjustedShippingCostCop,
|
|
2084
|
+
adjustedCpaCop,
|
|
2085
|
+
totalCostCop,
|
|
2086
|
+
suggestedPriceCop,
|
|
2087
|
+
baseCommercialPriceCop,
|
|
2088
|
+
minimumSafePriceCop,
|
|
2089
|
+
commercialPriceCop,
|
|
2090
|
+
comparisonPriceCop,
|
|
2091
|
+
estimatedProfitCop,
|
|
2092
|
+
actualMargin,
|
|
2093
|
+
breakEvenAdsManagerCpaCop,
|
|
2094
|
+
wasPriceAutoAdjusted: commercialPriceCop > baseCommercialPriceCop,
|
|
2095
|
+
profitabilityStatus: "healthy"
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
__name(calculateProductCosting, "calculateProductCosting");
|
|
2099
|
+
|
|
2100
|
+
// src/tools/product-costing.ts
|
|
2101
|
+
import { z as z9 } from "zod";
|
|
2102
|
+
var optionalCopAmount = /* @__PURE__ */ __name((description) => z9.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalCopAmount");
|
|
2103
|
+
var optionalPercent = /* @__PURE__ */ __name((description) => z9.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
|
|
2104
|
+
var ProductCostingToolInput = z9.object({
|
|
2105
|
+
supplierCostCop: z9.number().finite().max(Number.MAX_SAFE_INTEGER).positive().describe("Costo real de una unidad con el proveedor, en pesos colombianos (COP). Este valor s\xED debe venir del usuario."),
|
|
2106
|
+
shippingCostCop: optionalCopAmount("Costo de un env\xEDo entregado en COP. Omite para usar la referencia editable de Colombia: 19000."),
|
|
2107
|
+
returnRatePercent: optionalPercent("Porcentaje de pedidos devueltos. Ejemplo: 25 significa 25%. Omite para usar la referencia editable de Colombia: 25."),
|
|
2108
|
+
cancellationRatePercent: optionalPercent("Porcentaje de pedidos cancelados antes de entrega. Ejemplo: 15 significa 15%. Omite para usar la referencia editable de Colombia: 15."),
|
|
2109
|
+
adsManagerCpaCop: optionalCopAmount("Costo por compra mostrado en Ads Manager, en COP. Omite para usar la referencia editable de Colombia: 15000."),
|
|
2110
|
+
desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar la referencia editable de Colombia: 10.")
|
|
2111
|
+
});
|
|
2112
|
+
function registerProductCostingTools(server) {
|
|
2113
|
+
server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en Colombia (COP). Es READ-ONLY: no crea tareas ni publica nada. Pide al usuario \xFAnicamente el costo real del proveedor; si no conoce env\xEDo, devoluciones, cancelaciones, CPA o margen, om\xEDtelos y se usar\xE1n valores gu\xEDa editables de Colombia. Devuelve `price` y `compareAtPrice` listos para `create_product_task`; despu\xE9s puedes generar la tarea y publicarla con `publish_to_shopify`, `publish_to_meta` o `publish_to_tiktok`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, async (input) => {
|
|
2114
|
+
const normalized = normalizeToolInput(input);
|
|
2115
|
+
const parsed = ProductCostingInputSchema.safeParse(normalized.input);
|
|
2116
|
+
if (!parsed.success) {
|
|
2117
|
+
return {
|
|
2118
|
+
isError: true,
|
|
2119
|
+
content: [
|
|
2120
|
+
{
|
|
2121
|
+
type: "text",
|
|
2122
|
+
text: "No puedo calcular con esos porcentajes: devoluciones + cancelaciones deben sumar menos de 100%. Revisa los valores e int\xE9ntalo de nuevo."
|
|
2123
|
+
}
|
|
2124
|
+
]
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
return renderProductCosting(parsed.data, calculateProductCosting(parsed.data), normalized.usedReferenceValues);
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
__name(registerProductCostingTools, "registerProductCostingTools");
|
|
2131
|
+
function normalizeToolInput(input) {
|
|
2132
|
+
const optionalValues = [
|
|
2133
|
+
input.shippingCostCop,
|
|
2134
|
+
input.returnRatePercent,
|
|
2135
|
+
input.cancellationRatePercent,
|
|
2136
|
+
input.adsManagerCpaCop,
|
|
2137
|
+
input.desiredMarginPercent
|
|
2138
|
+
];
|
|
2139
|
+
return {
|
|
2140
|
+
input: {
|
|
2141
|
+
supplierCostCop: input.supplierCostCop,
|
|
2142
|
+
shippingCostCop: input.shippingCostCop ?? PRODUCT_COSTING_DEFAULTS.shippingCostCop,
|
|
2143
|
+
returnRate: (input.returnRatePercent ?? PRODUCT_COSTING_DEFAULTS.returnRate * 100) / 100,
|
|
2144
|
+
cancellationRate: (input.cancellationRatePercent ?? PRODUCT_COSTING_DEFAULTS.cancellationRate * 100) / 100,
|
|
2145
|
+
adsManagerCpaCop: input.adsManagerCpaCop ?? PRODUCT_COSTING_DEFAULTS.adsManagerCpaCop,
|
|
2146
|
+
desiredMargin: (input.desiredMarginPercent ?? PRODUCT_COSTING_DEFAULTS.desiredMargin * 100) / 100
|
|
2147
|
+
},
|
|
2148
|
+
usedReferenceValues: optionalValues.some((value) => value === void 0)
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
__name(normalizeToolInput, "normalizeToolInput");
|
|
2152
|
+
function renderProductCosting(input, result, usedReferenceValues) {
|
|
2153
|
+
const price = formatCop(result.commercialPriceCop);
|
|
2154
|
+
const compareAtPrice = formatCop(result.comparisonPriceCop);
|
|
2155
|
+
const lines = [
|
|
2156
|
+
"\u2705 **Costeo del producto \xB7 Colombia**",
|
|
2157
|
+
"",
|
|
2158
|
+
`Precio recomendado: **${price}**`,
|
|
2159
|
+
`Precio comparado: ~~${compareAtPrice}~~`,
|
|
2160
|
+
`Ganancia estimada por venta entregada: **${formatCop(result.estimatedProfitCop)}**`,
|
|
2161
|
+
`Margen real estimado: **${formatPercent(result.actualMargin)}**`,
|
|
2162
|
+
`Costo total estimado: ${formatCop(result.totalCostCop)}`,
|
|
2163
|
+
`M\xE1ximo sugerido en Ads Manager por compra: **${formatCop(result.breakEvenAdsManagerCpaCop)}**`
|
|
2164
|
+
];
|
|
2165
|
+
if (result.wasPriceAutoAdjusted) {
|
|
2166
|
+
lines.push("", `ProductMaker subi\xF3 autom\xE1ticamente el precio desde ${formatCop(result.baseCommercialPriceCop)} para proteger el margen y una ganancia gu\xEDa m\xEDnima de $10.000 COP.`);
|
|
2167
|
+
}
|
|
2168
|
+
lines.push("", "### Valores listos para crear y publicar", `- \`price\`: "${price}"`, `- \`compareAtPrice\`: "${compareAtPrice}"`, "", "Pasa esos dos valores a `create_product_task`; cuando la tarea termine, usa el tool de publicaci\xF3n que el usuario elija.");
|
|
2169
|
+
if (usedReferenceValues) {
|
|
2170
|
+
lines.push("", "_Se usaron valores gu\xEDa de Colombia para los campos omitidos. Son un punto de partida editable, no promedios oficiales._");
|
|
2171
|
+
}
|
|
2172
|
+
lines.push("", `_Este c\xE1lculo es read-only, no consume cr\xE9ditos y us\xF3 entrega estimada de ${formatPercent(result.deliveryRate)}._`, "", `Supuestos: proveedor ${formatCop(input.supplierCostCop)}, env\xEDo ${formatCop(input.shippingCostCop)}, devoluciones ${formatPercent(input.returnRate)}, cancelaciones ${formatPercent(input.cancellationRate)}, CPA ${formatCop(input.adsManagerCpaCop)}, margen deseado ${formatPercent(input.desiredMargin)}.`);
|
|
2173
|
+
return {
|
|
2174
|
+
content: [
|
|
2175
|
+
{
|
|
2176
|
+
type: "text",
|
|
2177
|
+
text: lines.join("\n")
|
|
2178
|
+
}
|
|
2179
|
+
]
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
__name(renderProductCosting, "renderProductCosting");
|
|
2183
|
+
function formatCop(value) {
|
|
2184
|
+
return `$${Math.round(value).toLocaleString("es-CO")} COP`;
|
|
2185
|
+
}
|
|
2186
|
+
__name(formatCop, "formatCop");
|
|
2187
|
+
function formatPercent(value) {
|
|
2188
|
+
return `${(value * 100).toFixed(1).replace(/\.0$/, "")}%`;
|
|
2189
|
+
}
|
|
2190
|
+
__name(formatPercent, "formatPercent");
|
|
2191
|
+
|
|
2027
2192
|
// src/server.ts
|
|
2028
2193
|
function createServer(opts) {
|
|
2029
2194
|
const { apiKey, apiBaseUrl, proxyBaseUrl = null, transport = "http" } = opts;
|
|
@@ -2039,6 +2204,7 @@ function createServer(opts) {
|
|
|
2039
2204
|
}
|
|
2040
2205
|
});
|
|
2041
2206
|
const api = new ApiClient(apiBaseUrl, apiKey);
|
|
2207
|
+
registerProductCostingTools(server);
|
|
2042
2208
|
registerTaskTools(server, api, {
|
|
2043
2209
|
proxyBaseUrl,
|
|
2044
2210
|
transport
|
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.13.0";
|
|
114
114
|
|
|
115
115
|
// src/tools/tasks.ts
|
|
116
116
|
import { z as z2 } from "zod";
|
|
@@ -411,15 +411,12 @@ var VideoPresetEnum = z.enum([
|
|
|
411
411
|
"auto",
|
|
412
412
|
"ugc_dynamic",
|
|
413
413
|
"ugc_unboxing_chaos",
|
|
414
|
-
"ugc_curiosity_reveal",
|
|
415
|
-
"ugc_listicle_3",
|
|
416
|
-
"ugc_skeptic_confession",
|
|
417
|
-
"ugc_demo_review",
|
|
418
414
|
"problem_solution",
|
|
419
|
-
"pixar_problem",
|
|
420
|
-
"pixar_sassy",
|
|
421
415
|
"product_showcase",
|
|
422
|
-
"wearable_showcase"
|
|
416
|
+
"wearable_showcase",
|
|
417
|
+
"cinematic",
|
|
418
|
+
"pixar_problem",
|
|
419
|
+
"pixar_sassy"
|
|
423
420
|
]);
|
|
424
421
|
var VIDEO_PRESET_RESOLUTION = {
|
|
425
422
|
auto: null,
|
|
@@ -433,26 +430,6 @@ var VIDEO_PRESET_RESOLUTION = {
|
|
|
433
430
|
narrativeStyle: "ugc_unboxing_chaos",
|
|
434
431
|
videoModel: "veo"
|
|
435
432
|
},
|
|
436
|
-
ugc_curiosity_reveal: {
|
|
437
|
-
videoStyle: "ai_dynamic",
|
|
438
|
-
narrativeStyle: "ugc_curiosity_reveal",
|
|
439
|
-
videoModel: "veo"
|
|
440
|
-
},
|
|
441
|
-
ugc_listicle_3: {
|
|
442
|
-
videoStyle: "ai_dynamic",
|
|
443
|
-
narrativeStyle: "ugc_listicle_3",
|
|
444
|
-
videoModel: "kling"
|
|
445
|
-
},
|
|
446
|
-
ugc_skeptic_confession: {
|
|
447
|
-
videoStyle: "ai_dynamic",
|
|
448
|
-
narrativeStyle: "ugc_skeptic_confession",
|
|
449
|
-
videoModel: "veo"
|
|
450
|
-
},
|
|
451
|
-
ugc_demo_review: {
|
|
452
|
-
videoStyle: "ai_dynamic",
|
|
453
|
-
narrativeStyle: "ugc_demo_review",
|
|
454
|
-
videoModel: "kling"
|
|
455
|
-
},
|
|
456
433
|
problem_solution: {
|
|
457
434
|
videoStyle: "ai_dynamic",
|
|
458
435
|
narrativeStyle: "problem_product_cta",
|
|
@@ -477,6 +454,11 @@ var VIDEO_PRESET_RESOLUTION = {
|
|
|
477
454
|
videoStyle: "ugc",
|
|
478
455
|
narrativeStyle: "wearable_showcase",
|
|
479
456
|
videoModel: "veo"
|
|
457
|
+
},
|
|
458
|
+
cinematic: {
|
|
459
|
+
videoStyle: "product_only",
|
|
460
|
+
narrativeStyle: "cinematic",
|
|
461
|
+
videoModel: "grok"
|
|
480
462
|
}
|
|
481
463
|
};
|
|
482
464
|
function resolveVideoPreset(preset) {
|
|
@@ -505,6 +487,7 @@ var NarrativeStyleEnum = z.enum([
|
|
|
505
487
|
"review",
|
|
506
488
|
"problem_product_cta",
|
|
507
489
|
"product_showcase",
|
|
490
|
+
"cinematic",
|
|
508
491
|
"metaphor_product_cta",
|
|
509
492
|
"wearable_showcase",
|
|
510
493
|
"animated_problem_pixar",
|
|
@@ -515,6 +498,16 @@ var NarrativeStyleEnum = z.enum([
|
|
|
515
498
|
"ugc_skeptic_confession",
|
|
516
499
|
"ugc_demo_review"
|
|
517
500
|
]);
|
|
501
|
+
var PublicNarrativeStyleEnum = z.enum([
|
|
502
|
+
"review",
|
|
503
|
+
"ugc_unboxing_chaos",
|
|
504
|
+
"problem_product_cta",
|
|
505
|
+
"product_showcase",
|
|
506
|
+
"wearable_showcase",
|
|
507
|
+
"cinematic",
|
|
508
|
+
"animated_problem_pixar",
|
|
509
|
+
"sassy_product_pixar"
|
|
510
|
+
]);
|
|
518
511
|
var VideoModelEnum = z.enum([
|
|
519
512
|
"veo",
|
|
520
513
|
"wan",
|
|
@@ -1155,11 +1148,11 @@ var CreateProductTaskInput = z2.object({
|
|
|
1155
1148
|
paymentMethods: z2.string().min(1).max(240).optional().describe('Opcional. M\xE9todos de pago que puede mostrar la landing. Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la landing no muestra m\xE9todos de pago.'),
|
|
1156
1149
|
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."),
|
|
1157
1150
|
productLabel: z2.string().trim().max(80).optional().describe('Opcional. Qu\xE9 es el producto, en pocas palabras (ej. "quita pelusas para ropa"). Es AUTORITATIVO: si lo env\xEDas, la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA detecta el producto desde la imagen.'),
|
|
1158
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo
|
|
1151
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo de video. Default "auto" (la IA escoge tras analizar el producto). Opciones actuales: "auto", "ugc_dynamic" (UGC Review), "ugc_unboxing_chaos" (Unboxing), "problem_solution" (problema\u2192soluci\xF3n), "product_showcase" (Producto en acci\xF3n: una prueba funcional), "wearable_showcase" (modelaje con la prenda/accesorio puesto), "cinematic" (historia visual premium), "pixar_problem" (problema animado Pixar), "pixar_sassy" (producto que habla Pixar). V\xEDa MCP todo video renderiza a 15s single_call. Si el usuario pide algo fuera de esta lista, p\xEDdele una opci\xF3n v\xE1lida.'),
|
|
1159
1152
|
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".'),
|
|
1160
1153
|
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).'),
|
|
1161
1154
|
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."),
|
|
1162
|
-
angleNarratives:
|
|
1155
|
+
angleNarratives: PublicNarrativeStyleEnum.array().max(3).optional().describe('Opcional. Narrativa POR \xC1NGULO (posicional). Valores actuales: "review", "ugc_unboxing_chaos", "problem_product_cta", "product_showcase", "wearable_showcase", "cinematic", "animated_problem_pixar", "sassy_product_pixar". angleNarratives[i] fuerza la narrativa del \xE1ngulo i; las posiciones restantes las elige la IA. Requiere multiAngleVideos:true para varios \xE1ngulos.'),
|
|
1163
1156
|
outputs: z2.array(z2.enum([
|
|
1164
1157
|
"landing",
|
|
1165
1158
|
"video",
|
|
@@ -1168,7 +1161,7 @@ var CreateProductTaskInput = z2.object({
|
|
|
1168
1161
|
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."),
|
|
1169
1162
|
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."),
|
|
1170
1163
|
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.'),
|
|
1171
|
-
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof", "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
|
|
1164
|
+
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution", "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity", "offer_value" o "lifestyle_identity". Default "classic".'),
|
|
1172
1165
|
landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica cuando landing est\xE1 entre outputs: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
|
|
1173
1166
|
}).strict();
|
|
1174
1167
|
var GetTaskStatusInput = z2.object({
|
|
@@ -1472,7 +1465,7 @@ var VideoInput = z3.object({
|
|
|
1472
1465
|
"Auto"
|
|
1473
1466
|
]).describe('Relaci\xF3n de aspecto. "9:16" = vertical (TikTok/Reels \u2014 default recomendado para ads). "16:9" = horizontal (YouTube). "Auto" = decide la IA.'),
|
|
1474
1467
|
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."),
|
|
1475
|
-
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo
|
|
1468
|
+
videoPreset: VideoPresetEnum.default("auto").describe('Opcional. Tipo de video. Default "auto". Opciones actuales: "auto", "ugc_dynamic" (UGC Review), "ugc_unboxing_chaos" (Unboxing), "problem_solution" (problema\u2192soluci\xF3n), "product_showcase" (Producto en acci\xF3n: una prueba funcional), "wearable_showcase" (modelaje con la prenda/accesorio puesto), "cinematic" (historia visual premium), "pixar_problem" (problema animado Pixar), "pixar_sassy" (producto que habla Pixar). V\xEDa MCP todo video renderiza a m\xE1x. 15s.'),
|
|
1476
1469
|
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".'),
|
|
1477
1470
|
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).'),
|
|
1478
1471
|
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"].'),
|
|
@@ -1534,7 +1527,7 @@ var LandingInput = z3.object({
|
|
|
1534
1527
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
1535
1528
|
brandId: CreativeRefs.brandId,
|
|
1536
1529
|
creativeDirection: CreativeRefs.creativeDirection,
|
|
1537
|
-
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution" (problema\u2192soluci\xF3n), "social_proof" (
|
|
1530
|
+
landingStrategy: LandingStrategyEnum.default("classic").describe('Opcional. Estructura de la landing cl\xE1sica: "classic" (estructura original probada y recomendada; default del primer rollout), "auto" (la IA decide; experimental), "pain_solution" (problema\u2192soluci\xF3n), "social_proof" (Landing Viral: UGC, WhatsApp, Instagram y evidencia social), "benefits_clarity" (beneficios claros), "offer_value" (promoci\xF3n/valor) o "lifestyle_identity" (estilo de vida). Default "classic".'),
|
|
1538
1531
|
landingImageFormat: LandingImageFormatEnum.default("9:16").describe('Opcional. Formato/relaci\xF3n de aspecto de las im\xE1genes de la landing cl\xE1sica: "9:16" (vertical, por defecto), "4:5" (vertical compacto para feed) o "1:1" (cuadrado). Default "9:16".')
|
|
1539
1532
|
});
|
|
1540
1533
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
@@ -1609,7 +1602,7 @@ var ApproveVideoScriptInput = z4.object({
|
|
|
1609
1602
|
var RegenerateVideoScriptInput = z4.object({
|
|
1610
1603
|
taskId: z4.string().min(1).describe("ID de la tarea."),
|
|
1611
1604
|
angleKey: z4.string().min(1).describe('Clave del \xE1ngulo a regenerar. Single-angle: pasa "angle_0". Multi-angle: el mismo que devuelve get_video_script. NO se puede omitir \u2014 el backend lo exige siempre.'),
|
|
1612
|
-
narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion
|
|
1605
|
+
narrativeStyle: NarrativeStyleEnum.describe('Narrativa para el nuevo guion. Incluye valores hist\xF3ricos porque esta herramienta tambi\xE9n regenera tareas antiguas. REQUERIDO \u2014 el backend NO acepta "mantener la actual"; siempre tienes que elegir una. Si el usuario quiere re-rolear con la misma narrativa, pasa la que ya tiene el \xE1ngulo (la ves en get_video_script o en get_task_status).')
|
|
1613
1606
|
}).strict();
|
|
1614
1607
|
function registerScriptReviewTools(server, api) {
|
|
1615
1608
|
server.tool("get_video_script", 'Lee el guion del video pendiente de revisi\xF3n. S\xF3lo funciona cuando la tarea fue creada con `pauseForVideoScriptReview: true` (via generate_video_creative). Devuelve { status, angleKey, perClipSeconds?, scenes: [{ index, dialogue, maxWords, minWords?, beatId? }], error? }. El campo `error` (cuando viene con status="ready") es el motivo legible por el que un intento previo de aprobaci\xF3n fue rechazado (p.ej. el guion no cab\xEDa en el tiempo y el \xE1ngulo volvi\xF3 a "ready"); mu\xE9straselo al usuario.\n\nCU\xC1NDO LLAMAR: no existe un status top-level "awaiting_script_review" en get_task_status. Si la tarea fue creada con pauseForVideoScriptReview=true, polea get_task_status hasta que `status === "processing"` y a partir de ah\xED llama este tool peri\xF3dicamente \u2014 la respuesta de get_video_script trae su propio campo `status` que es la se\xF1al real:\n- "ready" \u2192 el guion est\xE1 listo; mu\xE9strale al usuario cada `scenes[].dialogue` por `index` con su tope `maxWords`, recoge ediciones, llama approve_video_script.\n- "not_ready" \u2192 el guion a\xFAn se est\xE1 generando; espera 5\u201310s y reintenta este tool.\n- "approved" \u2192 ya fue aprobado; en multi-angle puede ser que ese \xE1ngulo ya est\xE9 hecho \u2014 vuelve a llamar con otro angleKey.\n- "skipped" \u2192 el usuario opt\xF3 por no revisar en una pausa previa.\n- "error" \u2192 el backend no pudo generar el guion; usa `regenerate_video_script` para re-intentar (es la ruta de recuperaci\xF3n v\xE1lida \u2014 el guion no se aprueba en estado "error").\n\nMULTI-ANGLE: si la tarea tiene varios \xE1ngulos, omitir angleKey siempre devuelve "angle_0" literal \u2014 itera expl\xEDcitamente por "angle_0", "angle_1", ... hasta encontrar uno con status="ready".', GetVideoScriptInput.shape, async (input) => {
|
|
@@ -1980,6 +1973,178 @@ function renderTikTokStatusMarkdown(r) {
|
|
|
1980
1973
|
}
|
|
1981
1974
|
__name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
|
|
1982
1975
|
|
|
1976
|
+
// ../../packages/product-economics/src/product-costing.ts
|
|
1977
|
+
import { z as z8 } from "zod";
|
|
1978
|
+
var PRODUCT_PROFIT_GUIDE_COP = 1e4;
|
|
1979
|
+
var ProductCostingInputSchema = z8.object({
|
|
1980
|
+
supplierCostCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
|
|
1981
|
+
shippingCostCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
|
|
1982
|
+
returnRate: z8.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
1983
|
+
cancellationRate: z8.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
|
|
1984
|
+
adsManagerCpaCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
|
|
1985
|
+
desiredMargin: z8.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
|
|
1986
|
+
}).superRefine(({ returnRate, cancellationRate }, context) => {
|
|
1987
|
+
if (returnRate + cancellationRate >= 1) {
|
|
1988
|
+
context.addIssue({
|
|
1989
|
+
code: z8.ZodIssueCode.custom,
|
|
1990
|
+
message: "delivery_rate_positive",
|
|
1991
|
+
path: [
|
|
1992
|
+
"cancellationRate"
|
|
1993
|
+
]
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
var PRODUCT_COSTING_DEFAULTS = {
|
|
1998
|
+
supplierCostCop: 19990,
|
|
1999
|
+
shippingCostCop: 19e3,
|
|
2000
|
+
returnRate: 0.25,
|
|
2001
|
+
cancellationRate: 0.15,
|
|
2002
|
+
adsManagerCpaCop: 15e3,
|
|
2003
|
+
desiredMargin: 0.1
|
|
2004
|
+
};
|
|
2005
|
+
function assertPositiveFinite(value) {
|
|
2006
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
2007
|
+
throw new RangeError("Commercial pricing requires a positive finite value.");
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
__name(assertPositiveFinite, "assertPositiveFinite");
|
|
2011
|
+
function roundToCommercial9990(value) {
|
|
2012
|
+
assertPositiveFinite(value);
|
|
2013
|
+
const tier = Math.max(Math.floor((value + 10) / 1e4 + 0.5), 1);
|
|
2014
|
+
return tier * 1e4 - 10;
|
|
2015
|
+
}
|
|
2016
|
+
__name(roundToCommercial9990, "roundToCommercial9990");
|
|
2017
|
+
function ceilToCommercial9990(value) {
|
|
2018
|
+
assertPositiveFinite(value);
|
|
2019
|
+
const tier = Math.max(Math.ceil((value + 10) / 1e4 - 1e-10), 1);
|
|
2020
|
+
return tier * 1e4 - 10;
|
|
2021
|
+
}
|
|
2022
|
+
__name(ceilToCommercial9990, "ceilToCommercial9990");
|
|
2023
|
+
function calculateProductCosting(rawInput) {
|
|
2024
|
+
const input = ProductCostingInputSchema.parse(rawInput);
|
|
2025
|
+
const deliveryRate = 1 - input.returnRate - input.cancellationRate;
|
|
2026
|
+
const adjustedShippingCostCop = input.shippingCostCop / (1 - input.returnRate);
|
|
2027
|
+
const adjustedCpaCop = input.adsManagerCpaCop / deliveryRate;
|
|
2028
|
+
const totalCostCop = input.supplierCostCop + adjustedShippingCostCop + adjustedCpaCop;
|
|
2029
|
+
const suggestedPriceCop = totalCostCop / (1 - input.desiredMargin);
|
|
2030
|
+
const baseCommercialPriceCop = roundToCommercial9990(suggestedPriceCop);
|
|
2031
|
+
const minimumSafePriceCop = Math.max(suggestedPriceCop, totalCostCop + PRODUCT_PROFIT_GUIDE_COP);
|
|
2032
|
+
const commercialPriceCop = ceilToCommercial9990(minimumSafePriceCop);
|
|
2033
|
+
const comparisonPriceCop = commercialPriceCop * 2;
|
|
2034
|
+
const estimatedProfitCop = commercialPriceCop - totalCostCop;
|
|
2035
|
+
const actualMargin = estimatedProfitCop / commercialPriceCop;
|
|
2036
|
+
const breakEvenAdsManagerCpaCop = Math.max(0, (commercialPriceCop - input.supplierCostCop - adjustedShippingCostCop) * deliveryRate);
|
|
2037
|
+
return {
|
|
2038
|
+
deliveryRate,
|
|
2039
|
+
adjustedShippingCostCop,
|
|
2040
|
+
adjustedCpaCop,
|
|
2041
|
+
totalCostCop,
|
|
2042
|
+
suggestedPriceCop,
|
|
2043
|
+
baseCommercialPriceCop,
|
|
2044
|
+
minimumSafePriceCop,
|
|
2045
|
+
commercialPriceCop,
|
|
2046
|
+
comparisonPriceCop,
|
|
2047
|
+
estimatedProfitCop,
|
|
2048
|
+
actualMargin,
|
|
2049
|
+
breakEvenAdsManagerCpaCop,
|
|
2050
|
+
wasPriceAutoAdjusted: commercialPriceCop > baseCommercialPriceCop,
|
|
2051
|
+
profitabilityStatus: "healthy"
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
__name(calculateProductCosting, "calculateProductCosting");
|
|
2055
|
+
|
|
2056
|
+
// src/tools/product-costing.ts
|
|
2057
|
+
import { z as z9 } from "zod";
|
|
2058
|
+
var optionalCopAmount = /* @__PURE__ */ __name((description) => z9.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalCopAmount");
|
|
2059
|
+
var optionalPercent = /* @__PURE__ */ __name((description) => z9.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
|
|
2060
|
+
var ProductCostingToolInput = z9.object({
|
|
2061
|
+
supplierCostCop: z9.number().finite().max(Number.MAX_SAFE_INTEGER).positive().describe("Costo real de una unidad con el proveedor, en pesos colombianos (COP). Este valor s\xED debe venir del usuario."),
|
|
2062
|
+
shippingCostCop: optionalCopAmount("Costo de un env\xEDo entregado en COP. Omite para usar la referencia editable de Colombia: 19000."),
|
|
2063
|
+
returnRatePercent: optionalPercent("Porcentaje de pedidos devueltos. Ejemplo: 25 significa 25%. Omite para usar la referencia editable de Colombia: 25."),
|
|
2064
|
+
cancellationRatePercent: optionalPercent("Porcentaje de pedidos cancelados antes de entrega. Ejemplo: 15 significa 15%. Omite para usar la referencia editable de Colombia: 15."),
|
|
2065
|
+
adsManagerCpaCop: optionalCopAmount("Costo por compra mostrado en Ads Manager, en COP. Omite para usar la referencia editable de Colombia: 15000."),
|
|
2066
|
+
desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar la referencia editable de Colombia: 10.")
|
|
2067
|
+
});
|
|
2068
|
+
function registerProductCostingTools(server) {
|
|
2069
|
+
server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en Colombia (COP). Es READ-ONLY: no crea tareas ni publica nada. Pide al usuario \xFAnicamente el costo real del proveedor; si no conoce env\xEDo, devoluciones, cancelaciones, CPA o margen, om\xEDtelos y se usar\xE1n valores gu\xEDa editables de Colombia. Devuelve `price` y `compareAtPrice` listos para `create_product_task`; despu\xE9s puedes generar la tarea y publicarla con `publish_to_shopify`, `publish_to_meta` o `publish_to_tiktok`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, async (input) => {
|
|
2070
|
+
const normalized = normalizeToolInput(input);
|
|
2071
|
+
const parsed = ProductCostingInputSchema.safeParse(normalized.input);
|
|
2072
|
+
if (!parsed.success) {
|
|
2073
|
+
return {
|
|
2074
|
+
isError: true,
|
|
2075
|
+
content: [
|
|
2076
|
+
{
|
|
2077
|
+
type: "text",
|
|
2078
|
+
text: "No puedo calcular con esos porcentajes: devoluciones + cancelaciones deben sumar menos de 100%. Revisa los valores e int\xE9ntalo de nuevo."
|
|
2079
|
+
}
|
|
2080
|
+
]
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
return renderProductCosting(parsed.data, calculateProductCosting(parsed.data), normalized.usedReferenceValues);
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
__name(registerProductCostingTools, "registerProductCostingTools");
|
|
2087
|
+
function normalizeToolInput(input) {
|
|
2088
|
+
const optionalValues = [
|
|
2089
|
+
input.shippingCostCop,
|
|
2090
|
+
input.returnRatePercent,
|
|
2091
|
+
input.cancellationRatePercent,
|
|
2092
|
+
input.adsManagerCpaCop,
|
|
2093
|
+
input.desiredMarginPercent
|
|
2094
|
+
];
|
|
2095
|
+
return {
|
|
2096
|
+
input: {
|
|
2097
|
+
supplierCostCop: input.supplierCostCop,
|
|
2098
|
+
shippingCostCop: input.shippingCostCop ?? PRODUCT_COSTING_DEFAULTS.shippingCostCop,
|
|
2099
|
+
returnRate: (input.returnRatePercent ?? PRODUCT_COSTING_DEFAULTS.returnRate * 100) / 100,
|
|
2100
|
+
cancellationRate: (input.cancellationRatePercent ?? PRODUCT_COSTING_DEFAULTS.cancellationRate * 100) / 100,
|
|
2101
|
+
adsManagerCpaCop: input.adsManagerCpaCop ?? PRODUCT_COSTING_DEFAULTS.adsManagerCpaCop,
|
|
2102
|
+
desiredMargin: (input.desiredMarginPercent ?? PRODUCT_COSTING_DEFAULTS.desiredMargin * 100) / 100
|
|
2103
|
+
},
|
|
2104
|
+
usedReferenceValues: optionalValues.some((value) => value === void 0)
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
__name(normalizeToolInput, "normalizeToolInput");
|
|
2108
|
+
function renderProductCosting(input, result, usedReferenceValues) {
|
|
2109
|
+
const price = formatCop(result.commercialPriceCop);
|
|
2110
|
+
const compareAtPrice = formatCop(result.comparisonPriceCop);
|
|
2111
|
+
const lines = [
|
|
2112
|
+
"\u2705 **Costeo del producto \xB7 Colombia**",
|
|
2113
|
+
"",
|
|
2114
|
+
`Precio recomendado: **${price}**`,
|
|
2115
|
+
`Precio comparado: ~~${compareAtPrice}~~`,
|
|
2116
|
+
`Ganancia estimada por venta entregada: **${formatCop(result.estimatedProfitCop)}**`,
|
|
2117
|
+
`Margen real estimado: **${formatPercent(result.actualMargin)}**`,
|
|
2118
|
+
`Costo total estimado: ${formatCop(result.totalCostCop)}`,
|
|
2119
|
+
`M\xE1ximo sugerido en Ads Manager por compra: **${formatCop(result.breakEvenAdsManagerCpaCop)}**`
|
|
2120
|
+
];
|
|
2121
|
+
if (result.wasPriceAutoAdjusted) {
|
|
2122
|
+
lines.push("", `ProductMaker subi\xF3 autom\xE1ticamente el precio desde ${formatCop(result.baseCommercialPriceCop)} para proteger el margen y una ganancia gu\xEDa m\xEDnima de $10.000 COP.`);
|
|
2123
|
+
}
|
|
2124
|
+
lines.push("", "### Valores listos para crear y publicar", `- \`price\`: "${price}"`, `- \`compareAtPrice\`: "${compareAtPrice}"`, "", "Pasa esos dos valores a `create_product_task`; cuando la tarea termine, usa el tool de publicaci\xF3n que el usuario elija.");
|
|
2125
|
+
if (usedReferenceValues) {
|
|
2126
|
+
lines.push("", "_Se usaron valores gu\xEDa de Colombia para los campos omitidos. Son un punto de partida editable, no promedios oficiales._");
|
|
2127
|
+
}
|
|
2128
|
+
lines.push("", `_Este c\xE1lculo es read-only, no consume cr\xE9ditos y us\xF3 entrega estimada de ${formatPercent(result.deliveryRate)}._`, "", `Supuestos: proveedor ${formatCop(input.supplierCostCop)}, env\xEDo ${formatCop(input.shippingCostCop)}, devoluciones ${formatPercent(input.returnRate)}, cancelaciones ${formatPercent(input.cancellationRate)}, CPA ${formatCop(input.adsManagerCpaCop)}, margen deseado ${formatPercent(input.desiredMargin)}.`);
|
|
2129
|
+
return {
|
|
2130
|
+
content: [
|
|
2131
|
+
{
|
|
2132
|
+
type: "text",
|
|
2133
|
+
text: lines.join("\n")
|
|
2134
|
+
}
|
|
2135
|
+
]
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
__name(renderProductCosting, "renderProductCosting");
|
|
2139
|
+
function formatCop(value) {
|
|
2140
|
+
return `$${Math.round(value).toLocaleString("es-CO")} COP`;
|
|
2141
|
+
}
|
|
2142
|
+
__name(formatCop, "formatCop");
|
|
2143
|
+
function formatPercent(value) {
|
|
2144
|
+
return `${(value * 100).toFixed(1).replace(/\.0$/, "")}%`;
|
|
2145
|
+
}
|
|
2146
|
+
__name(formatPercent, "formatPercent");
|
|
2147
|
+
|
|
1983
2148
|
// src/server.ts
|
|
1984
2149
|
function createServer(opts) {
|
|
1985
2150
|
const { apiKey, apiBaseUrl, proxyBaseUrl = null, transport = "http" } = opts;
|
|
@@ -1995,6 +2160,7 @@ function createServer(opts) {
|
|
|
1995
2160
|
}
|
|
1996
2161
|
});
|
|
1997
2162
|
const api = new ApiClient(apiBaseUrl, apiKey);
|
|
2163
|
+
registerProductCostingTools(server);
|
|
1998
2164
|
registerTaskTools(server, api, {
|
|
1999
2165
|
proxyBaseUrl,
|
|
2000
2166
|
transport
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
|
-
"description": "Product Maker MCP server —
|
|
6
|
+
"description": "Product Maker MCP server — cost products, generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from Claude, ChatGPT, or Gemini.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"mcp",
|
|
9
9
|
"mcp-server",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"ai",
|
|
19
19
|
"productmaker",
|
|
20
20
|
"dropshipping",
|
|
21
|
+
"product-costing",
|
|
21
22
|
"ecommerce",
|
|
22
23
|
"shopify",
|
|
23
24
|
"meta-ads",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"tsx": "^4.16.0",
|
|
63
64
|
"typescript": "^7.0.2",
|
|
64
65
|
"vitest": "^2.1.9",
|
|
66
|
+
"@packages/product-economics": "0.1.0",
|
|
65
67
|
"@packages/types": "0.1.0"
|
|
66
68
|
},
|
|
67
69
|
"publishConfig": {
|