@productmaker/mcp 2.9.2 → 2.9.4

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 CHANGED
@@ -153,7 +153,7 @@ var ApiClient = class {
153
153
  };
154
154
 
155
155
  // src/index.ts
156
- var MCP_VERSION = "2.9.2";
156
+ var MCP_VERSION = "2.9.4";
157
157
 
158
158
  // src/meta-upload-app.ts
159
159
  import { readFileSync } from "fs";
@@ -8052,7 +8052,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
8052
8052
  {
8053
8053
  name: "publish_creatives_to_meta",
8054
8054
  confirmation: "external_mutation",
8055
- schemaHash: "59b3d6d5ce25812fee3382b0b1c0cf5e07beb1063048dd0a0d0633686cf29da5"
8055
+ schemaHash: "f712fe12baee19de6ac5ed94b23de630247a345ed13c3a431a647d421480280f"
8056
8056
  },
8057
8057
  {
8058
8058
  name: "publish_to_meta",
@@ -8915,7 +8915,7 @@ var CODE_MESSAGES = {
8915
8915
  META_TOKEN_EXPIRED: "Your Meta session expired. Reconnect Facebook at https://productmaker.app/integrations.",
8916
8916
  META_ACCOUNT_RESTRICTED: "Your Meta Ads account is temporarily restricted from creating ads. Check the notices at https://business.facebook.com and complete the required verification.",
8917
8917
  META_CAPABILITY_MISSING: "The Facebook app lacks required permissions for this ad account. Reconnect Facebook at https://productmaker.app/integrations and accept ALL requested permissions (including business_management).",
8918
- META_PERMISSION_DENIED: "The Facebook connection lacks sufficient permissions. Reconnect it at https://productmaker.app/integrations and accept all permissions.",
8918
+ META_PERMISSION_DENIED: "Meta denied write access to the selected ad account. Ask a Business Manager admin to grant this user Advertiser role or higher for that account, then reconnect Facebook at https://productmaker.app/integrations and accept every requested permission, including ads_management.",
8919
8919
  META_PIXEL_NOT_FOUND: "Pixel not found or not associated with the ad account. Verify the `pixelId` at https://business.facebook.com/events_manager.",
8920
8920
  META_OBJECT_NOT_FOUND: "The requested object (campaign/ad set/ad) does not exist in Meta. Verify the ID or create a new one.",
8921
8921
  META_AI_DISCLOSURE_REQUIRED: "Meta rejected the ad for undisclosed AI content. The system adds the disclosure automatically; if it persists, edit the copy to avoid language Meta flags as misleading.",
@@ -10901,10 +10901,10 @@ var GroupedCreative = z52.object({
10901
10901
  cta: MetaCtaType.optional(),
10902
10902
  creativeId: z52.string().min(1).optional()
10903
10903
  });
10904
- var GroupedCampaignConfig = z52.object({
10904
+ var GroupedCampaignConfigBase = z52.object({
10905
10905
  campaignType: MetaCampaignType.default("sales"),
10906
10906
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
10907
- budgetMode: MetaBudgetMode.default("campaign"),
10907
+ budgetMode: MetaBudgetMode.optional(),
10908
10908
  campaignDailyBudget: z52.number().int().positive().optional(),
10909
10909
  adSets: z52.array(z52.object({
10910
10910
  tempId: z52.string().min(1).max(80),
@@ -10930,8 +10930,17 @@ var GroupedCampaignConfig = z52.object({
10930
10930
  ]),
10931
10931
  saveAsDefault: z52.boolean().default(false),
10932
10932
  budgetPolicy: MetaBudgetPolicy.optional()
10933
- }).superRefine((value, ctx) => {
10934
- if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10933
+ });
10934
+ function resolveBudgetMode(value) {
10935
+ if (value.budgetMode) return value.budgetMode;
10936
+ if (value.campaignDailyBudget !== void 0) return "campaign";
10937
+ if (value.adSets.every((adSet) => adSet.dailyBudget !== void 0)) return "ad_set";
10938
+ return "campaign";
10939
+ }
10940
+ __name(resolveBudgetMode, "resolveBudgetMode");
10941
+ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) => {
10942
+ const budgetMode = resolveBudgetMode(value);
10943
+ if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10935
10944
  ctx.addIssue({
10936
10945
  code: z52.ZodIssueCode.custom,
10937
10946
  path: [
@@ -10940,7 +10949,7 @@ var GroupedCampaignConfig = z52.object({
10940
10949
  message: "campaignDailyBudget is required when budgetMode is campaign"
10941
10950
  });
10942
10951
  }
10943
- if (value.budgetMode === "ad_set") {
10952
+ if (budgetMode === "ad_set") {
10944
10953
  value.adSets.forEach((adSet, index) => {
10945
10954
  if (adSet.dailyBudget === void 0) {
10946
10955
  ctx.addIssue({
@@ -10955,7 +10964,10 @@ var GroupedCampaignConfig = z52.object({
10955
10964
  }
10956
10965
  });
10957
10966
  }
10958
- });
10967
+ }).transform((value) => ({
10968
+ ...value,
10969
+ budgetMode: resolveBudgetMode(value)
10970
+ }));
10959
10971
  var PublishCreativesToMetaInputBase = z52.object({
10960
10972
  creatives: z52.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10961
10973
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
@@ -10973,7 +10985,7 @@ var PublishCreativesToMetaInputBase = z52.object({
10973
10985
  });
10974
10986
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
10975
10987
  const destination = value.campaignConfig?.destination;
10976
- if (destination?.type !== "whatsapp" && !value.landingUrl) {
10988
+ if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
10977
10989
  ctx.addIssue({
10978
10990
  code: z52.ZodIssueCode.custom,
10979
10991
  path: [
@@ -10982,7 +10994,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10982
10994
  message: "landingUrl is required unless campaignConfig.destination.type is whatsapp"
10983
10995
  });
10984
10996
  }
10985
- if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
10997
+ if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
10986
10998
  ctx.addIssue({
10987
10999
  code: z52.ZodIssueCode.custom,
10988
11000
  path: [
@@ -10993,6 +11005,15 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10993
11005
  message: "campaignConfig.destination.landingUrl must match landingUrl"
10994
11006
  });
10995
11007
  }
11008
+ }).transform((value) => {
11009
+ const destination = value.campaignConfig?.destination;
11010
+ if (destination?.type === "landing" && !value.landingUrl) {
11011
+ return {
11012
+ ...value,
11013
+ landingUrl: destination.landingUrl
11014
+ };
11015
+ }
11016
+ return value;
10996
11017
  });
10997
11018
  var PresignUploadResponseSchema = z52.object({
10998
11019
  uploadUrl: z52.string().url().refine((value) => {
@@ -11104,7 +11125,7 @@ function registerPublishTools(server, api, opts = {}) {
11104
11125
  throw translateError(e);
11105
11126
  }
11106
11127
  });
11107
- server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo para reintento y el TTL los limpia. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
11128
+ server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo durante el TTL para reintento: nunca afirmes que una sesi\xF3n no expira. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
11108
11129
  try {
11109
11130
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
11110
11131
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -11365,8 +11386,7 @@ function uploadSessionText(result) {
11365
11386
  text: [
11366
11387
  `### Prepara tu ${mediaLabel} en ProductMaker`,
11367
11388
  `Usa primero el App inline de este chat. Si tu host no soporta el bridge de archivos, usa este fallback: [Abrir carga segura](${result.uploadPageUrl})`,
11368
- `Puedes seleccionar hasta ${result.maxFiles} ${result.mediaKind === "mixed" ? "archivos" : "archivo"}. Cuando termine, escribe **\u201Clisto\u201D** en este chat para que ProductMaker contin\xFAe con Meta.`,
11369
- `sessionId: \`${result.sessionId}\``,
11389
+ `Puedes seleccionar hasta ${result.maxFiles} ${result.mediaKind === "mixed" ? "archivos" : "archivo"}. Cuando termines, pulsa **OK** dentro del App para que ProductMaker contin\xFAe en este chat.`,
11370
11390
  `Este enlace expira: ${result.expiresAt}`
11371
11391
  ].join("\n\n")
11372
11392
  }
@@ -11377,7 +11397,7 @@ __name(uploadSessionText, "uploadSessionText");
11377
11397
  function registerMetaUploadTools(server, api) {
11378
11398
  server.registerTool("request_meta_media_upload", {
11379
11399
  title: "Solicitar carga segura de Meta",
11380
- description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El flujo principal es el MCP App inline; el enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. Despu\xE9s conserva el `sessionId` y llama `check_meta_media_upload`. Esta herramienta no publica ni consume cr\xE9ditos.",
11400
+ description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Esta es la ruta DIRECTA para peticiones puntuales como \u2018sube esta imagen/video a Meta Ads\u2019 o \u2018publica este creativo\u2019; no conviertas esa intenci\xF3n en una generaci\xF3n completa de producto. No llames `guide_dropshipping_launch` para esta operaci\xF3n puntual. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El App es exclusivamente para cargar archivos: nunca debe pedir ni mostrar portafolio, cuenta publicitaria, p\xE1gina, IDs o JSON. El enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. El usuario pulsa OK cuando termina; el App inserta en el chat los asset_id verificados para continuar la petici\xF3n anterior. Luego llama `list_meta_destinations` y pregunta en el chat qu\xE9 `portfolioId`, `adAccountId` y `pageId` desea usar. No muestres tokens, URLs temporales, `sessionId` ni un bloque JSON. Esta herramienta no publica ni consume cr\xE9ditos.",
11381
11401
  inputSchema: MetaUploadSessionToolInputShape,
11382
11402
  outputSchema: MetaUploadSessionResponse2,
11383
11403
  _meta: {
@@ -11410,7 +11430,7 @@ function registerMetaUploadTools(server, api) {
11410
11430
  }
11411
11431
  });
11412
11432
  server.registerTool("check_meta_media_upload", {
11413
- description: 'Comprueba si el usuario ya termin\xF3 de subir desde el enlace de ProductMaker. Si status=uploaded, devuelve `assets[]`; crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como el tipo del creativo, hasta diez en el batch. Si la carga fue parcial, publica \xFAnicamente los assets que `assets[]` reporta como subidos. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
11433
+ description: 'Comprueba si el usuario ya termin\xF3 de subir desde el App o enlace de ProductMaker. Si status=uploaded, conserva `assets[]` privadamente, llama `list_meta_destinations` y pregunta en lenguaje natural dentro del chat qu\xE9 portafolio, cuenta publicitaria y p\xE1gina desea usar; nunca muestres `sessionId`, `stagingAssetId`, IDs t\xE9cnicos ni un bloque JSON. Despu\xE9s crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como tipo, hasta diez en el batch. Si la carga fue parcial, usa \xFAnicamente los assets confirmados. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
11414
11434
  inputSchema: MetaUploadStatusInput.shape,
11415
11435
  _meta: {
11416
11436
  "openai/widgetAccessible": true
@@ -12365,20 +12385,21 @@ var CostingInputSchema = z59.object({
12365
12385
  ]
12366
12386
  });
12367
12387
  });
12388
+ var FiniteCostingNumber = z59.number().finite();
12368
12389
  var CostingResultSchema = z59.object({
12369
12390
  country: z59.string().min(2).max(3),
12370
- deliveryRate: z59.number().finite(),
12371
- adjustedShippingCost: z59.number().finite(),
12372
- adjustedCpa: z59.number().finite(),
12373
- totalCost: z59.number().finite(),
12374
- suggestedPrice: z59.number().finite(),
12375
- baseCommercialPrice: z59.number().finite(),
12376
- minimumSafePrice: z59.number().finite(),
12377
- commercialPrice: z59.number().finite(),
12378
- comparisonPrice: z59.number().finite(),
12379
- estimatedProfit: z59.number().finite(),
12380
- actualMargin: z59.number().finite(),
12381
- breakEvenAdsManagerCpa: z59.number().finite(),
12391
+ deliveryRate: FiniteCostingNumber,
12392
+ adjustedShippingCost: FiniteCostingNumber,
12393
+ adjustedCpa: FiniteCostingNumber,
12394
+ totalCost: FiniteCostingNumber,
12395
+ suggestedPrice: FiniteCostingNumber,
12396
+ baseCommercialPrice: FiniteCostingNumber,
12397
+ minimumSafePrice: FiniteCostingNumber,
12398
+ commercialPrice: FiniteCostingNumber,
12399
+ comparisonPrice: FiniteCostingNumber,
12400
+ estimatedProfit: FiniteCostingNumber,
12401
+ actualMargin: FiniteCostingNumber,
12402
+ breakEvenAdsManagerCpa: FiniteCostingNumber,
12382
12403
  wasPriceAutoAdjusted: z59.boolean(),
12383
12404
  profitabilityStatus: z59.literal("healthy")
12384
12405
  });
@@ -12467,13 +12488,13 @@ function registerDropshippingProjectTools(server, api) {
12467
12488
  throw translateError(error);
12468
12489
  }
12469
12490
  });
12470
- server.tool("guide_dropshipping_launch", "Gu\xEDa read-only del lanzamiento. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, async (input) => {
12491
+ server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_meta_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, async (input) => {
12471
12492
  try {
12472
12493
  if (!input.projectId) {
12473
12494
  return asText({
12474
12495
  mode: "choose",
12475
12496
  guided: "Usa start_dropshipping_project cuando el usuario quiera encontrar un producto y dejarlo listo para publicar.",
12476
- direct: "En modo directo usa cost_product, create_product_task, publish_to_shopify o publish_to_meta cuando pidi\xF3 una operaci\xF3n espec\xEDfica.",
12497
+ direct: "En modo directo usa la herramienta puntual. Para subir/publicar un creativo propio en Meta: request_meta_media_upload \u2192 list_meta_destinations \u2192 publish_creatives_to_meta. No crees un proyecto guiado.",
12477
12498
  confirmation: "Antes de acciones pagadas o externas, muestra el estimado y pide confirmaci\xF3n.",
12478
12499
  safety: "Las campa\xF1as Meta quedan PAUSED; el usuario las activa manualmente en Ads Manager."
12479
12500
  });
package/dist/stdio.js CHANGED
@@ -150,7 +150,7 @@ var ApiClient = class {
150
150
  };
151
151
 
152
152
  // src/index.ts
153
- var MCP_VERSION = "2.9.2";
153
+ var MCP_VERSION = "2.9.4";
154
154
 
155
155
  // src/meta-upload-app.ts
156
156
  import { readFileSync } from "fs";
@@ -8049,7 +8049,7 @@ var PRODUCTMAKER_MCP_CAPABILITIES = [
8049
8049
  {
8050
8050
  name: "publish_creatives_to_meta",
8051
8051
  confirmation: "external_mutation",
8052
- schemaHash: "59b3d6d5ce25812fee3382b0b1c0cf5e07beb1063048dd0a0d0633686cf29da5"
8052
+ schemaHash: "f712fe12baee19de6ac5ed94b23de630247a345ed13c3a431a647d421480280f"
8053
8053
  },
8054
8054
  {
8055
8055
  name: "publish_to_meta",
@@ -8912,7 +8912,7 @@ var CODE_MESSAGES = {
8912
8912
  META_TOKEN_EXPIRED: "Your Meta session expired. Reconnect Facebook at https://productmaker.app/integrations.",
8913
8913
  META_ACCOUNT_RESTRICTED: "Your Meta Ads account is temporarily restricted from creating ads. Check the notices at https://business.facebook.com and complete the required verification.",
8914
8914
  META_CAPABILITY_MISSING: "The Facebook app lacks required permissions for this ad account. Reconnect Facebook at https://productmaker.app/integrations and accept ALL requested permissions (including business_management).",
8915
- META_PERMISSION_DENIED: "The Facebook connection lacks sufficient permissions. Reconnect it at https://productmaker.app/integrations and accept all permissions.",
8915
+ META_PERMISSION_DENIED: "Meta denied write access to the selected ad account. Ask a Business Manager admin to grant this user Advertiser role or higher for that account, then reconnect Facebook at https://productmaker.app/integrations and accept every requested permission, including ads_management.",
8916
8916
  META_PIXEL_NOT_FOUND: "Pixel not found or not associated with the ad account. Verify the `pixelId` at https://business.facebook.com/events_manager.",
8917
8917
  META_OBJECT_NOT_FOUND: "The requested object (campaign/ad set/ad) does not exist in Meta. Verify the ID or create a new one.",
8918
8918
  META_AI_DISCLOSURE_REQUIRED: "Meta rejected the ad for undisclosed AI content. The system adds the disclosure automatically; if it persists, edit the copy to avoid language Meta flags as misleading.",
@@ -10857,10 +10857,10 @@ var GroupedCreative = z52.object({
10857
10857
  cta: MetaCtaType.optional(),
10858
10858
  creativeId: z52.string().min(1).optional()
10859
10859
  });
10860
- var GroupedCampaignConfig = z52.object({
10860
+ var GroupedCampaignConfigBase = z52.object({
10861
10861
  campaignType: MetaCampaignType.default("sales"),
10862
10862
  optimizationMode: MetaOptimizationMode.default("advantage_plus"),
10863
- budgetMode: MetaBudgetMode.default("campaign"),
10863
+ budgetMode: MetaBudgetMode.optional(),
10864
10864
  campaignDailyBudget: z52.number().int().positive().optional(),
10865
10865
  adSets: z52.array(z52.object({
10866
10866
  tempId: z52.string().min(1).max(80),
@@ -10886,8 +10886,17 @@ var GroupedCampaignConfig = z52.object({
10886
10886
  ]),
10887
10887
  saveAsDefault: z52.boolean().default(false),
10888
10888
  budgetPolicy: MetaBudgetPolicy.optional()
10889
- }).superRefine((value, ctx) => {
10890
- if (value.budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10889
+ });
10890
+ function resolveBudgetMode(value) {
10891
+ if (value.budgetMode) return value.budgetMode;
10892
+ if (value.campaignDailyBudget !== void 0) return "campaign";
10893
+ if (value.adSets.every((adSet) => adSet.dailyBudget !== void 0)) return "ad_set";
10894
+ return "campaign";
10895
+ }
10896
+ __name(resolveBudgetMode, "resolveBudgetMode");
10897
+ var GroupedCampaignConfig = GroupedCampaignConfigBase.superRefine((value, ctx) => {
10898
+ const budgetMode = resolveBudgetMode(value);
10899
+ if (budgetMode === "campaign" && value.campaignDailyBudget === void 0) {
10891
10900
  ctx.addIssue({
10892
10901
  code: z52.ZodIssueCode.custom,
10893
10902
  path: [
@@ -10896,7 +10905,7 @@ var GroupedCampaignConfig = z52.object({
10896
10905
  message: "campaignDailyBudget is required when budgetMode is campaign"
10897
10906
  });
10898
10907
  }
10899
- if (value.budgetMode === "ad_set") {
10908
+ if (budgetMode === "ad_set") {
10900
10909
  value.adSets.forEach((adSet, index) => {
10901
10910
  if (adSet.dailyBudget === void 0) {
10902
10911
  ctx.addIssue({
@@ -10911,7 +10920,10 @@ var GroupedCampaignConfig = z52.object({
10911
10920
  }
10912
10921
  });
10913
10922
  }
10914
- });
10923
+ }).transform((value) => ({
10924
+ ...value,
10925
+ budgetMode: resolveBudgetMode(value)
10926
+ }));
10915
10927
  var PublishCreativesToMetaInputBase = z52.object({
10916
10928
  creatives: z52.array(GroupedCreative).min(1).max(CAMPAIGN_LIMITS.MAX_CREATIVES),
10917
10929
  /** Exact Meta Business portfolio selected from `list_meta_destinations`. */
@@ -10929,7 +10941,7 @@ var PublishCreativesToMetaInputBase = z52.object({
10929
10941
  });
10930
10942
  var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((value, ctx) => {
10931
10943
  const destination = value.campaignConfig?.destination;
10932
- if (destination?.type !== "whatsapp" && !value.landingUrl) {
10944
+ if (destination?.type !== "whatsapp" && destination?.type !== "landing" && !value.landingUrl) {
10933
10945
  ctx.addIssue({
10934
10946
  code: z52.ZodIssueCode.custom,
10935
10947
  path: [
@@ -10938,7 +10950,7 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10938
10950
  message: "landingUrl is required unless campaignConfig.destination.type is whatsapp"
10939
10951
  });
10940
10952
  }
10941
- if (destination?.type === "landing" && destination.landingUrl !== value.landingUrl) {
10953
+ if (destination?.type === "landing" && value.landingUrl && destination.landingUrl !== value.landingUrl) {
10942
10954
  ctx.addIssue({
10943
10955
  code: z52.ZodIssueCode.custom,
10944
10956
  path: [
@@ -10949,6 +10961,15 @@ var PublishCreativesToMetaInput = PublishCreativesToMetaInputBase.superRefine((v
10949
10961
  message: "campaignConfig.destination.landingUrl must match landingUrl"
10950
10962
  });
10951
10963
  }
10964
+ }).transform((value) => {
10965
+ const destination = value.campaignConfig?.destination;
10966
+ if (destination?.type === "landing" && !value.landingUrl) {
10967
+ return {
10968
+ ...value,
10969
+ landingUrl: destination.landingUrl
10970
+ };
10971
+ }
10972
+ return value;
10952
10973
  });
10953
10974
  var PresignUploadResponseSchema = z52.object({
10954
10975
  uploadUrl: z52.string().url().refine((value) => {
@@ -11060,7 +11081,7 @@ function registerPublishTools(server, api, opts = {}) {
11060
11081
  throw translateError(e);
11061
11082
  }
11062
11083
  });
11063
- server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo para reintento y el TTL los limpia. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
11084
+ server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Antes llama `list_meta_destinations` y pide al usuario elegir el `portfolioId`, `adAccountId` y `pageId` exactos. Para archivos en la laptop con MCP remoto: llama `request_meta_media_upload`, espera al usuario y consulta `check_meta_media_upload`; por CADA entrada de `assets[]` crea un creativo `source.type="staged"`, con `type` igual a su `mediaKind`. Si la carga fue parcial, publica s\xF3lo los assets que ese estado confirm\xF3; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se suben temporalmente al CDN de ProductMaker y se eliminan despu\xE9s de publicar correctamente; si falla, quedan s\xF3lo durante el TTL para reintento: nunca afirmes que una sesi\xF3n no expira. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
11064
11085
  try {
11065
11086
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
11066
11087
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -11321,8 +11342,7 @@ function uploadSessionText(result) {
11321
11342
  text: [
11322
11343
  `### Prepara tu ${mediaLabel} en ProductMaker`,
11323
11344
  `Usa primero el App inline de este chat. Si tu host no soporta el bridge de archivos, usa este fallback: [Abrir carga segura](${result.uploadPageUrl})`,
11324
- `Puedes seleccionar hasta ${result.maxFiles} ${result.mediaKind === "mixed" ? "archivos" : "archivo"}. Cuando termine, escribe **\u201Clisto\u201D** en este chat para que ProductMaker contin\xFAe con Meta.`,
11325
- `sessionId: \`${result.sessionId}\``,
11345
+ `Puedes seleccionar hasta ${result.maxFiles} ${result.mediaKind === "mixed" ? "archivos" : "archivo"}. Cuando termines, pulsa **OK** dentro del App para que ProductMaker contin\xFAe en este chat.`,
11326
11346
  `Este enlace expira: ${result.expiresAt}`
11327
11347
  ].join("\n\n")
11328
11348
  }
@@ -11333,7 +11353,7 @@ __name(uploadSessionText, "uploadSessionText");
11333
11353
  function registerMetaUploadTools(server, api) {
11334
11354
  server.registerTool("request_meta_media_upload", {
11335
11355
  title: "Solicitar carga segura de Meta",
11336
- description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El flujo principal es el MCP App inline; el enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. Despu\xE9s conserva el `sessionId` y llama `check_meta_media_upload`. Esta herramienta no publica ni consume cr\xE9ditos.",
11356
+ description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Esta es la ruta DIRECTA para peticiones puntuales como \u2018sube esta imagen/video a Meta Ads\u2019 o \u2018publica este creativo\u2019; no conviertas esa intenci\xF3n en una generaci\xF3n completa de producto. No llames `guide_dropshipping_launch` para esta operaci\xF3n puntual. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El App es exclusivamente para cargar archivos: nunca debe pedir ni mostrar portafolio, cuenta publicitaria, p\xE1gina, IDs o JSON. El enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. El usuario pulsa OK cuando termina; el App inserta en el chat los asset_id verificados para continuar la petici\xF3n anterior. Luego llama `list_meta_destinations` y pregunta en el chat qu\xE9 `portfolioId`, `adAccountId` y `pageId` desea usar. No muestres tokens, URLs temporales, `sessionId` ni un bloque JSON. Esta herramienta no publica ni consume cr\xE9ditos.",
11337
11357
  inputSchema: MetaUploadSessionToolInputShape,
11338
11358
  outputSchema: MetaUploadSessionResponse2,
11339
11359
  _meta: {
@@ -11366,7 +11386,7 @@ function registerMetaUploadTools(server, api) {
11366
11386
  }
11367
11387
  });
11368
11388
  server.registerTool("check_meta_media_upload", {
11369
- description: 'Comprueba si el usuario ya termin\xF3 de subir desde el enlace de ProductMaker. Si status=uploaded, devuelve `assets[]`; crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como el tipo del creativo, hasta diez en el batch. Si la carga fue parcial, publica \xFAnicamente los assets que `assets[]` reporta como subidos. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
11389
+ description: 'Comprueba si el usuario ya termin\xF3 de subir desde el App o enlace de ProductMaker. Si status=uploaded, conserva `assets[]` privadamente, llama `list_meta_destinations` y pregunta en lenguaje natural dentro del chat qu\xE9 portafolio, cuenta publicitaria y p\xE1gina desea usar; nunca muestres `sessionId`, `stagingAssetId`, IDs t\xE9cnicos ni un bloque JSON. Despu\xE9s crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como tipo, hasta diez en el batch. Si la carga fue parcial, usa \xFAnicamente los assets confirmados. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
11370
11390
  inputSchema: MetaUploadStatusInput.shape,
11371
11391
  _meta: {
11372
11392
  "openai/widgetAccessible": true
@@ -12321,20 +12341,21 @@ var CostingInputSchema = z59.object({
12321
12341
  ]
12322
12342
  });
12323
12343
  });
12344
+ var FiniteCostingNumber = z59.number().finite();
12324
12345
  var CostingResultSchema = z59.object({
12325
12346
  country: z59.string().min(2).max(3),
12326
- deliveryRate: z59.number().finite(),
12327
- adjustedShippingCost: z59.number().finite(),
12328
- adjustedCpa: z59.number().finite(),
12329
- totalCost: z59.number().finite(),
12330
- suggestedPrice: z59.number().finite(),
12331
- baseCommercialPrice: z59.number().finite(),
12332
- minimumSafePrice: z59.number().finite(),
12333
- commercialPrice: z59.number().finite(),
12334
- comparisonPrice: z59.number().finite(),
12335
- estimatedProfit: z59.number().finite(),
12336
- actualMargin: z59.number().finite(),
12337
- breakEvenAdsManagerCpa: z59.number().finite(),
12347
+ deliveryRate: FiniteCostingNumber,
12348
+ adjustedShippingCost: FiniteCostingNumber,
12349
+ adjustedCpa: FiniteCostingNumber,
12350
+ totalCost: FiniteCostingNumber,
12351
+ suggestedPrice: FiniteCostingNumber,
12352
+ baseCommercialPrice: FiniteCostingNumber,
12353
+ minimumSafePrice: FiniteCostingNumber,
12354
+ commercialPrice: FiniteCostingNumber,
12355
+ comparisonPrice: FiniteCostingNumber,
12356
+ estimatedProfit: FiniteCostingNumber,
12357
+ actualMargin: FiniteCostingNumber,
12358
+ breakEvenAdsManagerCpa: FiniteCostingNumber,
12338
12359
  wasPriceAutoAdjusted: z59.boolean(),
12339
12360
  profitabilityStatus: z59.literal("healthy")
12340
12361
  });
@@ -12423,13 +12444,13 @@ function registerDropshippingProjectTools(server, api) {
12423
12444
  throw translateError(error);
12424
12445
  }
12425
12446
  });
12426
- server.tool("guide_dropshipping_launch", "Gu\xEDa read-only del lanzamiento. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, async (input) => {
12447
+ server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_meta_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, async (input) => {
12427
12448
  try {
12428
12449
  if (!input.projectId) {
12429
12450
  return asText({
12430
12451
  mode: "choose",
12431
12452
  guided: "Usa start_dropshipping_project cuando el usuario quiera encontrar un producto y dejarlo listo para publicar.",
12432
- direct: "En modo directo usa cost_product, create_product_task, publish_to_shopify o publish_to_meta cuando pidi\xF3 una operaci\xF3n espec\xEDfica.",
12453
+ direct: "En modo directo usa la herramienta puntual. Para subir/publicar un creativo propio en Meta: request_meta_media_upload \u2192 list_meta_destinations \u2192 publish_creatives_to_meta. No crees un proyecto guiado.",
12433
12454
  confirmation: "Antes de acciones pagadas o externas, muestra el estimado y pide confirmaci\xF3n.",
12434
12455
  safety: "Las campa\xF1as Meta quedan PAUSED; el usuario las activa manualmente en Ads Manager."
12435
12456
  });