@productmaker/mcp 0.10.0 → 0.11.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.
Files changed (3) hide show
  1. package/dist/http.js +361 -103
  2. package/dist/stdio.js +361 -103
  3. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -391,106 +391,6 @@ __name(sleep, "sleep");
391
391
 
392
392
  // src/errors.ts
393
393
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
394
- var McpErrorCode = {
395
- INVALID_API_KEY: "INVALID_API_KEY",
396
- REVOKED_API_KEY: "REVOKED_API_KEY",
397
- RATE_LIMITED: "RATE_LIMITED",
398
- INVALID_INPUT: "INVALID_INPUT",
399
- IMAGE_DOWNLOAD_FAILED: "IMAGE_DOWNLOAD_FAILED",
400
- IMAGE_TOO_LARGE: "IMAGE_TOO_LARGE",
401
- IMAGE_TYPE_UNSUPPORTED: "IMAGE_TYPE_UNSUPPORTED",
402
- IMAGE_PATH_NOT_FOUND: "IMAGE_PATH_NOT_FOUND",
403
- IMAGE_PATH_NOT_READABLE: "IMAGE_PATH_NOT_READABLE",
404
- IMAGE_PATH_NOT_A_FILE: "IMAGE_PATH_NOT_A_FILE",
405
- PATH_NOT_SUPPORTED_IN_HTTP_MODE: "PATH_NOT_SUPPORTED_IN_HTTP_MODE",
406
- TASK_NOT_FOUND: "TASK_NOT_FOUND",
407
- TASK_NOT_READY: "TASK_NOT_READY",
408
- CAMPAIGN_NOT_FOUND: "CAMPAIGN_NOT_FOUND",
409
- NO_SHOPIFY_SHOPS: "NO_SHOPIFY_SHOPS",
410
- NO_META_AD_ACCOUNTS: "NO_META_AD_ACCOUNTS",
411
- SHOPIFY_NOT_CONNECTED: "SHOPIFY_NOT_CONNECTED",
412
- META_CAPABILITY_MISSING: "META_CAPABILITY_MISSING",
413
- TIKTOK_NOT_CONNECTED: "TIKTOK_NOT_CONNECTED",
414
- UPSTREAM_TIMEOUT: "UPSTREAM_TIMEOUT",
415
- UPSTREAM_UNAVAILABLE: "UPSTREAM_UNAVAILABLE",
416
- INTERNAL: "INTERNAL"
417
- };
418
- var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
419
- McpErrorCode.INVALID_INPUT,
420
- McpErrorCode.INVALID_API_KEY,
421
- McpErrorCode.REVOKED_API_KEY,
422
- McpErrorCode.IMAGE_PATH_NOT_FOUND,
423
- McpErrorCode.IMAGE_PATH_NOT_READABLE,
424
- McpErrorCode.IMAGE_PATH_NOT_A_FILE,
425
- McpErrorCode.PATH_NOT_SUPPORTED_IN_HTTP_MODE,
426
- // The caller passed a campaignId that does not exist — semantically a
427
- // bad parameter, not an internal failure. Matches the spec contract
428
- // (mcp-server.spec.ts: "McpError INVALID_PARAMS code=CAMPAIGN_NOT_FOUND").
429
- McpErrorCode.CAMPAIGN_NOT_FOUND
430
- ]);
431
- function mcp(code, message, extra = {}) {
432
- const errCode = INVALID_PARAM_CODES.has(code) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
433
- return new McpError(errCode, message, {
434
- code,
435
- ...extra
436
- });
437
- }
438
- __name(mcp, "mcp");
439
- var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
440
- "META_CAPABILITY_MISSING",
441
- "NO_SHOPIFY_SHOPS",
442
- "NO_META_AD_ACCOUNTS",
443
- "SHOPIFY_NOT_CONNECTED",
444
- "TIKTOK_NOT_CONNECTED",
445
- "REVOKED_API_KEY",
446
- "CAMPAIGN_NOT_FOUND"
447
- ]);
448
- function isRecord(v) {
449
- return typeof v === "object" && v !== null && !Array.isArray(v);
450
- }
451
- __name(isRecord, "isRecord");
452
- function translateError(err) {
453
- if (err instanceof ResolveImageError) {
454
- return mcp(McpErrorCode[err.code], err.message);
455
- }
456
- if (err instanceof ApiHttpError) {
457
- const body = isRecord(err.body) ? err.body : {};
458
- if (typeof body.code === "string" && PASSTHROUGH_CODES.has(body.code)) {
459
- return mcp(body.code, String(body.message ?? body.code), {
460
- ...body.helpUrl ? {
461
- helpUrl: body.helpUrl
462
- } : {}
463
- });
464
- }
465
- if (err.status === 401) {
466
- return mcp(McpErrorCode.INVALID_API_KEY, "API key inv\xE1lida. Revisa tu PM_API_KEY en la configuraci\xF3n del cliente MCP.");
467
- }
468
- if (err.status === 429) {
469
- return mcp(McpErrorCode.RATE_LIMITED, "Demasiadas solicitudes. Espera unos segundos.", {
470
- retryAfterSeconds: Number(body.retryAfterSeconds ?? 30)
471
- });
472
- }
473
- if (err.status === 400) {
474
- return mcp(McpErrorCode.INVALID_INPUT, typeof body.message === "string" ? body.message : "Entrada inv\xE1lida.");
475
- }
476
- if (err.status === 404) {
477
- const isTask = body.resource === "task" || /task/i.test(String(body.message ?? ""));
478
- return mcp(isTask ? McpErrorCode.TASK_NOT_FOUND : McpErrorCode.INVALID_INPUT, "Recurso no encontrado.");
479
- }
480
- if (err.status === 409) {
481
- return mcp(McpErrorCode.TASK_NOT_READY, "La tarea a\xFAn no est\xE1 lista para esta acci\xF3n.");
482
- }
483
- if (err.status >= 500) {
484
- return mcp(McpErrorCode.UPSTREAM_UNAVAILABLE, "El servicio Product Maker no respondi\xF3. Intenta de nuevo en un momento.");
485
- }
486
- return mcp(McpErrorCode.INTERNAL, `HTTP ${err.status}`);
487
- }
488
- if (err instanceof Error && /aborted|timeout/i.test(err.message)) {
489
- return mcp(McpErrorCode.UPSTREAM_TIMEOUT, "Tiempo de espera agotado.");
490
- }
491
- return mcp(McpErrorCode.INTERNAL, err instanceof Error ? err.message : "Error interno.");
492
- }
493
- __name(translateError, "translateError");
494
394
 
495
395
  // src/tools/shared.ts
496
396
  import { z } from "zod";
@@ -682,6 +582,10 @@ function asText(value) {
682
582
  };
683
583
  }
684
584
  __name(asText, "asText");
585
+ function isRecord(v) {
586
+ return typeof v === "object" && v !== null && !Array.isArray(v);
587
+ }
588
+ __name(isRecord, "isRecord");
685
589
  function escapeMdLabel(s) {
686
590
  return s.replace(/[\[\]\(\)\{\}`<>\r\n\\*_]/g, " ").replace(/\s+/g, " ").trim();
687
591
  }
@@ -718,6 +622,341 @@ async function submitCreative(api, opts) {
718
622
  }
719
623
  __name(submitCreative, "submitCreative");
720
624
 
625
+ // src/errors.ts
626
+ var McpErrorCode = {
627
+ // Auth / API key
628
+ INVALID_API_KEY: "INVALID_API_KEY",
629
+ REVOKED_API_KEY: "REVOKED_API_KEY",
630
+ // Rate limit / quota
631
+ RATE_LIMITED: "RATE_LIMITED",
632
+ // Input
633
+ INVALID_INPUT: "INVALID_INPUT",
634
+ // Image upload (MCP-local)
635
+ IMAGE_DOWNLOAD_FAILED: "IMAGE_DOWNLOAD_FAILED",
636
+ IMAGE_TOO_LARGE: "IMAGE_TOO_LARGE",
637
+ IMAGE_TYPE_UNSUPPORTED: "IMAGE_TYPE_UNSUPPORTED",
638
+ IMAGE_PATH_NOT_FOUND: "IMAGE_PATH_NOT_FOUND",
639
+ IMAGE_PATH_NOT_READABLE: "IMAGE_PATH_NOT_READABLE",
640
+ IMAGE_PATH_NOT_A_FILE: "IMAGE_PATH_NOT_A_FILE",
641
+ PATH_NOT_SUPPORTED_IN_HTTP_MODE: "PATH_NOT_SUPPORTED_IN_HTTP_MODE",
642
+ // Task lifecycle
643
+ TASK_NOT_FOUND: "TASK_NOT_FOUND",
644
+ TASK_NOT_READY: "TASK_NOT_READY",
645
+ // Connection presence
646
+ NO_SHOPIFY_SHOPS: "NO_SHOPIFY_SHOPS",
647
+ NO_META_AD_ACCOUNTS: "NO_META_AD_ACCOUNTS",
648
+ // Campaigns (shared Meta + TikTok)
649
+ CAMPAIGN_NOT_FOUND: "CAMPAIGN_NOT_FOUND",
650
+ CAMPAIGN_TASK_NOT_FOUND: "CAMPAIGN_TASK_NOT_FOUND",
651
+ CAMPAIGN_MISSING_LANDING_URL: "CAMPAIGN_MISSING_LANDING_URL",
652
+ CAMPAIGN_NO_CREATIVES: "CAMPAIGN_NO_CREATIVES",
653
+ CAMPAIGN_MISSING_THUMBNAILS: "CAMPAIGN_MISSING_THUMBNAILS",
654
+ // Shopify
655
+ SHOPIFY_NOT_CONNECTED: "SHOPIFY_NOT_CONNECTED",
656
+ SHOPIFY_TOKEN_EXPIRED: "SHOPIFY_TOKEN_EXPIRED",
657
+ SHOPIFY_SCOPE_UPGRADE_REQUIRED: "SHOPIFY_SCOPE_UPGRADE_REQUIRED",
658
+ SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
659
+ // Meta / Facebook Ads
660
+ META_NO_FB_PAGE: "META_NO_FB_PAGE",
661
+ META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
662
+ META_NO_ASSETS_READY: "META_NO_ASSETS_READY",
663
+ META_NO_THUMBNAIL_SOURCE: "META_NO_THUMBNAIL_SOURCE",
664
+ META_NO_LANDING_URL: "META_NO_LANDING_URL",
665
+ META_TASK_NOT_FOUND: "META_TASK_NOT_FOUND",
666
+ META_INVALID_DATA: "META_INVALID_DATA",
667
+ META_INVALID_AD_ACCOUNT: "META_INVALID_AD_ACCOUNT",
668
+ META_MISSING_PAYMENT: "META_MISSING_PAYMENT",
669
+ META_TOKEN_EXPIRED: "META_TOKEN_EXPIRED",
670
+ META_ACCOUNT_RESTRICTED: "META_ACCOUNT_RESTRICTED",
671
+ META_CAPABILITY_MISSING: "META_CAPABILITY_MISSING",
672
+ META_PERMISSION_DENIED: "META_PERMISSION_DENIED",
673
+ META_PIXEL_NOT_FOUND: "META_PIXEL_NOT_FOUND",
674
+ META_OBJECT_NOT_FOUND: "META_OBJECT_NOT_FOUND",
675
+ META_AI_DISCLOSURE_REQUIRED: "META_AI_DISCLOSURE_REQUIRED",
676
+ META_BID_STRATEGY_REQUIRES_AMOUNT: "META_BID_STRATEGY_REQUIRES_AMOUNT",
677
+ META_CBO_BUDGET_CONFLICT: "META_CBO_BUDGET_CONFLICT",
678
+ META_BUDGET_MODE_CONFLICT: "META_BUDGET_MODE_CONFLICT",
679
+ META_AD_SET_BUDGET_REQUIRED: "META_AD_SET_BUDGET_REQUIRED",
680
+ META_DAILY_BUDGET_TOO_HIGH: "META_DAILY_BUDGET_TOO_HIGH",
681
+ META_CAMPAIGN_CONFIG_INVALID: "META_CAMPAIGN_CONFIG_INVALID",
682
+ META_WHATSAPP_DESTINATION_DISABLED: "META_WHATSAPP_DESTINATION_DISABLED",
683
+ // TikTok Ads
684
+ TIKTOK_NOT_CONNECTED: "TIKTOK_NOT_CONNECTED",
685
+ TIKTOK_TOKEN_EXPIRED: "TIKTOK_TOKEN_EXPIRED",
686
+ TIKTOK_PERMISSION_DENIED: "TIKTOK_PERMISSION_DENIED",
687
+ TIKTOK_MISSING_PAYMENT: "TIKTOK_MISSING_PAYMENT",
688
+ TIKTOK_PIXEL_NOT_FOUND: "TIKTOK_PIXEL_NOT_FOUND",
689
+ TIKTOK_INVALID_ADVERTISER: "TIKTOK_INVALID_ADVERTISER",
690
+ TIKTOK_ADVERTISER_REQUIRED: "TIKTOK_ADVERTISER_REQUIRED",
691
+ TIKTOK_IDENTITY_REQUIRED: "TIKTOK_IDENTITY_REQUIRED",
692
+ TIKTOK_IDENTITY_API_REQUIRED: "TIKTOK_IDENTITY_API_REQUIRED",
693
+ TIKTOK_VIDEO_UPLOAD_FAILED: "TIKTOK_VIDEO_UPLOAD_FAILED",
694
+ TIKTOK_VIDEO_UPLOAD_TIMEOUT: "TIKTOK_VIDEO_UPLOAD_TIMEOUT",
695
+ TIKTOK_RATE_LIMITED: "TIKTOK_RATE_LIMITED",
696
+ TIKTOK_SERVER_ERROR: "TIKTOK_SERVER_ERROR",
697
+ TIKTOK_INVALID_DATA: "TIKTOK_INVALID_DATA",
698
+ // Generic transport / catch-all
699
+ UPSTREAM_TIMEOUT: "UPSTREAM_TIMEOUT",
700
+ UPSTREAM_UNAVAILABLE: "UPSTREAM_UNAVAILABLE",
701
+ INTERNAL: "INTERNAL"
702
+ };
703
+ var CODE_MESSAGES = {
704
+ // Shopify
705
+ SHOPIFY_NOT_CONNECTED: "No tienes ninguna tienda Shopify conectada. Conecta una en https://productmaker.app/integrations antes de publicar.",
706
+ SHOPIFY_TOKEN_EXPIRED: "La sesi\xF3n con Shopify expir\xF3. Reconecta la tienda en https://productmaker.app/integrations.",
707
+ SHOPIFY_SCOPE_UPGRADE_REQUIRED: "La conexi\xF3n con Shopify necesita permisos adicionales. Recon\xE9ctala en https://productmaker.app/integrations para aprobar los nuevos permisos.",
708
+ SHOPIFY_PUBLISH_FAILED: "Shopify rechaz\xF3 la publicaci\xF3n. Verifica que el producto/landing no exista ya en la tienda y que tu plan permita publicar m\xE1s productos.",
709
+ // Meta — high-frequency
710
+ META_NO_FB_PAGE: "No encontramos una P\xE1gina de Facebook asociada a tu cuenta. Crea una en https://www.facebook.com/pages/create antes de publicar (Meta exige una Page para correr ads).",
711
+ META_NO_ADS_CREATED: "No se pudieron crear los ads. Lo m\xE1s probable: la cuenta de Meta Ads no tiene m\xE9todo de pago, falta vincular una P\xE1gina, o la app de Facebook perdi\xF3 permisos. Verifica los tres en https://business.facebook.com.",
712
+ META_NO_ASSETS_READY: "La tarea a\xFAn no tiene videos ni ad creative listos para publicar. Espera a que termine la generaci\xF3n o regenera los assets.",
713
+ META_NO_THUMBNAIL_SOURCE: "No hay imagen disponible para usar de portada del video. Sube una imagen de producto o genera el ad creative antes de publicar.",
714
+ META_NO_LANDING_URL: "Falta la URL de la landing. P\xE1sala en `landingUrl` o publica primero a Shopify con `publish_to_shopify`.",
715
+ META_TASK_NOT_FOUND: "La tarea no existe o no pertenece a este usuario. Verifica el `taskId`.",
716
+ META_INVALID_DATA: "Meta rechaz\xF3 la solicitud por datos inv\xE1lidos. Verifica que `adAccountId` y `pixelId` sean correctos.",
717
+ META_INVALID_AD_ACCOUNT: "La cuenta publicitaria no existe o no tienes acceso. Confirma el `adAccountId` con `list_meta_ad_accounts`.",
718
+ META_MISSING_PAYMENT: "Tu cuenta de Meta Ads no tiene m\xE9todo de pago configurado. Visita https://business.facebook.com/billing_hub/payment_settings, selecciona la cuenta publicitaria y agrega una tarjeta antes de reintentar.",
719
+ META_TOKEN_EXPIRED: "Tu sesi\xF3n con Meta expir\xF3. Reconecta Facebook en https://productmaker.app/integrations.",
720
+ META_ACCOUNT_RESTRICTED: "Tu cuenta de Meta Ads est\xE1 temporalmente restringida para crear anuncios. Revisa avisos en https://business.facebook.com y completa la verificaci\xF3n requerida.",
721
+ META_CAPABILITY_MISSING: "Tu app de Facebook no tiene los permisos requeridos para esta cuenta publicitaria. Reconecta Facebook en https://productmaker.app/integrations y acepta TODOS los permisos solicitados (incluyendo business_management).",
722
+ META_PERMISSION_DENIED: "Tu conexi\xF3n de Facebook no tiene los permisos suficientes. Recon\xE9ctala en https://productmaker.app/integrations y acepta todos los permisos.",
723
+ META_PIXEL_NOT_FOUND: "El Pixel no existe o no est\xE1 asociado a la cuenta publicitaria. Verifica el `pixelId` en https://business.facebook.com/events_manager.",
724
+ META_OBJECT_NOT_FOUND: "El objeto solicitado (campa\xF1a/ad set/ad) no existe en Meta. Verifica el ID o crea uno nuevo.",
725
+ META_AI_DISCLOSURE_REQUIRED: "Meta rechaz\xF3 el ad por contenido IA no declarado. El sistema agrega la divulgaci\xF3n autom\xE1ticamente; si persiste, edita la copy para evitar lenguaje que Meta marque como enga\xF1oso.",
726
+ META_BID_STRATEGY_REQUIRES_AMOUNT: "Meta requiere un monto de puja expl\xEDcito para esta cuenta/presupuesto. Sube el presupuesto diario o desactiva las pujas con tope a nivel cuenta.",
727
+ META_CBO_BUDGET_CONFLICT: "Conflicto de presupuesto entre la campa\xF1a (CBO) y el ad set. No se puede mezclar.",
728
+ META_BUDGET_MODE_CONFLICT: "Meta recibi\xF3 presupuesto a nivel campa\xF1a Y a nivel ad set. Define s\xF3lo uno.",
729
+ META_AD_SET_BUDGET_REQUIRED: "Esta configuraci\xF3n de campa\xF1a requiere presupuesto a nivel ad set, no a nivel campa\xF1a.",
730
+ META_DAILY_BUDGET_TOO_HIGH: "El presupuesto diario excede el m\xE1ximo permitido por Meta para tu cuenta. B\xE1jalo y reintenta.",
731
+ META_CAMPAIGN_CONFIG_INVALID: "La configuraci\xF3n de la campa\xF1a es inv\xE1lida. Verifica `dailyBudgetUsd`, `targetCountries` y `campaignName`.",
732
+ META_WHATSAPP_DESTINATION_DISABLED: "El destino WhatsApp no est\xE1 habilitado para esta cuenta de Meta. Cambia el destino del ad o habilita WhatsApp Business en la cuenta.",
733
+ // TikTok
734
+ TIKTOK_NOT_CONNECTED: "No tienes TikTok Ads conectado. Con\xE9ctalo en https://productmaker.app/integrations.",
735
+ TIKTOK_TOKEN_EXPIRED: "La sesi\xF3n con TikTok expir\xF3. Recon\xE9ctalo en https://productmaker.app/integrations.",
736
+ TIKTOK_PERMISSION_DENIED: "Tu conexi\xF3n de TikTok no tiene los permisos suficientes. Recon\xE9ctala y acepta todos los permisos solicitados.",
737
+ TIKTOK_MISSING_PAYMENT: "Tu cuenta de TikTok Ads no tiene m\xE9todo de pago configurado. Agrega uno en https://ads.tiktok.com.",
738
+ TIKTOK_PIXEL_NOT_FOUND: "El Pixel de TikTok no existe o no est\xE1 asociado al advertiser. Verifica el `pixelId`.",
739
+ TIKTOK_INVALID_ADVERTISER: "El advertiser no existe o no tienes acceso. Confirma el `advertiserId` con `list_tiktok_advertisers`.",
740
+ TIKTOK_ADVERTISER_REQUIRED: "Falta el `advertiserId`. Obt\xE9n uno con `list_tiktok_advertisers` antes de publicar.",
741
+ TIKTOK_IDENTITY_REQUIRED: "TikTok requiere una identidad (perfil del anunciante) para publicar. Crea una en https://ads.tiktok.com.",
742
+ TIKTOK_IDENTITY_API_REQUIRED: "TikTok requiere habilitar la API de identidades en tu cuenta. Contacta a TikTok Ads Support.",
743
+ TIKTOK_VIDEO_UPLOAD_FAILED: "TikTok rechaz\xF3 la subida del video. Verifica que el formato sea MP4 < 500MB.",
744
+ TIKTOK_VIDEO_UPLOAD_TIMEOUT: "La subida del video a TikTok se demor\xF3 demasiado. Reintenta en unos minutos.",
745
+ TIKTOK_RATE_LIMITED: "TikTok limit\xF3 las solicitudes. Espera unos minutos antes de reintentar.",
746
+ TIKTOK_SERVER_ERROR: "TikTok devolvi\xF3 un error de servidor. Reintenta en unos minutos.",
747
+ TIKTOK_INVALID_DATA: "TikTok rechaz\xF3 la solicitud por datos inv\xE1lidos. Verifica `advertiserId`, `pixelId` y `campaignConfig`.",
748
+ // Campaign (shared)
749
+ CAMPAIGN_NOT_FOUND: "La campa\xF1a no existe o no pertenece a este usuario. Verifica el `campaignId`.",
750
+ CAMPAIGN_TASK_NOT_FOUND: "La tarea de la campa\xF1a no existe. Verifica el `taskId`.",
751
+ CAMPAIGN_MISSING_LANDING_URL: "Falta la URL de la landing en la campa\xF1a. Publica primero a Shopify o pasa `landingUrl` expl\xEDcitamente.",
752
+ CAMPAIGN_NO_CREATIVES: "La tarea no tiene creativos (video/imagen) listos para la campa\xF1a. Espera a que termine la generaci\xF3n.",
753
+ CAMPAIGN_MISSING_THUMBNAILS: "Faltan miniaturas para los videos. Verifica que la generaci\xF3n de imagen/video haya terminado.",
754
+ // Connections
755
+ NO_SHOPIFY_SHOPS: "No hay tiendas Shopify conectadas. Conecta una en https://productmaker.app/integrations.",
756
+ NO_META_AD_ACCOUNTS: "No hay cuentas de Meta Ads conectadas. Conecta Facebook en https://productmaker.app/integrations.",
757
+ // Task lifecycle
758
+ TASK_NOT_FOUND: "La tarea no existe o no pertenece a este usuario. Verifica el `taskId`.",
759
+ TASK_NOT_READY: "La tarea a\xFAn no est\xE1 lista para esta acci\xF3n. Espera a que termine y reintenta.",
760
+ // API key
761
+ INVALID_API_KEY: "API key inv\xE1lida. Revisa tu PM_API_KEY en la configuraci\xF3n del cliente MCP.",
762
+ REVOKED_API_KEY: "API key revocada. Genera una nueva en https://productmaker.app/account/api-keys.",
763
+ // Rate
764
+ RATE_LIMITED: "Demasiadas solicitudes. Espera unos segundos.",
765
+ // Generic
766
+ UPSTREAM_UNAVAILABLE: "El servicio Product Maker no respondi\xF3. Intenta de nuevo en un momento.",
767
+ UPSTREAM_TIMEOUT: "Tiempo de espera agotado. Reintenta en un momento.",
768
+ INTERNAL: "Error interno.",
769
+ INVALID_INPUT: "Entrada inv\xE1lida."
770
+ };
771
+ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
772
+ // Input/auth
773
+ McpErrorCode.INVALID_INPUT,
774
+ McpErrorCode.INVALID_API_KEY,
775
+ McpErrorCode.REVOKED_API_KEY,
776
+ McpErrorCode.IMAGE_PATH_NOT_FOUND,
777
+ McpErrorCode.IMAGE_PATH_NOT_READABLE,
778
+ McpErrorCode.IMAGE_PATH_NOT_A_FILE,
779
+ McpErrorCode.PATH_NOT_SUPPORTED_IN_HTTP_MODE,
780
+ // Identifier mismatches
781
+ McpErrorCode.CAMPAIGN_NOT_FOUND,
782
+ McpErrorCode.CAMPAIGN_TASK_NOT_FOUND,
783
+ McpErrorCode.TASK_NOT_FOUND,
784
+ McpErrorCode.META_TASK_NOT_FOUND,
785
+ McpErrorCode.META_INVALID_AD_ACCOUNT,
786
+ McpErrorCode.META_PIXEL_NOT_FOUND,
787
+ McpErrorCode.META_OBJECT_NOT_FOUND,
788
+ McpErrorCode.TIKTOK_INVALID_ADVERTISER,
789
+ McpErrorCode.TIKTOK_ADVERTISER_REQUIRED,
790
+ McpErrorCode.TIKTOK_PIXEL_NOT_FOUND,
791
+ McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
792
+ McpErrorCode.META_NO_LANDING_URL,
793
+ McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
794
+ McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
795
+ McpErrorCode.META_BID_STRATEGY_REQUIRES_AMOUNT,
796
+ McpErrorCode.META_CBO_BUDGET_CONFLICT,
797
+ McpErrorCode.META_BUDGET_MODE_CONFLICT,
798
+ McpErrorCode.META_AD_SET_BUDGET_REQUIRED,
799
+ // Validation-shape backend errors (data-level rejection)
800
+ McpErrorCode.META_INVALID_DATA,
801
+ McpErrorCode.TIKTOK_INVALID_DATA,
802
+ // Out-of-band user-resolvable auth/billing/connection — caller fixes by
803
+ // going to Facebook/TikTok/Shopify and reconnecting, finishing
804
+ // verification, or adding a payment method. None of these are transient,
805
+ // so InternalError would mislead the agent into retrying blindly.
806
+ McpErrorCode.META_PERMISSION_DENIED,
807
+ McpErrorCode.META_CAPABILITY_MISSING,
808
+ McpErrorCode.META_TOKEN_EXPIRED,
809
+ McpErrorCode.META_MISSING_PAYMENT,
810
+ McpErrorCode.META_ACCOUNT_RESTRICTED,
811
+ McpErrorCode.META_NO_FB_PAGE,
812
+ McpErrorCode.TIKTOK_TOKEN_EXPIRED,
813
+ McpErrorCode.TIKTOK_PERMISSION_DENIED,
814
+ McpErrorCode.TIKTOK_MISSING_PAYMENT,
815
+ McpErrorCode.TIKTOK_IDENTITY_REQUIRED,
816
+ McpErrorCode.TIKTOK_IDENTITY_API_REQUIRED,
817
+ McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
818
+ McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
819
+ McpErrorCode.SHOPIFY_NOT_CONNECTED,
820
+ McpErrorCode.TIKTOK_NOT_CONNECTED,
821
+ McpErrorCode.NO_SHOPIFY_SHOPS,
822
+ McpErrorCode.NO_META_AD_ACCOUNTS
823
+ ]);
824
+ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
825
+ // Shopify
826
+ McpErrorCode.SHOPIFY_NOT_CONNECTED,
827
+ McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
828
+ McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
829
+ McpErrorCode.SHOPIFY_PUBLISH_FAILED,
830
+ // Meta
831
+ McpErrorCode.META_NO_FB_PAGE,
832
+ McpErrorCode.META_NO_ADS_CREATED,
833
+ McpErrorCode.META_NO_ASSETS_READY,
834
+ McpErrorCode.META_NO_THUMBNAIL_SOURCE,
835
+ McpErrorCode.META_NO_LANDING_URL,
836
+ McpErrorCode.META_TASK_NOT_FOUND,
837
+ McpErrorCode.META_INVALID_DATA,
838
+ McpErrorCode.META_INVALID_AD_ACCOUNT,
839
+ McpErrorCode.META_MISSING_PAYMENT,
840
+ McpErrorCode.META_TOKEN_EXPIRED,
841
+ McpErrorCode.META_ACCOUNT_RESTRICTED,
842
+ McpErrorCode.META_CAPABILITY_MISSING,
843
+ McpErrorCode.META_PERMISSION_DENIED,
844
+ McpErrorCode.META_PIXEL_NOT_FOUND,
845
+ McpErrorCode.META_OBJECT_NOT_FOUND,
846
+ McpErrorCode.META_AI_DISCLOSURE_REQUIRED,
847
+ McpErrorCode.META_BID_STRATEGY_REQUIRES_AMOUNT,
848
+ McpErrorCode.META_CBO_BUDGET_CONFLICT,
849
+ McpErrorCode.META_BUDGET_MODE_CONFLICT,
850
+ McpErrorCode.META_AD_SET_BUDGET_REQUIRED,
851
+ McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
852
+ McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
853
+ McpErrorCode.META_WHATSAPP_DESTINATION_DISABLED,
854
+ // TikTok
855
+ McpErrorCode.TIKTOK_NOT_CONNECTED,
856
+ McpErrorCode.TIKTOK_TOKEN_EXPIRED,
857
+ McpErrorCode.TIKTOK_PERMISSION_DENIED,
858
+ McpErrorCode.TIKTOK_MISSING_PAYMENT,
859
+ McpErrorCode.TIKTOK_PIXEL_NOT_FOUND,
860
+ McpErrorCode.TIKTOK_INVALID_ADVERTISER,
861
+ McpErrorCode.TIKTOK_ADVERTISER_REQUIRED,
862
+ McpErrorCode.TIKTOK_IDENTITY_REQUIRED,
863
+ McpErrorCode.TIKTOK_IDENTITY_API_REQUIRED,
864
+ McpErrorCode.TIKTOK_VIDEO_UPLOAD_FAILED,
865
+ McpErrorCode.TIKTOK_VIDEO_UPLOAD_TIMEOUT,
866
+ McpErrorCode.TIKTOK_RATE_LIMITED,
867
+ McpErrorCode.TIKTOK_SERVER_ERROR,
868
+ McpErrorCode.TIKTOK_INVALID_DATA,
869
+ // Campaign (shared)
870
+ McpErrorCode.CAMPAIGN_NOT_FOUND,
871
+ McpErrorCode.CAMPAIGN_TASK_NOT_FOUND,
872
+ McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
873
+ McpErrorCode.CAMPAIGN_NO_CREATIVES,
874
+ McpErrorCode.CAMPAIGN_MISSING_THUMBNAILS,
875
+ // Connections
876
+ McpErrorCode.NO_SHOPIFY_SHOPS,
877
+ McpErrorCode.NO_META_AD_ACCOUNTS,
878
+ // Auth
879
+ McpErrorCode.REVOKED_API_KEY
880
+ ]);
881
+ function mcp(code, message, extra = {}) {
882
+ const errCode = INVALID_PARAM_CODES.has(code) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
883
+ return new McpError(errCode, message, {
884
+ code,
885
+ ...extra
886
+ });
887
+ }
888
+ __name(mcp, "mcp");
889
+ var GENERIC_BACKEND_MESSAGES = /* @__PURE__ */ new Set([
890
+ "No ads could be created for the campaign.",
891
+ "No assets could be created for the campaign.",
892
+ "Invalid data provided to Meta API",
893
+ "Insufficient permissions for Meta account",
894
+ "Meta session has expired",
895
+ "Meta Ads account has no payment method configured"
896
+ ]);
897
+ function looksLikeMeaningfulBackendMessage(message, mapped) {
898
+ if (!message || message.length === 0) return false;
899
+ if (GENERIC_BACKEND_MESSAGES.has(message)) return false;
900
+ const norm = /* @__PURE__ */ __name((s) => s.toLowerCase().replace(/[.\s]+$/, ""), "norm");
901
+ if (norm(mapped).includes(norm(message))) return false;
902
+ return true;
903
+ }
904
+ __name(looksLikeMeaningfulBackendMessage, "looksLikeMeaningfulBackendMessage");
905
+ function resolveMessage(code, backendMessage) {
906
+ const mapped = CODE_MESSAGES[code];
907
+ const backend = typeof backendMessage === "string" ? backendMessage.trim() : "";
908
+ if (mapped && backend && looksLikeMeaningfulBackendMessage(backend, mapped)) {
909
+ return `${mapped} (Detalle: ${backend})`;
910
+ }
911
+ if (mapped) return mapped;
912
+ if (backend) return backend;
913
+ return `El servicio devolvi\xF3 un error (c\xF3digo: ${code}). Intenta de nuevo en un momento o contacta soporte si persiste.`;
914
+ }
915
+ __name(resolveMessage, "resolveMessage");
916
+ function translateError(err) {
917
+ if (err instanceof ResolveImageError) {
918
+ return mcp(McpErrorCode[err.code], err.message);
919
+ }
920
+ if (err instanceof ApiHttpError) {
921
+ const body = isRecord(err.body) ? err.body : {};
922
+ if (typeof body.code === "string" && PASSTHROUGH_CODES.has(body.code)) {
923
+ const code = body.code;
924
+ return mcp(code, resolveMessage(code, body.message), {
925
+ ...body.helpUrl ? {
926
+ helpUrl: body.helpUrl
927
+ } : {}
928
+ });
929
+ }
930
+ if (err.status === 401) {
931
+ return mcp(McpErrorCode.INVALID_API_KEY, resolveMessage(McpErrorCode.INVALID_API_KEY, void 0));
932
+ }
933
+ if (err.status === 429) {
934
+ return mcp(McpErrorCode.RATE_LIMITED, resolveMessage(McpErrorCode.RATE_LIMITED, void 0), {
935
+ retryAfterSeconds: Number(body.retryAfterSeconds ?? 30)
936
+ });
937
+ }
938
+ if (err.status === 400) {
939
+ return mcp(McpErrorCode.INVALID_INPUT, typeof body.message === "string" ? body.message : resolveMessage(McpErrorCode.INVALID_INPUT, void 0));
940
+ }
941
+ if (err.status === 404) {
942
+ const isTask = body.resource === "task" || /task/i.test(String(body.message ?? ""));
943
+ return mcp(isTask ? McpErrorCode.TASK_NOT_FOUND : McpErrorCode.INVALID_INPUT, resolveMessage(isTask ? McpErrorCode.TASK_NOT_FOUND : McpErrorCode.INVALID_INPUT, void 0));
944
+ }
945
+ if (err.status === 409) {
946
+ return mcp(McpErrorCode.TASK_NOT_READY, resolveMessage(McpErrorCode.TASK_NOT_READY, void 0));
947
+ }
948
+ if (err.status >= 500) {
949
+ return mcp(McpErrorCode.UPSTREAM_UNAVAILABLE, resolveMessage(McpErrorCode.UPSTREAM_UNAVAILABLE, void 0));
950
+ }
951
+ return mcp(McpErrorCode.INTERNAL, `HTTP ${err.status}`);
952
+ }
953
+ if (err instanceof Error && /aborted|timeout/i.test(err.message)) {
954
+ return mcp(McpErrorCode.UPSTREAM_TIMEOUT, resolveMessage(McpErrorCode.UPSTREAM_TIMEOUT, void 0));
955
+ }
956
+ return mcp(McpErrorCode.INTERNAL, err instanceof Error ? err.message : resolveMessage(McpErrorCode.INTERNAL, void 0));
957
+ }
958
+ __name(translateError, "translateError");
959
+
721
960
  // src/asset-proxy.ts
722
961
  import { createHmac, timingSafeEqual } from "crypto";
723
962
  var TOKEN_TTL_SECONDS = 3600;
@@ -1377,7 +1616,11 @@ import { z as z6 } from "zod";
1377
1616
  var ShopifyInput = z6.object({
1378
1617
  taskId: z6.string().min(1),
1379
1618
  shopDomain: z6.string().min(1),
1380
- numLandingImages: z6.number().int().min(1).max(7).optional(),
1619
+ /** Override: number of landing images to include. Default = ALL images the
1620
+ * task generated (the MCP fetches /v1/results first to count). Bounded
1621
+ * generously here; the backend validates each index against
1622
+ * landing.images.length and rejects out-of-range. */
1623
+ numLandingImages: z6.number().int().min(1).max(20).optional(),
1381
1624
  variantIndex: z6.number().int().min(0).optional(),
1382
1625
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
1383
1626
  * When true, the landing renders variant-picker pills; when false, it ships
@@ -1410,10 +1653,12 @@ var TikTokInput = z6.object({
1410
1653
  campaignConfig: McpCampaignConfig.optional()
1411
1654
  });
1412
1655
  function registerPublishTools(server, api) {
1413
- server.tool("publish_to_shopify", "Publica la landing y producto generados en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. ANTES de llamar preg\xFAntale al usuario \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED \u2014 eso renderiza los pills de variantes en la landing. Por defecto incluye 3 im\xE1genes del hero. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
1656
+ server.tool("publish_to_shopify", "Publica la landing y producto generados en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. ANTES de llamar preg\xFAntale al usuario \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED \u2014 eso renderiza los pills de variantes en la landing. Por defecto publica TODAS las im\xE1genes que la tarea gener\xF3 para la landing; pasa `numLandingImages` s\xF3lo si el usuario te pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
1414
1657
  try {
1415
- const numImages = input.numLandingImages ?? 3;
1416
1658
  const variantIndex = input.variantIndex ?? 0;
1659
+ const availableImages = await fetchLandingImageCount(api, input.taskId);
1660
+ const effectiveAvailable = availableImages > 0 ? availableImages : 3;
1661
+ const numImages = Math.min(input.numLandingImages ?? effectiveAvailable, effectiveAvailable);
1417
1662
  const selectedLandingImages = Array.from({
1418
1663
  length: numImages
1419
1664
  }, (_, i) => ({
@@ -1490,6 +1735,19 @@ function renderShopifyPublishMarkdown(r) {
1490
1735
  };
1491
1736
  }
1492
1737
  __name(renderShopifyPublishMarkdown, "renderShopifyPublishMarkdown");
1738
+ async function fetchLandingImageCount(api, taskId) {
1739
+ try {
1740
+ const r = await api.getJson(`/v1/results/${encodeURIComponent(taskId)}`);
1741
+ if (!isRecord(r)) return 0;
1742
+ const landing = r.landing;
1743
+ if (!isRecord(landing)) return 0;
1744
+ const images = landing.images;
1745
+ return Array.isArray(images) ? images.length : 0;
1746
+ } catch {
1747
+ return 0;
1748
+ }
1749
+ }
1750
+ __name(fetchLandingImageCount, "fetchLandingImageCount");
1493
1751
  function toMetaCampaignOverrides(cfg) {
1494
1752
  const out = {};
1495
1753
  if (cfg.dailyBudgetUsd !== void 0) out.dailyBudgetUsd = cfg.dailyBudgetUsd;
package/dist/stdio.js CHANGED
@@ -388,106 +388,6 @@ __name(sleep, "sleep");
388
388
 
389
389
  // src/errors.ts
390
390
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
391
- var McpErrorCode = {
392
- INVALID_API_KEY: "INVALID_API_KEY",
393
- REVOKED_API_KEY: "REVOKED_API_KEY",
394
- RATE_LIMITED: "RATE_LIMITED",
395
- INVALID_INPUT: "INVALID_INPUT",
396
- IMAGE_DOWNLOAD_FAILED: "IMAGE_DOWNLOAD_FAILED",
397
- IMAGE_TOO_LARGE: "IMAGE_TOO_LARGE",
398
- IMAGE_TYPE_UNSUPPORTED: "IMAGE_TYPE_UNSUPPORTED",
399
- IMAGE_PATH_NOT_FOUND: "IMAGE_PATH_NOT_FOUND",
400
- IMAGE_PATH_NOT_READABLE: "IMAGE_PATH_NOT_READABLE",
401
- IMAGE_PATH_NOT_A_FILE: "IMAGE_PATH_NOT_A_FILE",
402
- PATH_NOT_SUPPORTED_IN_HTTP_MODE: "PATH_NOT_SUPPORTED_IN_HTTP_MODE",
403
- TASK_NOT_FOUND: "TASK_NOT_FOUND",
404
- TASK_NOT_READY: "TASK_NOT_READY",
405
- CAMPAIGN_NOT_FOUND: "CAMPAIGN_NOT_FOUND",
406
- NO_SHOPIFY_SHOPS: "NO_SHOPIFY_SHOPS",
407
- NO_META_AD_ACCOUNTS: "NO_META_AD_ACCOUNTS",
408
- SHOPIFY_NOT_CONNECTED: "SHOPIFY_NOT_CONNECTED",
409
- META_CAPABILITY_MISSING: "META_CAPABILITY_MISSING",
410
- TIKTOK_NOT_CONNECTED: "TIKTOK_NOT_CONNECTED",
411
- UPSTREAM_TIMEOUT: "UPSTREAM_TIMEOUT",
412
- UPSTREAM_UNAVAILABLE: "UPSTREAM_UNAVAILABLE",
413
- INTERNAL: "INTERNAL"
414
- };
415
- var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
416
- McpErrorCode.INVALID_INPUT,
417
- McpErrorCode.INVALID_API_KEY,
418
- McpErrorCode.REVOKED_API_KEY,
419
- McpErrorCode.IMAGE_PATH_NOT_FOUND,
420
- McpErrorCode.IMAGE_PATH_NOT_READABLE,
421
- McpErrorCode.IMAGE_PATH_NOT_A_FILE,
422
- McpErrorCode.PATH_NOT_SUPPORTED_IN_HTTP_MODE,
423
- // The caller passed a campaignId that does not exist — semantically a
424
- // bad parameter, not an internal failure. Matches the spec contract
425
- // (mcp-server.spec.ts: "McpError INVALID_PARAMS code=CAMPAIGN_NOT_FOUND").
426
- McpErrorCode.CAMPAIGN_NOT_FOUND
427
- ]);
428
- function mcp(code, message, extra = {}) {
429
- const errCode = INVALID_PARAM_CODES.has(code) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
430
- return new McpError(errCode, message, {
431
- code,
432
- ...extra
433
- });
434
- }
435
- __name(mcp, "mcp");
436
- var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
437
- "META_CAPABILITY_MISSING",
438
- "NO_SHOPIFY_SHOPS",
439
- "NO_META_AD_ACCOUNTS",
440
- "SHOPIFY_NOT_CONNECTED",
441
- "TIKTOK_NOT_CONNECTED",
442
- "REVOKED_API_KEY",
443
- "CAMPAIGN_NOT_FOUND"
444
- ]);
445
- function isRecord(v) {
446
- return typeof v === "object" && v !== null && !Array.isArray(v);
447
- }
448
- __name(isRecord, "isRecord");
449
- function translateError(err) {
450
- if (err instanceof ResolveImageError) {
451
- return mcp(McpErrorCode[err.code], err.message);
452
- }
453
- if (err instanceof ApiHttpError) {
454
- const body = isRecord(err.body) ? err.body : {};
455
- if (typeof body.code === "string" && PASSTHROUGH_CODES.has(body.code)) {
456
- return mcp(body.code, String(body.message ?? body.code), {
457
- ...body.helpUrl ? {
458
- helpUrl: body.helpUrl
459
- } : {}
460
- });
461
- }
462
- if (err.status === 401) {
463
- return mcp(McpErrorCode.INVALID_API_KEY, "API key inv\xE1lida. Revisa tu PM_API_KEY en la configuraci\xF3n del cliente MCP.");
464
- }
465
- if (err.status === 429) {
466
- return mcp(McpErrorCode.RATE_LIMITED, "Demasiadas solicitudes. Espera unos segundos.", {
467
- retryAfterSeconds: Number(body.retryAfterSeconds ?? 30)
468
- });
469
- }
470
- if (err.status === 400) {
471
- return mcp(McpErrorCode.INVALID_INPUT, typeof body.message === "string" ? body.message : "Entrada inv\xE1lida.");
472
- }
473
- if (err.status === 404) {
474
- const isTask = body.resource === "task" || /task/i.test(String(body.message ?? ""));
475
- return mcp(isTask ? McpErrorCode.TASK_NOT_FOUND : McpErrorCode.INVALID_INPUT, "Recurso no encontrado.");
476
- }
477
- if (err.status === 409) {
478
- return mcp(McpErrorCode.TASK_NOT_READY, "La tarea a\xFAn no est\xE1 lista para esta acci\xF3n.");
479
- }
480
- if (err.status >= 500) {
481
- return mcp(McpErrorCode.UPSTREAM_UNAVAILABLE, "El servicio Product Maker no respondi\xF3. Intenta de nuevo en un momento.");
482
- }
483
- return mcp(McpErrorCode.INTERNAL, `HTTP ${err.status}`);
484
- }
485
- if (err instanceof Error && /aborted|timeout/i.test(err.message)) {
486
- return mcp(McpErrorCode.UPSTREAM_TIMEOUT, "Tiempo de espera agotado.");
487
- }
488
- return mcp(McpErrorCode.INTERNAL, err instanceof Error ? err.message : "Error interno.");
489
- }
490
- __name(translateError, "translateError");
491
391
 
492
392
  // src/tools/shared.ts
493
393
  import { z } from "zod";
@@ -679,6 +579,10 @@ function asText(value) {
679
579
  };
680
580
  }
681
581
  __name(asText, "asText");
582
+ function isRecord(v) {
583
+ return typeof v === "object" && v !== null && !Array.isArray(v);
584
+ }
585
+ __name(isRecord, "isRecord");
682
586
  function escapeMdLabel(s) {
683
587
  return s.replace(/[\[\]\(\)\{\}`<>\r\n\\*_]/g, " ").replace(/\s+/g, " ").trim();
684
588
  }
@@ -715,6 +619,341 @@ async function submitCreative(api, opts) {
715
619
  }
716
620
  __name(submitCreative, "submitCreative");
717
621
 
622
+ // src/errors.ts
623
+ var McpErrorCode = {
624
+ // Auth / API key
625
+ INVALID_API_KEY: "INVALID_API_KEY",
626
+ REVOKED_API_KEY: "REVOKED_API_KEY",
627
+ // Rate limit / quota
628
+ RATE_LIMITED: "RATE_LIMITED",
629
+ // Input
630
+ INVALID_INPUT: "INVALID_INPUT",
631
+ // Image upload (MCP-local)
632
+ IMAGE_DOWNLOAD_FAILED: "IMAGE_DOWNLOAD_FAILED",
633
+ IMAGE_TOO_LARGE: "IMAGE_TOO_LARGE",
634
+ IMAGE_TYPE_UNSUPPORTED: "IMAGE_TYPE_UNSUPPORTED",
635
+ IMAGE_PATH_NOT_FOUND: "IMAGE_PATH_NOT_FOUND",
636
+ IMAGE_PATH_NOT_READABLE: "IMAGE_PATH_NOT_READABLE",
637
+ IMAGE_PATH_NOT_A_FILE: "IMAGE_PATH_NOT_A_FILE",
638
+ PATH_NOT_SUPPORTED_IN_HTTP_MODE: "PATH_NOT_SUPPORTED_IN_HTTP_MODE",
639
+ // Task lifecycle
640
+ TASK_NOT_FOUND: "TASK_NOT_FOUND",
641
+ TASK_NOT_READY: "TASK_NOT_READY",
642
+ // Connection presence
643
+ NO_SHOPIFY_SHOPS: "NO_SHOPIFY_SHOPS",
644
+ NO_META_AD_ACCOUNTS: "NO_META_AD_ACCOUNTS",
645
+ // Campaigns (shared Meta + TikTok)
646
+ CAMPAIGN_NOT_FOUND: "CAMPAIGN_NOT_FOUND",
647
+ CAMPAIGN_TASK_NOT_FOUND: "CAMPAIGN_TASK_NOT_FOUND",
648
+ CAMPAIGN_MISSING_LANDING_URL: "CAMPAIGN_MISSING_LANDING_URL",
649
+ CAMPAIGN_NO_CREATIVES: "CAMPAIGN_NO_CREATIVES",
650
+ CAMPAIGN_MISSING_THUMBNAILS: "CAMPAIGN_MISSING_THUMBNAILS",
651
+ // Shopify
652
+ SHOPIFY_NOT_CONNECTED: "SHOPIFY_NOT_CONNECTED",
653
+ SHOPIFY_TOKEN_EXPIRED: "SHOPIFY_TOKEN_EXPIRED",
654
+ SHOPIFY_SCOPE_UPGRADE_REQUIRED: "SHOPIFY_SCOPE_UPGRADE_REQUIRED",
655
+ SHOPIFY_PUBLISH_FAILED: "SHOPIFY_PUBLISH_FAILED",
656
+ // Meta / Facebook Ads
657
+ META_NO_FB_PAGE: "META_NO_FB_PAGE",
658
+ META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
659
+ META_NO_ASSETS_READY: "META_NO_ASSETS_READY",
660
+ META_NO_THUMBNAIL_SOURCE: "META_NO_THUMBNAIL_SOURCE",
661
+ META_NO_LANDING_URL: "META_NO_LANDING_URL",
662
+ META_TASK_NOT_FOUND: "META_TASK_NOT_FOUND",
663
+ META_INVALID_DATA: "META_INVALID_DATA",
664
+ META_INVALID_AD_ACCOUNT: "META_INVALID_AD_ACCOUNT",
665
+ META_MISSING_PAYMENT: "META_MISSING_PAYMENT",
666
+ META_TOKEN_EXPIRED: "META_TOKEN_EXPIRED",
667
+ META_ACCOUNT_RESTRICTED: "META_ACCOUNT_RESTRICTED",
668
+ META_CAPABILITY_MISSING: "META_CAPABILITY_MISSING",
669
+ META_PERMISSION_DENIED: "META_PERMISSION_DENIED",
670
+ META_PIXEL_NOT_FOUND: "META_PIXEL_NOT_FOUND",
671
+ META_OBJECT_NOT_FOUND: "META_OBJECT_NOT_FOUND",
672
+ META_AI_DISCLOSURE_REQUIRED: "META_AI_DISCLOSURE_REQUIRED",
673
+ META_BID_STRATEGY_REQUIRES_AMOUNT: "META_BID_STRATEGY_REQUIRES_AMOUNT",
674
+ META_CBO_BUDGET_CONFLICT: "META_CBO_BUDGET_CONFLICT",
675
+ META_BUDGET_MODE_CONFLICT: "META_BUDGET_MODE_CONFLICT",
676
+ META_AD_SET_BUDGET_REQUIRED: "META_AD_SET_BUDGET_REQUIRED",
677
+ META_DAILY_BUDGET_TOO_HIGH: "META_DAILY_BUDGET_TOO_HIGH",
678
+ META_CAMPAIGN_CONFIG_INVALID: "META_CAMPAIGN_CONFIG_INVALID",
679
+ META_WHATSAPP_DESTINATION_DISABLED: "META_WHATSAPP_DESTINATION_DISABLED",
680
+ // TikTok Ads
681
+ TIKTOK_NOT_CONNECTED: "TIKTOK_NOT_CONNECTED",
682
+ TIKTOK_TOKEN_EXPIRED: "TIKTOK_TOKEN_EXPIRED",
683
+ TIKTOK_PERMISSION_DENIED: "TIKTOK_PERMISSION_DENIED",
684
+ TIKTOK_MISSING_PAYMENT: "TIKTOK_MISSING_PAYMENT",
685
+ TIKTOK_PIXEL_NOT_FOUND: "TIKTOK_PIXEL_NOT_FOUND",
686
+ TIKTOK_INVALID_ADVERTISER: "TIKTOK_INVALID_ADVERTISER",
687
+ TIKTOK_ADVERTISER_REQUIRED: "TIKTOK_ADVERTISER_REQUIRED",
688
+ TIKTOK_IDENTITY_REQUIRED: "TIKTOK_IDENTITY_REQUIRED",
689
+ TIKTOK_IDENTITY_API_REQUIRED: "TIKTOK_IDENTITY_API_REQUIRED",
690
+ TIKTOK_VIDEO_UPLOAD_FAILED: "TIKTOK_VIDEO_UPLOAD_FAILED",
691
+ TIKTOK_VIDEO_UPLOAD_TIMEOUT: "TIKTOK_VIDEO_UPLOAD_TIMEOUT",
692
+ TIKTOK_RATE_LIMITED: "TIKTOK_RATE_LIMITED",
693
+ TIKTOK_SERVER_ERROR: "TIKTOK_SERVER_ERROR",
694
+ TIKTOK_INVALID_DATA: "TIKTOK_INVALID_DATA",
695
+ // Generic transport / catch-all
696
+ UPSTREAM_TIMEOUT: "UPSTREAM_TIMEOUT",
697
+ UPSTREAM_UNAVAILABLE: "UPSTREAM_UNAVAILABLE",
698
+ INTERNAL: "INTERNAL"
699
+ };
700
+ var CODE_MESSAGES = {
701
+ // Shopify
702
+ SHOPIFY_NOT_CONNECTED: "No tienes ninguna tienda Shopify conectada. Conecta una en https://productmaker.app/integrations antes de publicar.",
703
+ SHOPIFY_TOKEN_EXPIRED: "La sesi\xF3n con Shopify expir\xF3. Reconecta la tienda en https://productmaker.app/integrations.",
704
+ SHOPIFY_SCOPE_UPGRADE_REQUIRED: "La conexi\xF3n con Shopify necesita permisos adicionales. Recon\xE9ctala en https://productmaker.app/integrations para aprobar los nuevos permisos.",
705
+ SHOPIFY_PUBLISH_FAILED: "Shopify rechaz\xF3 la publicaci\xF3n. Verifica que el producto/landing no exista ya en la tienda y que tu plan permita publicar m\xE1s productos.",
706
+ // Meta — high-frequency
707
+ META_NO_FB_PAGE: "No encontramos una P\xE1gina de Facebook asociada a tu cuenta. Crea una en https://www.facebook.com/pages/create antes de publicar (Meta exige una Page para correr ads).",
708
+ META_NO_ADS_CREATED: "No se pudieron crear los ads. Lo m\xE1s probable: la cuenta de Meta Ads no tiene m\xE9todo de pago, falta vincular una P\xE1gina, o la app de Facebook perdi\xF3 permisos. Verifica los tres en https://business.facebook.com.",
709
+ META_NO_ASSETS_READY: "La tarea a\xFAn no tiene videos ni ad creative listos para publicar. Espera a que termine la generaci\xF3n o regenera los assets.",
710
+ META_NO_THUMBNAIL_SOURCE: "No hay imagen disponible para usar de portada del video. Sube una imagen de producto o genera el ad creative antes de publicar.",
711
+ META_NO_LANDING_URL: "Falta la URL de la landing. P\xE1sala en `landingUrl` o publica primero a Shopify con `publish_to_shopify`.",
712
+ META_TASK_NOT_FOUND: "La tarea no existe o no pertenece a este usuario. Verifica el `taskId`.",
713
+ META_INVALID_DATA: "Meta rechaz\xF3 la solicitud por datos inv\xE1lidos. Verifica que `adAccountId` y `pixelId` sean correctos.",
714
+ META_INVALID_AD_ACCOUNT: "La cuenta publicitaria no existe o no tienes acceso. Confirma el `adAccountId` con `list_meta_ad_accounts`.",
715
+ META_MISSING_PAYMENT: "Tu cuenta de Meta Ads no tiene m\xE9todo de pago configurado. Visita https://business.facebook.com/billing_hub/payment_settings, selecciona la cuenta publicitaria y agrega una tarjeta antes de reintentar.",
716
+ META_TOKEN_EXPIRED: "Tu sesi\xF3n con Meta expir\xF3. Reconecta Facebook en https://productmaker.app/integrations.",
717
+ META_ACCOUNT_RESTRICTED: "Tu cuenta de Meta Ads est\xE1 temporalmente restringida para crear anuncios. Revisa avisos en https://business.facebook.com y completa la verificaci\xF3n requerida.",
718
+ META_CAPABILITY_MISSING: "Tu app de Facebook no tiene los permisos requeridos para esta cuenta publicitaria. Reconecta Facebook en https://productmaker.app/integrations y acepta TODOS los permisos solicitados (incluyendo business_management).",
719
+ META_PERMISSION_DENIED: "Tu conexi\xF3n de Facebook no tiene los permisos suficientes. Recon\xE9ctala en https://productmaker.app/integrations y acepta todos los permisos.",
720
+ META_PIXEL_NOT_FOUND: "El Pixel no existe o no est\xE1 asociado a la cuenta publicitaria. Verifica el `pixelId` en https://business.facebook.com/events_manager.",
721
+ META_OBJECT_NOT_FOUND: "El objeto solicitado (campa\xF1a/ad set/ad) no existe en Meta. Verifica el ID o crea uno nuevo.",
722
+ META_AI_DISCLOSURE_REQUIRED: "Meta rechaz\xF3 el ad por contenido IA no declarado. El sistema agrega la divulgaci\xF3n autom\xE1ticamente; si persiste, edita la copy para evitar lenguaje que Meta marque como enga\xF1oso.",
723
+ META_BID_STRATEGY_REQUIRES_AMOUNT: "Meta requiere un monto de puja expl\xEDcito para esta cuenta/presupuesto. Sube el presupuesto diario o desactiva las pujas con tope a nivel cuenta.",
724
+ META_CBO_BUDGET_CONFLICT: "Conflicto de presupuesto entre la campa\xF1a (CBO) y el ad set. No se puede mezclar.",
725
+ META_BUDGET_MODE_CONFLICT: "Meta recibi\xF3 presupuesto a nivel campa\xF1a Y a nivel ad set. Define s\xF3lo uno.",
726
+ META_AD_SET_BUDGET_REQUIRED: "Esta configuraci\xF3n de campa\xF1a requiere presupuesto a nivel ad set, no a nivel campa\xF1a.",
727
+ META_DAILY_BUDGET_TOO_HIGH: "El presupuesto diario excede el m\xE1ximo permitido por Meta para tu cuenta. B\xE1jalo y reintenta.",
728
+ META_CAMPAIGN_CONFIG_INVALID: "La configuraci\xF3n de la campa\xF1a es inv\xE1lida. Verifica `dailyBudgetUsd`, `targetCountries` y `campaignName`.",
729
+ META_WHATSAPP_DESTINATION_DISABLED: "El destino WhatsApp no est\xE1 habilitado para esta cuenta de Meta. Cambia el destino del ad o habilita WhatsApp Business en la cuenta.",
730
+ // TikTok
731
+ TIKTOK_NOT_CONNECTED: "No tienes TikTok Ads conectado. Con\xE9ctalo en https://productmaker.app/integrations.",
732
+ TIKTOK_TOKEN_EXPIRED: "La sesi\xF3n con TikTok expir\xF3. Recon\xE9ctalo en https://productmaker.app/integrations.",
733
+ TIKTOK_PERMISSION_DENIED: "Tu conexi\xF3n de TikTok no tiene los permisos suficientes. Recon\xE9ctala y acepta todos los permisos solicitados.",
734
+ TIKTOK_MISSING_PAYMENT: "Tu cuenta de TikTok Ads no tiene m\xE9todo de pago configurado. Agrega uno en https://ads.tiktok.com.",
735
+ TIKTOK_PIXEL_NOT_FOUND: "El Pixel de TikTok no existe o no est\xE1 asociado al advertiser. Verifica el `pixelId`.",
736
+ TIKTOK_INVALID_ADVERTISER: "El advertiser no existe o no tienes acceso. Confirma el `advertiserId` con `list_tiktok_advertisers`.",
737
+ TIKTOK_ADVERTISER_REQUIRED: "Falta el `advertiserId`. Obt\xE9n uno con `list_tiktok_advertisers` antes de publicar.",
738
+ TIKTOK_IDENTITY_REQUIRED: "TikTok requiere una identidad (perfil del anunciante) para publicar. Crea una en https://ads.tiktok.com.",
739
+ TIKTOK_IDENTITY_API_REQUIRED: "TikTok requiere habilitar la API de identidades en tu cuenta. Contacta a TikTok Ads Support.",
740
+ TIKTOK_VIDEO_UPLOAD_FAILED: "TikTok rechaz\xF3 la subida del video. Verifica que el formato sea MP4 < 500MB.",
741
+ TIKTOK_VIDEO_UPLOAD_TIMEOUT: "La subida del video a TikTok se demor\xF3 demasiado. Reintenta en unos minutos.",
742
+ TIKTOK_RATE_LIMITED: "TikTok limit\xF3 las solicitudes. Espera unos minutos antes de reintentar.",
743
+ TIKTOK_SERVER_ERROR: "TikTok devolvi\xF3 un error de servidor. Reintenta en unos minutos.",
744
+ TIKTOK_INVALID_DATA: "TikTok rechaz\xF3 la solicitud por datos inv\xE1lidos. Verifica `advertiserId`, `pixelId` y `campaignConfig`.",
745
+ // Campaign (shared)
746
+ CAMPAIGN_NOT_FOUND: "La campa\xF1a no existe o no pertenece a este usuario. Verifica el `campaignId`.",
747
+ CAMPAIGN_TASK_NOT_FOUND: "La tarea de la campa\xF1a no existe. Verifica el `taskId`.",
748
+ CAMPAIGN_MISSING_LANDING_URL: "Falta la URL de la landing en la campa\xF1a. Publica primero a Shopify o pasa `landingUrl` expl\xEDcitamente.",
749
+ CAMPAIGN_NO_CREATIVES: "La tarea no tiene creativos (video/imagen) listos para la campa\xF1a. Espera a que termine la generaci\xF3n.",
750
+ CAMPAIGN_MISSING_THUMBNAILS: "Faltan miniaturas para los videos. Verifica que la generaci\xF3n de imagen/video haya terminado.",
751
+ // Connections
752
+ NO_SHOPIFY_SHOPS: "No hay tiendas Shopify conectadas. Conecta una en https://productmaker.app/integrations.",
753
+ NO_META_AD_ACCOUNTS: "No hay cuentas de Meta Ads conectadas. Conecta Facebook en https://productmaker.app/integrations.",
754
+ // Task lifecycle
755
+ TASK_NOT_FOUND: "La tarea no existe o no pertenece a este usuario. Verifica el `taskId`.",
756
+ TASK_NOT_READY: "La tarea a\xFAn no est\xE1 lista para esta acci\xF3n. Espera a que termine y reintenta.",
757
+ // API key
758
+ INVALID_API_KEY: "API key inv\xE1lida. Revisa tu PM_API_KEY en la configuraci\xF3n del cliente MCP.",
759
+ REVOKED_API_KEY: "API key revocada. Genera una nueva en https://productmaker.app/account/api-keys.",
760
+ // Rate
761
+ RATE_LIMITED: "Demasiadas solicitudes. Espera unos segundos.",
762
+ // Generic
763
+ UPSTREAM_UNAVAILABLE: "El servicio Product Maker no respondi\xF3. Intenta de nuevo en un momento.",
764
+ UPSTREAM_TIMEOUT: "Tiempo de espera agotado. Reintenta en un momento.",
765
+ INTERNAL: "Error interno.",
766
+ INVALID_INPUT: "Entrada inv\xE1lida."
767
+ };
768
+ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
769
+ // Input/auth
770
+ McpErrorCode.INVALID_INPUT,
771
+ McpErrorCode.INVALID_API_KEY,
772
+ McpErrorCode.REVOKED_API_KEY,
773
+ McpErrorCode.IMAGE_PATH_NOT_FOUND,
774
+ McpErrorCode.IMAGE_PATH_NOT_READABLE,
775
+ McpErrorCode.IMAGE_PATH_NOT_A_FILE,
776
+ McpErrorCode.PATH_NOT_SUPPORTED_IN_HTTP_MODE,
777
+ // Identifier mismatches
778
+ McpErrorCode.CAMPAIGN_NOT_FOUND,
779
+ McpErrorCode.CAMPAIGN_TASK_NOT_FOUND,
780
+ McpErrorCode.TASK_NOT_FOUND,
781
+ McpErrorCode.META_TASK_NOT_FOUND,
782
+ McpErrorCode.META_INVALID_AD_ACCOUNT,
783
+ McpErrorCode.META_PIXEL_NOT_FOUND,
784
+ McpErrorCode.META_OBJECT_NOT_FOUND,
785
+ McpErrorCode.TIKTOK_INVALID_ADVERTISER,
786
+ McpErrorCode.TIKTOK_ADVERTISER_REQUIRED,
787
+ McpErrorCode.TIKTOK_PIXEL_NOT_FOUND,
788
+ McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
789
+ McpErrorCode.META_NO_LANDING_URL,
790
+ McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
791
+ McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
792
+ McpErrorCode.META_BID_STRATEGY_REQUIRES_AMOUNT,
793
+ McpErrorCode.META_CBO_BUDGET_CONFLICT,
794
+ McpErrorCode.META_BUDGET_MODE_CONFLICT,
795
+ McpErrorCode.META_AD_SET_BUDGET_REQUIRED,
796
+ // Validation-shape backend errors (data-level rejection)
797
+ McpErrorCode.META_INVALID_DATA,
798
+ McpErrorCode.TIKTOK_INVALID_DATA,
799
+ // Out-of-band user-resolvable auth/billing/connection — caller fixes by
800
+ // going to Facebook/TikTok/Shopify and reconnecting, finishing
801
+ // verification, or adding a payment method. None of these are transient,
802
+ // so InternalError would mislead the agent into retrying blindly.
803
+ McpErrorCode.META_PERMISSION_DENIED,
804
+ McpErrorCode.META_CAPABILITY_MISSING,
805
+ McpErrorCode.META_TOKEN_EXPIRED,
806
+ McpErrorCode.META_MISSING_PAYMENT,
807
+ McpErrorCode.META_ACCOUNT_RESTRICTED,
808
+ McpErrorCode.META_NO_FB_PAGE,
809
+ McpErrorCode.TIKTOK_TOKEN_EXPIRED,
810
+ McpErrorCode.TIKTOK_PERMISSION_DENIED,
811
+ McpErrorCode.TIKTOK_MISSING_PAYMENT,
812
+ McpErrorCode.TIKTOK_IDENTITY_REQUIRED,
813
+ McpErrorCode.TIKTOK_IDENTITY_API_REQUIRED,
814
+ McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
815
+ McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
816
+ McpErrorCode.SHOPIFY_NOT_CONNECTED,
817
+ McpErrorCode.TIKTOK_NOT_CONNECTED,
818
+ McpErrorCode.NO_SHOPIFY_SHOPS,
819
+ McpErrorCode.NO_META_AD_ACCOUNTS
820
+ ]);
821
+ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
822
+ // Shopify
823
+ McpErrorCode.SHOPIFY_NOT_CONNECTED,
824
+ McpErrorCode.SHOPIFY_TOKEN_EXPIRED,
825
+ McpErrorCode.SHOPIFY_SCOPE_UPGRADE_REQUIRED,
826
+ McpErrorCode.SHOPIFY_PUBLISH_FAILED,
827
+ // Meta
828
+ McpErrorCode.META_NO_FB_PAGE,
829
+ McpErrorCode.META_NO_ADS_CREATED,
830
+ McpErrorCode.META_NO_ASSETS_READY,
831
+ McpErrorCode.META_NO_THUMBNAIL_SOURCE,
832
+ McpErrorCode.META_NO_LANDING_URL,
833
+ McpErrorCode.META_TASK_NOT_FOUND,
834
+ McpErrorCode.META_INVALID_DATA,
835
+ McpErrorCode.META_INVALID_AD_ACCOUNT,
836
+ McpErrorCode.META_MISSING_PAYMENT,
837
+ McpErrorCode.META_TOKEN_EXPIRED,
838
+ McpErrorCode.META_ACCOUNT_RESTRICTED,
839
+ McpErrorCode.META_CAPABILITY_MISSING,
840
+ McpErrorCode.META_PERMISSION_DENIED,
841
+ McpErrorCode.META_PIXEL_NOT_FOUND,
842
+ McpErrorCode.META_OBJECT_NOT_FOUND,
843
+ McpErrorCode.META_AI_DISCLOSURE_REQUIRED,
844
+ McpErrorCode.META_BID_STRATEGY_REQUIRES_AMOUNT,
845
+ McpErrorCode.META_CBO_BUDGET_CONFLICT,
846
+ McpErrorCode.META_BUDGET_MODE_CONFLICT,
847
+ McpErrorCode.META_AD_SET_BUDGET_REQUIRED,
848
+ McpErrorCode.META_DAILY_BUDGET_TOO_HIGH,
849
+ McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
850
+ McpErrorCode.META_WHATSAPP_DESTINATION_DISABLED,
851
+ // TikTok
852
+ McpErrorCode.TIKTOK_NOT_CONNECTED,
853
+ McpErrorCode.TIKTOK_TOKEN_EXPIRED,
854
+ McpErrorCode.TIKTOK_PERMISSION_DENIED,
855
+ McpErrorCode.TIKTOK_MISSING_PAYMENT,
856
+ McpErrorCode.TIKTOK_PIXEL_NOT_FOUND,
857
+ McpErrorCode.TIKTOK_INVALID_ADVERTISER,
858
+ McpErrorCode.TIKTOK_ADVERTISER_REQUIRED,
859
+ McpErrorCode.TIKTOK_IDENTITY_REQUIRED,
860
+ McpErrorCode.TIKTOK_IDENTITY_API_REQUIRED,
861
+ McpErrorCode.TIKTOK_VIDEO_UPLOAD_FAILED,
862
+ McpErrorCode.TIKTOK_VIDEO_UPLOAD_TIMEOUT,
863
+ McpErrorCode.TIKTOK_RATE_LIMITED,
864
+ McpErrorCode.TIKTOK_SERVER_ERROR,
865
+ McpErrorCode.TIKTOK_INVALID_DATA,
866
+ // Campaign (shared)
867
+ McpErrorCode.CAMPAIGN_NOT_FOUND,
868
+ McpErrorCode.CAMPAIGN_TASK_NOT_FOUND,
869
+ McpErrorCode.CAMPAIGN_MISSING_LANDING_URL,
870
+ McpErrorCode.CAMPAIGN_NO_CREATIVES,
871
+ McpErrorCode.CAMPAIGN_MISSING_THUMBNAILS,
872
+ // Connections
873
+ McpErrorCode.NO_SHOPIFY_SHOPS,
874
+ McpErrorCode.NO_META_AD_ACCOUNTS,
875
+ // Auth
876
+ McpErrorCode.REVOKED_API_KEY
877
+ ]);
878
+ function mcp(code, message, extra = {}) {
879
+ const errCode = INVALID_PARAM_CODES.has(code) ? ErrorCode.InvalidParams : ErrorCode.InternalError;
880
+ return new McpError(errCode, message, {
881
+ code,
882
+ ...extra
883
+ });
884
+ }
885
+ __name(mcp, "mcp");
886
+ var GENERIC_BACKEND_MESSAGES = /* @__PURE__ */ new Set([
887
+ "No ads could be created for the campaign.",
888
+ "No assets could be created for the campaign.",
889
+ "Invalid data provided to Meta API",
890
+ "Insufficient permissions for Meta account",
891
+ "Meta session has expired",
892
+ "Meta Ads account has no payment method configured"
893
+ ]);
894
+ function looksLikeMeaningfulBackendMessage(message, mapped) {
895
+ if (!message || message.length === 0) return false;
896
+ if (GENERIC_BACKEND_MESSAGES.has(message)) return false;
897
+ const norm = /* @__PURE__ */ __name((s) => s.toLowerCase().replace(/[.\s]+$/, ""), "norm");
898
+ if (norm(mapped).includes(norm(message))) return false;
899
+ return true;
900
+ }
901
+ __name(looksLikeMeaningfulBackendMessage, "looksLikeMeaningfulBackendMessage");
902
+ function resolveMessage(code, backendMessage) {
903
+ const mapped = CODE_MESSAGES[code];
904
+ const backend = typeof backendMessage === "string" ? backendMessage.trim() : "";
905
+ if (mapped && backend && looksLikeMeaningfulBackendMessage(backend, mapped)) {
906
+ return `${mapped} (Detalle: ${backend})`;
907
+ }
908
+ if (mapped) return mapped;
909
+ if (backend) return backend;
910
+ return `El servicio devolvi\xF3 un error (c\xF3digo: ${code}). Intenta de nuevo en un momento o contacta soporte si persiste.`;
911
+ }
912
+ __name(resolveMessage, "resolveMessage");
913
+ function translateError(err) {
914
+ if (err instanceof ResolveImageError) {
915
+ return mcp(McpErrorCode[err.code], err.message);
916
+ }
917
+ if (err instanceof ApiHttpError) {
918
+ const body = isRecord(err.body) ? err.body : {};
919
+ if (typeof body.code === "string" && PASSTHROUGH_CODES.has(body.code)) {
920
+ const code = body.code;
921
+ return mcp(code, resolveMessage(code, body.message), {
922
+ ...body.helpUrl ? {
923
+ helpUrl: body.helpUrl
924
+ } : {}
925
+ });
926
+ }
927
+ if (err.status === 401) {
928
+ return mcp(McpErrorCode.INVALID_API_KEY, resolveMessage(McpErrorCode.INVALID_API_KEY, void 0));
929
+ }
930
+ if (err.status === 429) {
931
+ return mcp(McpErrorCode.RATE_LIMITED, resolveMessage(McpErrorCode.RATE_LIMITED, void 0), {
932
+ retryAfterSeconds: Number(body.retryAfterSeconds ?? 30)
933
+ });
934
+ }
935
+ if (err.status === 400) {
936
+ return mcp(McpErrorCode.INVALID_INPUT, typeof body.message === "string" ? body.message : resolveMessage(McpErrorCode.INVALID_INPUT, void 0));
937
+ }
938
+ if (err.status === 404) {
939
+ const isTask = body.resource === "task" || /task/i.test(String(body.message ?? ""));
940
+ return mcp(isTask ? McpErrorCode.TASK_NOT_FOUND : McpErrorCode.INVALID_INPUT, resolveMessage(isTask ? McpErrorCode.TASK_NOT_FOUND : McpErrorCode.INVALID_INPUT, void 0));
941
+ }
942
+ if (err.status === 409) {
943
+ return mcp(McpErrorCode.TASK_NOT_READY, resolveMessage(McpErrorCode.TASK_NOT_READY, void 0));
944
+ }
945
+ if (err.status >= 500) {
946
+ return mcp(McpErrorCode.UPSTREAM_UNAVAILABLE, resolveMessage(McpErrorCode.UPSTREAM_UNAVAILABLE, void 0));
947
+ }
948
+ return mcp(McpErrorCode.INTERNAL, `HTTP ${err.status}`);
949
+ }
950
+ if (err instanceof Error && /aborted|timeout/i.test(err.message)) {
951
+ return mcp(McpErrorCode.UPSTREAM_TIMEOUT, resolveMessage(McpErrorCode.UPSTREAM_TIMEOUT, void 0));
952
+ }
953
+ return mcp(McpErrorCode.INTERNAL, err instanceof Error ? err.message : resolveMessage(McpErrorCode.INTERNAL, void 0));
954
+ }
955
+ __name(translateError, "translateError");
956
+
718
957
  // src/asset-proxy.ts
719
958
  import { createHmac, timingSafeEqual } from "crypto";
720
959
  var TOKEN_TTL_SECONDS = 3600;
@@ -1333,7 +1572,11 @@ import { z as z6 } from "zod";
1333
1572
  var ShopifyInput = z6.object({
1334
1573
  taskId: z6.string().min(1),
1335
1574
  shopDomain: z6.string().min(1),
1336
- numLandingImages: z6.number().int().min(1).max(7).optional(),
1575
+ /** Override: number of landing images to include. Default = ALL images the
1576
+ * task generated (the MCP fetches /v1/results first to count). Bounded
1577
+ * generously here; the backend validates each index against
1578
+ * landing.images.length and rejects out-of-range. */
1579
+ numLandingImages: z6.number().int().min(1).max(20).optional(),
1337
1580
  variantIndex: z6.number().int().min(0).optional(),
1338
1581
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
1339
1582
  * When true, the landing renders variant-picker pills; when false, it ships
@@ -1366,10 +1609,12 @@ var TikTokInput = z6.object({
1366
1609
  campaignConfig: McpCampaignConfig.optional()
1367
1610
  });
1368
1611
  function registerPublishTools(server, api) {
1369
- server.tool("publish_to_shopify", "Publica la landing y producto generados en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. ANTES de llamar preg\xFAntale al usuario \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED \u2014 eso renderiza los pills de variantes en la landing. Por defecto incluye 3 im\xE1genes del hero. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
1612
+ server.tool("publish_to_shopify", "Publica la landing y producto generados en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. ANTES de llamar preg\xFAntale al usuario \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED \u2014 eso renderiza los pills de variantes en la landing. Por defecto publica TODAS las im\xE1genes que la tarea gener\xF3 para la landing; pasa `numLandingImages` s\xF3lo si el usuario te pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
1370
1613
  try {
1371
- const numImages = input.numLandingImages ?? 3;
1372
1614
  const variantIndex = input.variantIndex ?? 0;
1615
+ const availableImages = await fetchLandingImageCount(api, input.taskId);
1616
+ const effectiveAvailable = availableImages > 0 ? availableImages : 3;
1617
+ const numImages = Math.min(input.numLandingImages ?? effectiveAvailable, effectiveAvailable);
1373
1618
  const selectedLandingImages = Array.from({
1374
1619
  length: numImages
1375
1620
  }, (_, i) => ({
@@ -1446,6 +1691,19 @@ function renderShopifyPublishMarkdown(r) {
1446
1691
  };
1447
1692
  }
1448
1693
  __name(renderShopifyPublishMarkdown, "renderShopifyPublishMarkdown");
1694
+ async function fetchLandingImageCount(api, taskId) {
1695
+ try {
1696
+ const r = await api.getJson(`/v1/results/${encodeURIComponent(taskId)}`);
1697
+ if (!isRecord(r)) return 0;
1698
+ const landing = r.landing;
1699
+ if (!isRecord(landing)) return 0;
1700
+ const images = landing.images;
1701
+ return Array.isArray(images) ? images.length : 0;
1702
+ } catch {
1703
+ return 0;
1704
+ }
1705
+ }
1706
+ __name(fetchLandingImageCount, "fetchLandingImageCount");
1449
1707
  function toMetaCampaignOverrides(cfg) {
1450
1708
  const out = {};
1451
1709
  if (cfg.dailyBudgetUsd !== void 0) out.dailyBudgetUsd = cfg.dailyBudgetUsd;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "0.10.0",
3
+ "version": "0.11.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.",