@productmaker/mcp 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http.js +29 -6
- package/dist/stdio.js +29 -6
- package/package.json +1 -1
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 = "
|
|
116
|
+
var MCP_VERSION = "1.1.1";
|
|
117
117
|
|
|
118
118
|
// src/tools/tasks.ts
|
|
119
119
|
import { z as z2 } from "zod";
|
|
@@ -676,7 +676,8 @@ var McpErrorCode = {
|
|
|
676
676
|
META_AD_SET_BUDGET_REQUIRED: "META_AD_SET_BUDGET_REQUIRED",
|
|
677
677
|
META_DAILY_BUDGET_TOO_HIGH: "META_DAILY_BUDGET_TOO_HIGH",
|
|
678
678
|
META_CAMPAIGN_CONFIG_INVALID: "META_CAMPAIGN_CONFIG_INVALID",
|
|
679
|
-
|
|
679
|
+
META_WHATSAPP_INVALID_NUMBER: "META_WHATSAPP_INVALID_NUMBER",
|
|
680
|
+
META_INVALID_CTA: "META_INVALID_CTA",
|
|
680
681
|
// TikTok Ads
|
|
681
682
|
TIKTOK_NOT_CONNECTED: "TIKTOK_NOT_CONNECTED",
|
|
682
683
|
TIKTOK_TOKEN_EXPIRED: "TIKTOK_TOKEN_EXPIRED",
|
|
@@ -726,7 +727,8 @@ var CODE_MESSAGES = {
|
|
|
726
727
|
META_AD_SET_BUDGET_REQUIRED: "This campaign configuration requires ad-set-level budget, not campaign-level.",
|
|
727
728
|
META_DAILY_BUDGET_TOO_HIGH: "The daily budget exceeds the maximum allowed by Meta for this account. Lower it and retry.",
|
|
728
729
|
META_CAMPAIGN_CONFIG_INVALID: "Campaign configuration is invalid. Verify `dailyBudgetUsd`, `targetCountries`, and `campaignName`.",
|
|
729
|
-
|
|
730
|
+
META_WHATSAPP_INVALID_NUMBER: "The WhatsApp `phoneNumber` is invalid. Pass it in international format with the country code (e.g. +573001234567).",
|
|
731
|
+
META_INVALID_CTA: "Meta could not configure the ad button (invalid call-to-action). Try again or contact support.",
|
|
730
732
|
// TikTok
|
|
731
733
|
TIKTOK_NOT_CONNECTED: "TikTok Ads is not connected. Connect it at https://productmaker.app/integrations.",
|
|
732
734
|
TIKTOK_TOKEN_EXPIRED: "The TikTok session expired. Reconnect it at https://productmaker.app/integrations.",
|
|
@@ -789,6 +791,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
789
791
|
McpErrorCode.META_NO_LANDING_URL,
|
|
790
792
|
McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
|
|
791
793
|
McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
|
|
794
|
+
McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
|
|
792
795
|
McpErrorCode.META_BID_STRATEGY_REQUIRES_AMOUNT,
|
|
793
796
|
McpErrorCode.META_CBO_BUDGET_CONFLICT,
|
|
794
797
|
McpErrorCode.META_BUDGET_MODE_CONFLICT,
|
|
@@ -847,7 +850,8 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
847
850
|
McpErrorCode.META_AD_SET_BUDGET_REQUIRED,
|
|
848
851
|
McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
|
|
849
852
|
McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
|
|
850
|
-
McpErrorCode.
|
|
853
|
+
McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
|
|
854
|
+
McpErrorCode.META_INVALID_CTA,
|
|
851
855
|
// TikTok
|
|
852
856
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
853
857
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -1170,6 +1174,7 @@ var CreateProductTaskInput = z2.object({
|
|
|
1170
1174
|
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.'),
|
|
1171
1175
|
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.'),
|
|
1172
1176
|
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."),
|
|
1177
|
+
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.'),
|
|
1173
1178
|
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
1179
|
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
1180
|
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).'),
|
|
@@ -1275,6 +1280,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1275
1280
|
"options.price": input.price,
|
|
1276
1281
|
"options.offer": input.offer,
|
|
1277
1282
|
"options.productContext": input.productContext,
|
|
1283
|
+
"options.productLabel": input.productLabel,
|
|
1278
1284
|
"options.videoPreset": input.videoPreset,
|
|
1279
1285
|
"options.videoStyle": resolved?.videoStyle,
|
|
1280
1286
|
"options.narrativeStyle": resolved?.narrativeStyle,
|
|
@@ -1429,6 +1435,7 @@ var CreativeRefs = {
|
|
|
1429
1435
|
price: z3.string().optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
|
|
1430
1436
|
offer: z3.string().optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "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 el usuario te dice "t\xFA elige", omite el campo.'),
|
|
1431
1437
|
productContext: z3.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."),
|
|
1438
|
+
productLabel: z3.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
|
|
1432
1439
|
buyerPersona: z3.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
|
|
1433
1440
|
isCombo: z3.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
|
|
1434
1441
|
competitorScanEnabled: z3.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.")
|
|
@@ -1452,6 +1459,7 @@ var VideoInput = z3.object({
|
|
|
1452
1459
|
price: CreativeRefs.price,
|
|
1453
1460
|
offer: CreativeRefs.offer,
|
|
1454
1461
|
productContext: CreativeRefs.productContext,
|
|
1462
|
+
productLabel: CreativeRefs.productLabel,
|
|
1455
1463
|
cta: z3.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
|
|
1456
1464
|
scriptNotes: z3.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
|
|
1457
1465
|
pauseForVideoScriptReview: z3.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
|
|
@@ -1483,6 +1491,7 @@ var ImageInput = z3.object({
|
|
|
1483
1491
|
compareAtPrice: z3.string().optional().describe('Opcional. Precio "antes" de la oferta \u2014 solo para im\xE1genes con descuento. Ej. "$129.000" cuando price="$89.900". \xDAtil con creativeArchetype="offer_value_prop".'),
|
|
1484
1492
|
offer: CreativeRefs.offer,
|
|
1485
1493
|
productContext: CreativeRefs.productContext,
|
|
1494
|
+
productLabel: CreativeRefs.productLabel,
|
|
1486
1495
|
isCombo: CreativeRefs.isCombo,
|
|
1487
1496
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1488
1497
|
});
|
|
@@ -1495,6 +1504,7 @@ var LandingInput = z3.object({
|
|
|
1495
1504
|
price: CreativeRefs.price,
|
|
1496
1505
|
offer: CreativeRefs.offer,
|
|
1497
1506
|
productContext: CreativeRefs.productContext,
|
|
1507
|
+
productLabel: CreativeRefs.productLabel,
|
|
1498
1508
|
paymentMethods: z3.string().optional().describe('Opcional. M\xE9todos de pago a mostrar (lista libre). Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la IA usa los m\xE1s comunes del pa\xEDs.'),
|
|
1499
1509
|
isCombo: CreativeRefs.isCombo,
|
|
1500
1510
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
@@ -1674,7 +1684,10 @@ var MetaInput = z6.object({
|
|
|
1674
1684
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
1675
1685
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
1676
1686
|
* (Meta's primary-text limit). */
|
|
1677
|
-
primaryText: z6.string().trim().min(1).max(500).optional()
|
|
1687
|
+
primaryText: z6.string().trim().min(1).max(500).optional(),
|
|
1688
|
+
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
1689
|
+
* (wa.me) instead of a landing page. */
|
|
1690
|
+
phoneNumber: z6.string().min(8).max(32).optional()
|
|
1678
1691
|
});
|
|
1679
1692
|
var TikTokInput = z6.object({
|
|
1680
1693
|
taskId: z6.string().min(1),
|
|
@@ -1710,7 +1723,7 @@ function registerPublishTools(server, api) {
|
|
|
1710
1723
|
throw translateError(e);
|
|
1711
1724
|
}
|
|
1712
1725
|
});
|
|
1713
|
-
server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en Meta Ads Manager. Use `list_meta_ad_accounts` antes para un adAccountId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 si la tarea gener\xF3 3 \xE1ngulos y el usuario solo quiere los #0 y #2, pasa [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario en USD, ej. 25), `targetCountries` (c\xF3digos ISO-2, m\xE1x 1 para Meta), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. Despu\xE9s de publicar usa `get_meta_campaign_status` para chequear el estado.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, ...rest }) => {
|
|
1726
|
+
server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en Meta Ads Manager. Use `list_meta_ad_accounts` antes para un adAccountId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 si la tarea gener\xF3 3 \xE1ngulos y el usuario solo quiere los #0 y #2, pasa [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario en USD, ej. 25), `targetCountries` (c\xF3digos ISO-2, m\xE1x 1 para Meta), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. `phoneNumber` (opcional): si el usuario quiere que el bot\xF3n del anuncio abra un chat de WhatsApp en vez de una landing, pasa el n\xFAmero (con c\xF3digo de pa\xEDs, ej. +573001234567) \u2014 no hace falta `landingUrl` en ese caso. Despu\xE9s de publicar usa `get_meta_campaign_status` para chequear el estado.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
|
|
1714
1727
|
try {
|
|
1715
1728
|
const body = {
|
|
1716
1729
|
...rest
|
|
@@ -1718,6 +1731,16 @@ function registerPublishTools(server, api) {
|
|
|
1718
1731
|
if (campaignConfig) {
|
|
1719
1732
|
body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
|
|
1720
1733
|
}
|
|
1734
|
+
if (phoneNumber) {
|
|
1735
|
+
const overrides = isRecord(body.campaignOverrides) ? body.campaignOverrides : {};
|
|
1736
|
+
body.campaignOverrides = {
|
|
1737
|
+
...overrides,
|
|
1738
|
+
destination: {
|
|
1739
|
+
type: "whatsapp",
|
|
1740
|
+
phoneNumber
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1721
1744
|
if (primaryText) body.primaryText = primaryText;
|
|
1722
1745
|
const r = await api.postJson(`/v1/facebook/campaigns/from-task/${encodeURIComponent(taskId)}`, body);
|
|
1723
1746
|
return asText({
|
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 = "
|
|
113
|
+
var MCP_VERSION = "1.1.1";
|
|
114
114
|
|
|
115
115
|
// src/tools/tasks.ts
|
|
116
116
|
import { z as z2 } from "zod";
|
|
@@ -673,7 +673,8 @@ var McpErrorCode = {
|
|
|
673
673
|
META_AD_SET_BUDGET_REQUIRED: "META_AD_SET_BUDGET_REQUIRED",
|
|
674
674
|
META_DAILY_BUDGET_TOO_HIGH: "META_DAILY_BUDGET_TOO_HIGH",
|
|
675
675
|
META_CAMPAIGN_CONFIG_INVALID: "META_CAMPAIGN_CONFIG_INVALID",
|
|
676
|
-
|
|
676
|
+
META_WHATSAPP_INVALID_NUMBER: "META_WHATSAPP_INVALID_NUMBER",
|
|
677
|
+
META_INVALID_CTA: "META_INVALID_CTA",
|
|
677
678
|
// TikTok Ads
|
|
678
679
|
TIKTOK_NOT_CONNECTED: "TIKTOK_NOT_CONNECTED",
|
|
679
680
|
TIKTOK_TOKEN_EXPIRED: "TIKTOK_TOKEN_EXPIRED",
|
|
@@ -723,7 +724,8 @@ var CODE_MESSAGES = {
|
|
|
723
724
|
META_AD_SET_BUDGET_REQUIRED: "This campaign configuration requires ad-set-level budget, not campaign-level.",
|
|
724
725
|
META_DAILY_BUDGET_TOO_HIGH: "The daily budget exceeds the maximum allowed by Meta for this account. Lower it and retry.",
|
|
725
726
|
META_CAMPAIGN_CONFIG_INVALID: "Campaign configuration is invalid. Verify `dailyBudgetUsd`, `targetCountries`, and `campaignName`.",
|
|
726
|
-
|
|
727
|
+
META_WHATSAPP_INVALID_NUMBER: "The WhatsApp `phoneNumber` is invalid. Pass it in international format with the country code (e.g. +573001234567).",
|
|
728
|
+
META_INVALID_CTA: "Meta could not configure the ad button (invalid call-to-action). Try again or contact support.",
|
|
727
729
|
// TikTok
|
|
728
730
|
TIKTOK_NOT_CONNECTED: "TikTok Ads is not connected. Connect it at https://productmaker.app/integrations.",
|
|
729
731
|
TIKTOK_TOKEN_EXPIRED: "The TikTok session expired. Reconnect it at https://productmaker.app/integrations.",
|
|
@@ -786,6 +788,7 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
|
|
|
786
788
|
McpErrorCode.META_NO_LANDING_URL,
|
|
787
789
|
McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
|
|
788
790
|
McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
|
|
791
|
+
McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
|
|
789
792
|
McpErrorCode.META_BID_STRATEGY_REQUIRES_AMOUNT,
|
|
790
793
|
McpErrorCode.META_CBO_BUDGET_CONFLICT,
|
|
791
794
|
McpErrorCode.META_BUDGET_MODE_CONFLICT,
|
|
@@ -844,7 +847,8 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
|
|
|
844
847
|
McpErrorCode.META_AD_SET_BUDGET_REQUIRED,
|
|
845
848
|
McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
|
|
846
849
|
McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
|
|
847
|
-
McpErrorCode.
|
|
850
|
+
McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
|
|
851
|
+
McpErrorCode.META_INVALID_CTA,
|
|
848
852
|
// TikTok
|
|
849
853
|
McpErrorCode.TIKTOK_NOT_CONNECTED,
|
|
850
854
|
McpErrorCode.TIKTOK_TOKEN_EXPIRED,
|
|
@@ -1126,6 +1130,7 @@ var CreateProductTaskInput = z2.object({
|
|
|
1126
1130
|
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.'),
|
|
1127
1131
|
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.'),
|
|
1128
1132
|
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."),
|
|
1133
|
+
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.'),
|
|
1129
1134
|
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
1135
|
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
1136
|
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).'),
|
|
@@ -1231,6 +1236,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1231
1236
|
"options.price": input.price,
|
|
1232
1237
|
"options.offer": input.offer,
|
|
1233
1238
|
"options.productContext": input.productContext,
|
|
1239
|
+
"options.productLabel": input.productLabel,
|
|
1234
1240
|
"options.videoPreset": input.videoPreset,
|
|
1235
1241
|
"options.videoStyle": resolved?.videoStyle,
|
|
1236
1242
|
"options.narrativeStyle": resolved?.narrativeStyle,
|
|
@@ -1385,6 +1391,7 @@ var CreativeRefs = {
|
|
|
1385
1391
|
price: z3.string().optional().describe('Precio en formato libre \u2014 ej. "$89.900", "USD 29.99". Opcional pero MUY IMPACTANTE para conversi\xF3n: si el usuario no lo mencion\xF3, PREG\xDANTALE antes de invocar. Si el usuario te dice "t\xFA elige", omite el campo y la IA del backend sugiere uno.'),
|
|
1386
1392
|
offer: z3.string().optional().describe('Oferta a destacar \u2014 ej. "2x1", "Env\xEDo gratis", "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 el usuario te dice "t\xFA elige", omite el campo.'),
|
|
1387
1393
|
productContext: z3.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."),
|
|
1394
|
+
productLabel: z3.string().trim().max(80).optional().describe('Opcional. Nombre o tipo del producto en pocas palabras (ej. "collar de oro", "quita pelusas para ropa"). Es AUTORITATIVO: la IA usa esta identidad y no la reemplaza por otra categor\xEDa. Si lo omites, la IA lo detecta desde la imagen. NO salta la extracci\xF3n.'),
|
|
1388
1395
|
buyerPersona: z3.string().optional().describe('Opcional. Descripci\xF3n del comprador objetivo. Ej. "Mujeres 35-50, profesionales urbanas". Si se omite, la IA infiere desde el \xE1ngulo y producto.'),
|
|
1389
1396
|
isCombo: z3.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
|
|
1390
1397
|
competitorScanEnabled: z3.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.")
|
|
@@ -1408,6 +1415,7 @@ var VideoInput = z3.object({
|
|
|
1408
1415
|
price: CreativeRefs.price,
|
|
1409
1416
|
offer: CreativeRefs.offer,
|
|
1410
1417
|
productContext: CreativeRefs.productContext,
|
|
1418
|
+
productLabel: CreativeRefs.productLabel,
|
|
1411
1419
|
cta: z3.string().optional().describe('Opcional. Call-to-action final del video. Ej. "Compra ahora", "Pide el tuyo hoy", "Link en bio". Si se omite, la IA escribe uno.'),
|
|
1412
1420
|
scriptNotes: z3.string().optional().describe('Opcional. Notas/restricciones para el guion (la IA las usa al redactar). Ej. "No mencionar la marca X", "Tono divertido, no cl\xEDnico", "Mencionar 2x1". NO es el guion literal \u2014 para editar el guion palabra-por-palabra, pasa `pauseForVideoScriptReview: true` y usa get_video_script / approve_video_script tras la pausa.'),
|
|
1413
1421
|
pauseForVideoScriptReview: z3.boolean().optional().describe("Opcional. Si true, la tarea pausa despu\xE9s de generar el guion y antes de renderizar. \xDAsalo cuando el usuario quiera editar el guion antes de gastar cr\xE9ditos en el video. Tras la pausa: get_video_script para leerlo, approve_video_script para aprobar (con o sin ediciones). Default false."),
|
|
@@ -1439,6 +1447,7 @@ var ImageInput = z3.object({
|
|
|
1439
1447
|
compareAtPrice: z3.string().optional().describe('Opcional. Precio "antes" de la oferta \u2014 solo para im\xE1genes con descuento. Ej. "$129.000" cuando price="$89.900". \xDAtil con creativeArchetype="offer_value_prop".'),
|
|
1440
1448
|
offer: CreativeRefs.offer,
|
|
1441
1449
|
productContext: CreativeRefs.productContext,
|
|
1450
|
+
productLabel: CreativeRefs.productLabel,
|
|
1442
1451
|
isCombo: CreativeRefs.isCombo,
|
|
1443
1452
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1444
1453
|
});
|
|
@@ -1451,6 +1460,7 @@ var LandingInput = z3.object({
|
|
|
1451
1460
|
price: CreativeRefs.price,
|
|
1452
1461
|
offer: CreativeRefs.offer,
|
|
1453
1462
|
productContext: CreativeRefs.productContext,
|
|
1463
|
+
productLabel: CreativeRefs.productLabel,
|
|
1454
1464
|
paymentMethods: z3.string().optional().describe('Opcional. M\xE9todos de pago a mostrar (lista libre). Ej. "Tarjeta, PSE, Nequi, Contraentrega". Si se omite, la IA usa los m\xE1s comunes del pa\xEDs.'),
|
|
1455
1465
|
isCombo: CreativeRefs.isCombo,
|
|
1456
1466
|
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
@@ -1630,7 +1640,10 @@ var MetaInput = z6.object({
|
|
|
1630
1640
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
1631
1641
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
1632
1642
|
* (Meta's primary-text limit). */
|
|
1633
|
-
primaryText: z6.string().trim().min(1).max(500).optional()
|
|
1643
|
+
primaryText: z6.string().trim().min(1).max(500).optional(),
|
|
1644
|
+
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
1645
|
+
* (wa.me) instead of a landing page. */
|
|
1646
|
+
phoneNumber: z6.string().min(8).max(32).optional()
|
|
1634
1647
|
});
|
|
1635
1648
|
var TikTokInput = z6.object({
|
|
1636
1649
|
taskId: z6.string().min(1),
|
|
@@ -1666,7 +1679,7 @@ function registerPublishTools(server, api) {
|
|
|
1666
1679
|
throw translateError(e);
|
|
1667
1680
|
}
|
|
1668
1681
|
});
|
|
1669
|
-
server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en Meta Ads Manager. Use `list_meta_ad_accounts` antes para un adAccountId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 si la tarea gener\xF3 3 \xE1ngulos y el usuario solo quiere los #0 y #2, pasa [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario en USD, ej. 25), `targetCountries` (c\xF3digos ISO-2, m\xE1x 1 para Meta), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. Despu\xE9s de publicar usa `get_meta_campaign_status` para chequear el estado.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, ...rest }) => {
|
|
1682
|
+
server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde la tarea. La campa\xF1a se crea SIEMPRE en estado PAUSED \u2014 el usuario la activa manualmente en Meta Ads Manager. Use `list_meta_ad_accounts` antes para un adAccountId v\xE1lido. `pixelId` opcional pero recomendado para tracking. `angleIndexes` (opcional): selecciona qu\xE9 \xE1ngulos publicar \u2014 si la tarea gener\xF3 3 \xE1ngulos y el usuario solo quiere los #0 y #2, pasa [0,2]; omitir = publicar todos. `campaignConfig` (opcional): `dailyBudgetUsd` (presupuesto diario en USD, ej. 25), `targetCountries` (c\xF3digos ISO-2, m\xE1x 1 para Meta), `campaignName` (m\xE1x 100 chars). `primaryText` (opcional, m\xE1x 500 chars): override del copy del anuncio \u2014 solo \xFAsalo si el usuario te pas\xF3 un texto espec\xEDfico; si no, ProductMaker usa el copy auto-generado de la tarea. `phoneNumber` (opcional): si el usuario quiere que el bot\xF3n del anuncio abra un chat de WhatsApp en vez de una landing, pasa el n\xFAmero (con c\xF3digo de pa\xEDs, ej. +573001234567) \u2014 no hace falta `landingUrl` en ese caso. Despu\xE9s de publicar usa `get_meta_campaign_status` para chequear el estado.", MetaInput.shape, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
|
|
1670
1683
|
try {
|
|
1671
1684
|
const body = {
|
|
1672
1685
|
...rest
|
|
@@ -1674,6 +1687,16 @@ function registerPublishTools(server, api) {
|
|
|
1674
1687
|
if (campaignConfig) {
|
|
1675
1688
|
body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
|
|
1676
1689
|
}
|
|
1690
|
+
if (phoneNumber) {
|
|
1691
|
+
const overrides = isRecord(body.campaignOverrides) ? body.campaignOverrides : {};
|
|
1692
|
+
body.campaignOverrides = {
|
|
1693
|
+
...overrides,
|
|
1694
|
+
destination: {
|
|
1695
|
+
type: "whatsapp",
|
|
1696
|
+
phoneNumber
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1677
1700
|
if (primaryText) body.primaryText = primaryText;
|
|
1678
1701
|
const r = await api.postJson(`/v1/facebook/campaigns/from-task/${encodeURIComponent(taskId)}`, body);
|
|
1679
1702
|
return asText({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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.",
|