@productmaker/mcp 1.1.1 → 1.2.1
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 +34 -11
- package/dist/stdio.js +34 -11
- 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 = "1.
|
|
116
|
+
var MCP_VERSION = "1.2.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,
|
|
@@ -1181,7 +1185,8 @@ var CreateProductTaskInput = z2.object({
|
|
|
1181
1185
|
"video",
|
|
1182
1186
|
"image"
|
|
1183
1187
|
])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
|
|
1184
|
-
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.")
|
|
1188
|
+
competitorScanEnabled: z2.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
|
|
1189
|
+
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.")
|
|
1185
1190
|
}).strict();
|
|
1186
1191
|
var GetTaskStatusInput = z2.object({
|
|
1187
1192
|
taskId: z2.string().min(1),
|
|
@@ -1286,6 +1291,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1286
1291
|
"options.angleNarratives": input.angleNarratives,
|
|
1287
1292
|
"options.selectedOutputs": input.outputs,
|
|
1288
1293
|
"options.competitorScanEnabled": input.competitorScanEnabled,
|
|
1294
|
+
"options.brandId": input.brandId,
|
|
1289
1295
|
url: input.productUrl
|
|
1290
1296
|
});
|
|
1291
1297
|
const r = await api.postMultipart("/v1/ingest", {
|
|
@@ -1434,7 +1440,8 @@ var CreativeRefs = {
|
|
|
1434
1440
|
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.'),
|
|
1435
1441
|
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.'),
|
|
1436
1442
|
isCombo: z3.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
|
|
1437
|
-
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.")
|
|
1443
|
+
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."),
|
|
1444
|
+
brandId: z3.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 el creativo.")
|
|
1438
1445
|
};
|
|
1439
1446
|
var VideoInput = z3.object({
|
|
1440
1447
|
images: CreativeRefs.images,
|
|
@@ -1459,7 +1466,8 @@ var VideoInput = z3.object({
|
|
|
1459
1466
|
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.'),
|
|
1460
1467
|
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.'),
|
|
1461
1468
|
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."),
|
|
1462
|
-
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1469
|
+
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
1470
|
+
brandId: CreativeRefs.brandId
|
|
1463
1471
|
}).strict();
|
|
1464
1472
|
var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
|
|
1465
1473
|
var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
|
|
@@ -1489,7 +1497,8 @@ var ImageInput = z3.object({
|
|
|
1489
1497
|
productContext: CreativeRefs.productContext,
|
|
1490
1498
|
productLabel: CreativeRefs.productLabel,
|
|
1491
1499
|
isCombo: CreativeRefs.isCombo,
|
|
1492
|
-
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1500
|
+
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
1501
|
+
brandId: CreativeRefs.brandId
|
|
1493
1502
|
});
|
|
1494
1503
|
var LandingInput = z3.object({
|
|
1495
1504
|
images: CreativeRefs.images,
|
|
@@ -1503,7 +1512,8 @@ var LandingInput = z3.object({
|
|
|
1503
1512
|
productLabel: CreativeRefs.productLabel,
|
|
1504
1513
|
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.'),
|
|
1505
1514
|
isCombo: CreativeRefs.isCombo,
|
|
1506
|
-
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1515
|
+
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
1516
|
+
brandId: CreativeRefs.brandId
|
|
1507
1517
|
});
|
|
1508
1518
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
1509
1519
|
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) => {
|
|
@@ -1680,7 +1690,10 @@ var MetaInput = z6.object({
|
|
|
1680
1690
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
1681
1691
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
1682
1692
|
* (Meta's primary-text limit). */
|
|
1683
|
-
primaryText: z6.string().trim().min(1).max(500).optional()
|
|
1693
|
+
primaryText: z6.string().trim().min(1).max(500).optional(),
|
|
1694
|
+
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
1695
|
+
* (wa.me) instead of a landing page. */
|
|
1696
|
+
phoneNumber: z6.string().min(8).max(32).optional()
|
|
1684
1697
|
});
|
|
1685
1698
|
var TikTokInput = z6.object({
|
|
1686
1699
|
taskId: z6.string().min(1),
|
|
@@ -1716,7 +1729,7 @@ function registerPublishTools(server, api) {
|
|
|
1716
1729
|
throw translateError(e);
|
|
1717
1730
|
}
|
|
1718
1731
|
});
|
|
1719
|
-
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 }) => {
|
|
1732
|
+
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 }) => {
|
|
1720
1733
|
try {
|
|
1721
1734
|
const body = {
|
|
1722
1735
|
...rest
|
|
@@ -1724,6 +1737,16 @@ function registerPublishTools(server, api) {
|
|
|
1724
1737
|
if (campaignConfig) {
|
|
1725
1738
|
body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
|
|
1726
1739
|
}
|
|
1740
|
+
if (phoneNumber) {
|
|
1741
|
+
const overrides = isRecord(body.campaignOverrides) ? body.campaignOverrides : {};
|
|
1742
|
+
body.campaignOverrides = {
|
|
1743
|
+
...overrides,
|
|
1744
|
+
destination: {
|
|
1745
|
+
type: "whatsapp",
|
|
1746
|
+
phoneNumber
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1727
1750
|
if (primaryText) body.primaryText = primaryText;
|
|
1728
1751
|
const r = await api.postJson(`/v1/facebook/campaigns/from-task/${encodeURIComponent(taskId)}`, body);
|
|
1729
1752
|
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 = "1.
|
|
113
|
+
var MCP_VERSION = "1.2.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,
|
|
@@ -1137,7 +1141,8 @@ var CreateProductTaskInput = z2.object({
|
|
|
1137
1141
|
"video",
|
|
1138
1142
|
"image"
|
|
1139
1143
|
])).min(2).max(3).optional().describe('Opcional. Qu\xE9 outputs generar. Default: todos ["landing","video","image"]. Para 2 outputs: ["landing","image"] (sin video), ["video","image"] (sin landing), ["landing","video"] (sin imagen). Para 1 solo output, usa generate_landing / generate_video_creative / generate_image_creatives.'),
|
|
1140
|
-
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.")
|
|
1144
|
+
competitorScanEnabled: z2.boolean().optional().describe("Opcional. Si true (default), el sistema analiza ads de competencia en Meta Ad Library y enriquece los \xE1ngulos generados con hooks y painPoints validados por el mercado (\u2248$0.20-0.48 extra por task \u2014 var\xEDa con cu\xE1ntas transcripciones de video se capturen). Si false, el pipeline genera \xE1ngulos sin esa se\xF1al \u2014 usa esto SOLO si el usuario ya tiene su propia investigaci\xF3n de mercado o quiere ahorrar costos en productos muy nicho sin presencia en Meta. Default true."),
|
|
1145
|
+
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.")
|
|
1141
1146
|
}).strict();
|
|
1142
1147
|
var GetTaskStatusInput = z2.object({
|
|
1143
1148
|
taskId: z2.string().min(1),
|
|
@@ -1242,6 +1247,7 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
1242
1247
|
"options.angleNarratives": input.angleNarratives,
|
|
1243
1248
|
"options.selectedOutputs": input.outputs,
|
|
1244
1249
|
"options.competitorScanEnabled": input.competitorScanEnabled,
|
|
1250
|
+
"options.brandId": input.brandId,
|
|
1245
1251
|
url: input.productUrl
|
|
1246
1252
|
});
|
|
1247
1253
|
const r = await api.postMultipart("/v1/ingest", {
|
|
@@ -1390,7 +1396,8 @@ var CreativeRefs = {
|
|
|
1390
1396
|
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.'),
|
|
1391
1397
|
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.'),
|
|
1392
1398
|
isCombo: z3.boolean().optional().describe("Opcional. Si true, el creativo presenta el producto como combo/pack. Default false."),
|
|
1393
|
-
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.")
|
|
1399
|
+
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."),
|
|
1400
|
+
brandId: z3.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 el creativo.")
|
|
1394
1401
|
};
|
|
1395
1402
|
var VideoInput = z3.object({
|
|
1396
1403
|
images: CreativeRefs.images,
|
|
@@ -1415,7 +1422,8 @@ var VideoInput = z3.object({
|
|
|
1415
1422
|
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.'),
|
|
1416
1423
|
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.'),
|
|
1417
1424
|
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."),
|
|
1418
|
-
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1425
|
+
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
1426
|
+
brandId: CreativeRefs.brandId
|
|
1419
1427
|
}).strict();
|
|
1420
1428
|
var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
|
|
1421
1429
|
var archetypeList = ImageCreativeArchetypeEnum.options.join(" | ");
|
|
@@ -1445,7 +1453,8 @@ var ImageInput = z3.object({
|
|
|
1445
1453
|
productContext: CreativeRefs.productContext,
|
|
1446
1454
|
productLabel: CreativeRefs.productLabel,
|
|
1447
1455
|
isCombo: CreativeRefs.isCombo,
|
|
1448
|
-
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1456
|
+
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
1457
|
+
brandId: CreativeRefs.brandId
|
|
1449
1458
|
});
|
|
1450
1459
|
var LandingInput = z3.object({
|
|
1451
1460
|
images: CreativeRefs.images,
|
|
@@ -1459,7 +1468,8 @@ var LandingInput = z3.object({
|
|
|
1459
1468
|
productLabel: CreativeRefs.productLabel,
|
|
1460
1469
|
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.'),
|
|
1461
1470
|
isCombo: CreativeRefs.isCombo,
|
|
1462
|
-
competitorScanEnabled: CreativeRefs.competitorScanEnabled
|
|
1471
|
+
competitorScanEnabled: CreativeRefs.competitorScanEnabled,
|
|
1472
|
+
brandId: CreativeRefs.brandId
|
|
1463
1473
|
});
|
|
1464
1474
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
1465
1475
|
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) => {
|
|
@@ -1636,7 +1646,10 @@ var MetaInput = z6.object({
|
|
|
1636
1646
|
/** Override the ad's primary text (body copy). If omitted, ProductMaker
|
|
1637
1647
|
* uses the auto-generated copy attached to the task. Capped at 500 chars
|
|
1638
1648
|
* (Meta's primary-text limit). */
|
|
1639
|
-
primaryText: z6.string().trim().min(1).max(500).optional()
|
|
1649
|
+
primaryText: z6.string().trim().min(1).max(500).optional(),
|
|
1650
|
+
/** Optional WhatsApp number. When set, the ad button opens a WhatsApp chat
|
|
1651
|
+
* (wa.me) instead of a landing page. */
|
|
1652
|
+
phoneNumber: z6.string().min(8).max(32).optional()
|
|
1640
1653
|
});
|
|
1641
1654
|
var TikTokInput = z6.object({
|
|
1642
1655
|
taskId: z6.string().min(1),
|
|
@@ -1672,7 +1685,7 @@ function registerPublishTools(server, api) {
|
|
|
1672
1685
|
throw translateError(e);
|
|
1673
1686
|
}
|
|
1674
1687
|
});
|
|
1675
|
-
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 }) => {
|
|
1688
|
+
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 }) => {
|
|
1676
1689
|
try {
|
|
1677
1690
|
const body = {
|
|
1678
1691
|
...rest
|
|
@@ -1680,6 +1693,16 @@ function registerPublishTools(server, api) {
|
|
|
1680
1693
|
if (campaignConfig) {
|
|
1681
1694
|
body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
|
|
1682
1695
|
}
|
|
1696
|
+
if (phoneNumber) {
|
|
1697
|
+
const overrides = isRecord(body.campaignOverrides) ? body.campaignOverrides : {};
|
|
1698
|
+
body.campaignOverrides = {
|
|
1699
|
+
...overrides,
|
|
1700
|
+
destination: {
|
|
1701
|
+
type: "whatsapp",
|
|
1702
|
+
phoneNumber
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1683
1706
|
if (primaryText) body.primaryText = primaryText;
|
|
1684
1707
|
const r = await api.postJson(`/v1/facebook/campaigns/from-task/${encodeURIComponent(taskId)}`, body);
|
|
1685
1708
|
return asText({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
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.",
|