@productmaker/mcp 3.4.2 → 3.4.3

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 +112 -25
  2. package/dist/stdio.js +112 -25
  3. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -4901,6 +4901,7 @@ var MetaCampaignType = z33.enum([
4901
4901
  "traffic",
4902
4902
  "engagement"
4903
4903
  ]);
4904
+ var MetaPublishAttemptId = z33.string().uuid();
4904
4905
  var MetaOptimizationMode = z33.enum([
4905
4906
  "advantage_plus",
4906
4907
  "manual"
@@ -5084,6 +5085,8 @@ var MetaCampaignDefaults = MetaCampaignConfigBase.omit({
5084
5085
  currency: z33.string().length(3).regex(/^[A-Z]{3}$/).nullable().optional()
5085
5086
  });
5086
5087
  var UploadToMetaRequest = z33.object({
5088
+ /** Caller-owned retry fence. Required before any provider operation. */
5089
+ publishAttemptId: MetaPublishAttemptId,
5087
5090
  /** Array of creatives to upload */
5088
5091
  creatives: z33.array(z33.object({
5089
5092
  creativeId: z33.string().min(1).optional(),
@@ -5240,6 +5243,8 @@ var MetaCampaignOverridesSchema = z34.object({
5240
5243
  destination: MetaCampaignDestination.optional()
5241
5244
  });
5242
5245
  var FacebookCreateCampaignBodySchema = z34.object({
5246
+ /** Caller-owned retry fence. Required before any provider operation. */
5247
+ publishAttemptId: MetaPublishAttemptId,
5243
5248
  adAccountId: MetaAdAccountId,
5244
5249
  portfolioId: z34.string().min(1).optional(),
5245
5250
  pageId: z34.string().min(1).optional(),
@@ -5309,6 +5314,8 @@ var FacebookCreateCampaignResponseSchema = z34.object({
5309
5314
  appliedBudget: z34.number().int().positive()
5310
5315
  });
5311
5316
  var FacebookCreateCampaignFromCreativesBodySchema = z34.object({
5317
+ /** Caller-owned retry fence. Required before any provider operation. */
5318
+ publishAttemptId: MetaPublishAttemptId,
5312
5319
  creatives: z34.array(z34.object({
5313
5320
  type: z34.enum([
5314
5321
  "image",
@@ -5456,6 +5463,8 @@ var FacebookCreativeSourceSchema = z34.discriminatedUnion("type", [
5456
5463
  FacebookMediaCreativeSourceSchema
5457
5464
  ]);
5458
5465
  var FacebookCreateCampaignFromTasksBodySchema = z34.object({
5466
+ /** Caller-owned retry fence. Required before any provider operation. */
5467
+ publishAttemptId: MetaPublishAttemptId,
5459
5468
  creatives: z34.array(z34.object({
5460
5469
  type: z34.enum([
5461
5470
  "image",
@@ -11678,7 +11687,7 @@ var ApiClient = class {
11678
11687
  };
11679
11688
 
11680
11689
  // src/index.ts
11681
- var MCP_VERSION = "3.4.2";
11690
+ var MCP_VERSION = "3.4.3";
11682
11691
 
11683
11692
  // src/meta-upload-app.ts
11684
11693
  import { readFileSync } from "fs";
@@ -12724,6 +12733,12 @@ var McpErrorCode = {
12724
12733
  META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
12725
12734
  META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
12726
12735
  META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
12736
+ META_STAGING_QUOTA_EXCEEDED: "META_STAGING_QUOTA_EXCEEDED",
12737
+ CAPACITY_UNAVAILABLE: "CAPACITY_UNAVAILABLE",
12738
+ UPLOAD_INCOMPLETE: "UPLOAD_INCOMPLETE",
12739
+ UPLOAD_EXPIRED: "UPLOAD_EXPIRED",
12740
+ META_UPLOAD_SESSION_EXPIRED: "META_UPLOAD_SESSION_EXPIRED",
12741
+ META_PUBLICATION_LEASE_STALE: "META_PUBLICATION_LEASE_STALE",
12727
12742
  META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
12728
12743
  META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
12729
12744
  META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
@@ -12785,8 +12800,14 @@ var CODE_MESSAGES = {
12785
12800
  META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
12786
12801
  META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
12787
12802
  META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
12788
- META_PUBLICATION_RECONCILIATION_REQUIRED: "A previous Meta publication with these exact inputs may already exist. Check Meta before retrying so you do not create a duplicate campaign.",
12789
- META_PUBLICATION_IN_PROGRESS: "A Meta publication with these exact inputs is already in progress. Wait for it to finish, then check the campaign status.",
12803
+ META_STAGING_QUOTA_EXCEEDED: "The temporary Meta staging quota was reached. Keep completed files and retry only pending files after retryAt.",
12804
+ CAPACITY_UNAVAILABLE: "Temporary Meta staging capacity is unavailable. Keep completed files and retry only pending files later.",
12805
+ UPLOAD_INCOMPLETE: "A temporary Meta upload is incomplete. Keep completed files and retry only pending files.",
12806
+ UPLOAD_EXPIRED: "The temporary Meta upload expired. Re-upload this file before publishing.",
12807
+ META_UPLOAD_SESSION_EXPIRED: "The Meta upload session expired. Open a new session and retry only pending files.",
12808
+ META_PUBLICATION_LEASE_STALE: "The Meta publication lease expired or was replaced. Do not continue this stale publication; start a new explicit attempt only after confirming Meta did not receive it.",
12809
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "Meta may already have received this publication. Do not resend it; check Meta Ads Manager.",
12810
+ META_PUBLICATION_IN_PROGRESS: "This Meta publication is already in progress. Do not resend it.",
12790
12811
  META_NO_ADS_CREATED: "No ads could be created. Most likely: the Meta Ads account has no payment method, no Facebook Page is linked, or the Facebook app lost permissions. Verify all three at https://business.facebook.com.",
12791
12812
  META_NO_ASSETS_READY: "The task has no videos or ad creative ready to publish yet. Wait for generation to finish or regenerate the assets.",
12792
12813
  META_NO_THUMBNAIL_SOURCE: "No image is available to use as the video thumbnail. Upload a product image or generate the ad creative before publishing.",
@@ -12935,6 +12956,11 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
12935
12956
  McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
12936
12957
  McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
12937
12958
  McpErrorCode.META_STAGED_ASSET_INVALID,
12959
+ McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
12960
+ McpErrorCode.UPLOAD_INCOMPLETE,
12961
+ McpErrorCode.UPLOAD_EXPIRED,
12962
+ McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
12963
+ McpErrorCode.META_PUBLICATION_LEASE_STALE,
12938
12964
  McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
12939
12965
  McpErrorCode.META_PUBLICATION_IN_PROGRESS,
12940
12966
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
@@ -12994,6 +13020,14 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
12994
13020
  McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
12995
13021
  McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
12996
13022
  McpErrorCode.META_INVALID_CTA,
13023
+ McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
13024
+ McpErrorCode.CAPACITY_UNAVAILABLE,
13025
+ McpErrorCode.UPLOAD_INCOMPLETE,
13026
+ McpErrorCode.UPLOAD_EXPIRED,
13027
+ McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
13028
+ McpErrorCode.META_PUBLICATION_LEASE_STALE,
13029
+ McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
13030
+ McpErrorCode.META_PUBLICATION_IN_PROGRESS,
12997
13031
  // TikTok
12998
13032
  McpErrorCode.TIKTOK_NOT_CONNECTED,
12999
13033
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
@@ -13082,6 +13116,10 @@ function translateError(err) {
13082
13116
  if (body.helpUrl !== void 0) extra.helpUrl = body.helpUrl;
13083
13117
  if (typeof body.requiredCredits === "number") extra.requiredCredits = body.requiredCredits;
13084
13118
  if (typeof body.availableCredits === "number") extra.availableCredits = body.availableCredits;
13119
+ if (typeof body.dimension === "string") extra.dimension = body.dimension;
13120
+ if (typeof body.usage === "number") extra.usage = body.usage;
13121
+ if (typeof body.limit === "number") extra.limit = body.limit;
13122
+ if (typeof body.retryAt === "string") extra.retryAt = body.retryAt;
13085
13123
  return mcp(code, resolveMessage(code, body.message), extra);
13086
13124
  }
13087
13125
  if (err.status === 401) {
@@ -14755,6 +14793,57 @@ __name(registerActorTools, "registerActorTools");
14755
14793
 
14756
14794
  // src/tools/publish.ts
14757
14795
  import { z as z56 } from "zod";
14796
+
14797
+ // src/tools/stable-uuid.ts
14798
+ import { createHash as createHash3 } from "crypto";
14799
+ function canonicalize(value) {
14800
+ if (Array.isArray(value)) return value.map(canonicalize);
14801
+ if (value === null || typeof value !== "object") return value;
14802
+ const record = value;
14803
+ return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
14804
+ key,
14805
+ canonicalize(record[key])
14806
+ ]));
14807
+ }
14808
+ __name(canonicalize, "canonicalize");
14809
+ function canonicalJson(value) {
14810
+ return JSON.stringify(canonicalize(value));
14811
+ }
14812
+ __name(canonicalJson, "canonicalJson");
14813
+ function deriveUuidV5(name, namespace) {
14814
+ const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
14815
+ const digest = createHash3("sha1").update(namespaceBytes).update(name, "utf8").digest();
14816
+ const bytes = Buffer.from(digest.subarray(0, 16));
14817
+ bytes[6] = (bytes[6] ?? 0) & 15 | 80;
14818
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
14819
+ const hex = bytes.toString("hex");
14820
+ return [
14821
+ hex.slice(0, 8),
14822
+ hex.slice(8, 12),
14823
+ hex.slice(12, 16),
14824
+ hex.slice(16, 20),
14825
+ hex.slice(20)
14826
+ ].join("-");
14827
+ }
14828
+ __name(deriveUuidV5, "deriveUuidV5");
14829
+
14830
+ // src/tools/meta-publish-attempt-id.ts
14831
+ var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
14832
+ function deriveMetaPublishAttemptId(input) {
14833
+ const operationIdentity = canonicalJson([
14834
+ input.fingerprint,
14835
+ input.toolName,
14836
+ [
14837
+ typeof input.requestId,
14838
+ input.requestId
14839
+ ],
14840
+ input.canonicalInput
14841
+ ]);
14842
+ return deriveUuidV5(operationIdentity, META_PUBLISH_NAMESPACE);
14843
+ }
14844
+ __name(deriveMetaPublishAttemptId, "deriveMetaPublishAttemptId");
14845
+
14846
+ // src/tools/publish.ts
14758
14847
  var ShopifyInput = z56.object({
14759
14848
  taskId: z56.string().min(1),
14760
14849
  shopDomain: z56.string().min(1),
@@ -15082,10 +15171,19 @@ function registerPublishTools(server, api, opts = {}) {
15082
15171
  throw translateError(e);
15083
15172
  }
15084
15173
  });
15085
- server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, CREATES_EXTERNAL, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
15174
+ server.tool("publish_to_meta", "Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.", MetaInput.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
15086
15175
  try {
15176
+ const parsed = MetaInput.safeParse(rawInput);
15177
+ if (!parsed.success) throw invalidInputFromZod(parsed.error);
15178
+ const { taskId, campaignConfig, primaryText, phoneNumber, ...rest } = parsed.data;
15087
15179
  const body = {
15088
- ...rest
15180
+ ...rest,
15181
+ publishAttemptId: deriveMetaPublishAttemptId({
15182
+ fingerprint: api.fingerprint(),
15183
+ toolName: "publish_to_meta",
15184
+ requestId: extra.requestId,
15185
+ canonicalInput: parsed.data
15186
+ })
15089
15187
  };
15090
15188
  if (campaignConfig) {
15091
15189
  body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
@@ -15110,7 +15208,7 @@ function registerPublishTools(server, api, opts = {}) {
15110
15208
  throw translateError(e);
15111
15209
  }
15112
15210
  });
15113
- server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, publica s\xF3lo los assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla, quedan durante el TTL para reintento. 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, CREATES_EXTERNAL, async (rawInput) => {
15211
+ server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, conserva los assets confirmados, procesa s\xF3lo los pendientes y publica \xFAnicamente assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla antes de enviar una mutaci\xF3n a Meta, quedan durante el TTL. 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. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.', PublishCreativesToMetaInputBase.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
15114
15212
  try {
15115
15213
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
15116
15214
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -15190,7 +15288,13 @@ function registerPublishTools(server, api, opts = {}) {
15190
15288
  landingUrl: input.landingUrl,
15191
15289
  targetCountry: input.targetCountry,
15192
15290
  campaignName: input.campaignName,
15193
- campaignConfig: input.campaignConfig
15291
+ campaignConfig: input.campaignConfig,
15292
+ publishAttemptId: deriveMetaPublishAttemptId({
15293
+ fingerprint: api.fingerprint(),
15294
+ toolName: "publish_creatives_to_meta",
15295
+ requestId: extra.requestId,
15296
+ canonicalInput: input
15297
+ })
15194
15298
  });
15195
15299
  return asText({
15196
15300
  ...r,
@@ -16077,29 +16181,12 @@ __name(formatPercent, "formatPercent");
16077
16181
  import { z as z62 } from "zod";
16078
16182
 
16079
16183
  // src/tools/product-research-idempotency.ts
16080
- import { createHash as createHash3 } from "crypto";
16081
16184
  var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
16082
16185
  var NAMESPACE = "6f9d3a1e-8c47-4b52-9f10-2d7e5a3c8b64";
16083
- function uuidV5(name, namespace) {
16084
- const namespaceBytes = Buffer.from(namespace.replace(/-/g, ""), "hex");
16085
- const digest = createHash3("sha1").update(namespaceBytes).update(name, "utf8").digest();
16086
- const bytes = Buffer.from(digest.subarray(0, 16));
16087
- bytes[6] = (bytes[6] ?? 0) & 15 | 80;
16088
- bytes[8] = (bytes[8] ?? 0) & 63 | 128;
16089
- const hex = bytes.toString("hex");
16090
- return [
16091
- hex.slice(0, 8),
16092
- hex.slice(8, 12),
16093
- hex.slice(12, 16),
16094
- hex.slice(16, 20),
16095
- hex.slice(20)
16096
- ].join("-");
16097
- }
16098
- __name(uuidV5, "uuidV5");
16099
16186
  function deriveIdempotencyKey(input) {
16100
16187
  const window = Math.floor(input.nowMs / RESEARCH_IDEMPOTENCY_WINDOW_MS);
16101
16188
  const name = `${input.fingerprint}:${input.targetCountry}:${input.categoryKey}:${window}`;
16102
- return uuidV5(name, NAMESPACE);
16189
+ return deriveUuidV5(name, NAMESPACE);
16103
16190
  }
16104
16191
  __name(deriveIdempotencyKey, "deriveIdempotencyKey");
16105
16192
 
package/dist/stdio.js CHANGED
@@ -264,7 +264,7 @@ var ApiClient = class {
264
264
  };
265
265
 
266
266
  // src/index.ts
267
- var MCP_VERSION = "3.4.2";
267
+ var MCP_VERSION = "3.4.3";
268
268
 
269
269
  // src/meta-upload-app.ts
270
270
  import { readFileSync } from "fs";
@@ -5523,6 +5523,7 @@ var MetaCampaignType = z33.enum([
5523
5523
  "traffic",
5524
5524
  "engagement"
5525
5525
  ]);
5526
+ var MetaPublishAttemptId = z33.string().uuid();
5526
5527
  var MetaOptimizationMode = z33.enum([
5527
5528
  "advantage_plus",
5528
5529
  "manual"
@@ -5706,6 +5707,8 @@ var MetaCampaignDefaults = MetaCampaignConfigBase.omit({
5706
5707
  currency: z33.string().length(3).regex(/^[A-Z]{3}$/).nullable().optional()
5707
5708
  });
5708
5709
  var UploadToMetaRequest = z33.object({
5710
+ /** Caller-owned retry fence. Required before any provider operation. */
5711
+ publishAttemptId: MetaPublishAttemptId,
5709
5712
  /** Array of creatives to upload */
5710
5713
  creatives: z33.array(z33.object({
5711
5714
  creativeId: z33.string().min(1).optional(),
@@ -5862,6 +5865,8 @@ var MetaCampaignOverridesSchema = z34.object({
5862
5865
  destination: MetaCampaignDestination.optional()
5863
5866
  });
5864
5867
  var FacebookCreateCampaignBodySchema = z34.object({
5868
+ /** Caller-owned retry fence. Required before any provider operation. */
5869
+ publishAttemptId: MetaPublishAttemptId,
5865
5870
  adAccountId: MetaAdAccountId,
5866
5871
  portfolioId: z34.string().min(1).optional(),
5867
5872
  pageId: z34.string().min(1).optional(),
@@ -5931,6 +5936,8 @@ var FacebookCreateCampaignResponseSchema = z34.object({
5931
5936
  appliedBudget: z34.number().int().positive()
5932
5937
  });
5933
5938
  var FacebookCreateCampaignFromCreativesBodySchema = z34.object({
5939
+ /** Caller-owned retry fence. Required before any provider operation. */
5940
+ publishAttemptId: MetaPublishAttemptId,
5934
5941
  creatives: z34.array(z34.object({
5935
5942
  type: z34.enum([
5936
5943
  "image",
@@ -6078,6 +6085,8 @@ var FacebookCreativeSourceSchema = z34.discriminatedUnion("type", [
6078
6085
  FacebookMediaCreativeSourceSchema
6079
6086
  ]);
6080
6087
  var FacebookCreateCampaignFromTasksBodySchema = z34.object({
6088
+ /** Caller-owned retry fence. Required before any provider operation. */
6089
+ publishAttemptId: MetaPublishAttemptId,
6081
6090
  creatives: z34.array(z34.object({
6082
6091
  type: z34.enum([
6083
6092
  "image",
@@ -11473,6 +11482,12 @@ var McpErrorCode = {
11473
11482
  META_STAGED_ASSET_AMBIGUOUS: "META_STAGED_ASSET_AMBIGUOUS",
11474
11483
  META_STAGED_ASSET_TYPE_MISMATCH: "META_STAGED_ASSET_TYPE_MISMATCH",
11475
11484
  META_STAGED_ASSET_INVALID: "META_STAGED_ASSET_INVALID",
11485
+ META_STAGING_QUOTA_EXCEEDED: "META_STAGING_QUOTA_EXCEEDED",
11486
+ CAPACITY_UNAVAILABLE: "CAPACITY_UNAVAILABLE",
11487
+ UPLOAD_INCOMPLETE: "UPLOAD_INCOMPLETE",
11488
+ UPLOAD_EXPIRED: "UPLOAD_EXPIRED",
11489
+ META_UPLOAD_SESSION_EXPIRED: "META_UPLOAD_SESSION_EXPIRED",
11490
+ META_PUBLICATION_LEASE_STALE: "META_PUBLICATION_LEASE_STALE",
11476
11491
  META_PUBLICATION_RECONCILIATION_REQUIRED: "META_PUBLICATION_RECONCILIATION_REQUIRED",
11477
11492
  META_PUBLICATION_IN_PROGRESS: "META_PUBLICATION_IN_PROGRESS",
11478
11493
  META_NO_ADS_CREATED: "META_NO_ADS_CREATED",
@@ -11534,8 +11549,14 @@ var CODE_MESSAGES = {
11534
11549
  META_STAGED_ASSET_AMBIGUOUS: "The temporary media upload could not be uniquely resolved. Re-upload the media and retry the Meta publication.",
11535
11550
  META_STAGED_ASSET_TYPE_MISMATCH: "The temporary media type does not match the creative. Re-upload the correct image or video and retry.",
11536
11551
  META_STAGED_ASSET_INVALID: "The temporary media is empty or exceeds the server size limit. Re-upload the file and retry.",
11537
- META_PUBLICATION_RECONCILIATION_REQUIRED: "A previous Meta publication with these exact inputs may already exist. Check Meta before retrying so you do not create a duplicate campaign.",
11538
- META_PUBLICATION_IN_PROGRESS: "A Meta publication with these exact inputs is already in progress. Wait for it to finish, then check the campaign status.",
11552
+ META_STAGING_QUOTA_EXCEEDED: "The temporary Meta staging quota was reached. Keep completed files and retry only pending files after retryAt.",
11553
+ CAPACITY_UNAVAILABLE: "Temporary Meta staging capacity is unavailable. Keep completed files and retry only pending files later.",
11554
+ UPLOAD_INCOMPLETE: "A temporary Meta upload is incomplete. Keep completed files and retry only pending files.",
11555
+ UPLOAD_EXPIRED: "The temporary Meta upload expired. Re-upload this file before publishing.",
11556
+ META_UPLOAD_SESSION_EXPIRED: "The Meta upload session expired. Open a new session and retry only pending files.",
11557
+ META_PUBLICATION_LEASE_STALE: "The Meta publication lease expired or was replaced. Do not continue this stale publication; start a new explicit attempt only after confirming Meta did not receive it.",
11558
+ META_PUBLICATION_RECONCILIATION_REQUIRED: "Meta may already have received this publication. Do not resend it; check Meta Ads Manager.",
11559
+ META_PUBLICATION_IN_PROGRESS: "This Meta publication is already in progress. Do not resend it.",
11539
11560
  META_NO_ADS_CREATED: "No ads could be created. Most likely: the Meta Ads account has no payment method, no Facebook Page is linked, or the Facebook app lost permissions. Verify all three at https://business.facebook.com.",
11540
11561
  META_NO_ASSETS_READY: "The task has no videos or ad creative ready to publish yet. Wait for generation to finish or regenerate the assets.",
11541
11562
  META_NO_THUMBNAIL_SOURCE: "No image is available to use as the video thumbnail. Upload a product image or generate the ad creative before publishing.",
@@ -11684,6 +11705,11 @@ var INVALID_PARAM_CODES = /* @__PURE__ */ new Set([
11684
11705
  McpErrorCode.META_STAGED_ASSET_AMBIGUOUS,
11685
11706
  McpErrorCode.META_STAGED_ASSET_TYPE_MISMATCH,
11686
11707
  McpErrorCode.META_STAGED_ASSET_INVALID,
11708
+ McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
11709
+ McpErrorCode.UPLOAD_INCOMPLETE,
11710
+ McpErrorCode.UPLOAD_EXPIRED,
11711
+ McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
11712
+ McpErrorCode.META_PUBLICATION_LEASE_STALE,
11687
11713
  McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
11688
11714
  McpErrorCode.META_PUBLICATION_IN_PROGRESS,
11689
11715
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
@@ -11743,6 +11769,14 @@ var PASSTHROUGH_CODES = /* @__PURE__ */ new Set([
11743
11769
  McpErrorCode.META_CAMPAIGN_CONFIG_INVALID,
11744
11770
  McpErrorCode.META_WHATSAPP_INVALID_NUMBER,
11745
11771
  McpErrorCode.META_INVALID_CTA,
11772
+ McpErrorCode.META_STAGING_QUOTA_EXCEEDED,
11773
+ McpErrorCode.CAPACITY_UNAVAILABLE,
11774
+ McpErrorCode.UPLOAD_INCOMPLETE,
11775
+ McpErrorCode.UPLOAD_EXPIRED,
11776
+ McpErrorCode.META_UPLOAD_SESSION_EXPIRED,
11777
+ McpErrorCode.META_PUBLICATION_LEASE_STALE,
11778
+ McpErrorCode.META_PUBLICATION_RECONCILIATION_REQUIRED,
11779
+ McpErrorCode.META_PUBLICATION_IN_PROGRESS,
11746
11780
  // TikTok
11747
11781
  McpErrorCode.TIKTOK_NOT_CONNECTED,
11748
11782
  McpErrorCode.TIKTOK_TOKEN_EXPIRED,
@@ -11831,6 +11865,10 @@ function translateError(err) {
11831
11865
  if (body.helpUrl !== void 0) extra.helpUrl = body.helpUrl;
11832
11866
  if (typeof body.requiredCredits === "number") extra.requiredCredits = body.requiredCredits;
11833
11867
  if (typeof body.availableCredits === "number") extra.availableCredits = body.availableCredits;
11868
+ if (typeof body.dimension === "string") extra.dimension = body.dimension;
11869
+ if (typeof body.usage === "number") extra.usage = body.usage;
11870
+ if (typeof body.limit === "number") extra.limit = body.limit;
11871
+ if (typeof body.retryAt === "string") extra.retryAt = body.retryAt;
11834
11872
  return mcp(code, resolveMessage(code, body.message), extra);
11835
11873
  }
11836
11874
  if (err.status === 401) {
@@ -13557,6 +13595,57 @@ __name(registerActorTools, "registerActorTools");
13557
13595
 
13558
13596
  // src/tools/publish.ts
13559
13597
  import { z as z55 } from "zod";
13598
+
13599
+ // src/tools/stable-uuid.ts
13600
+ import { createHash as createHash2 } from "crypto";
13601
+ function canonicalize(value) {
13602
+ if (Array.isArray(value)) return value.map(canonicalize);
13603
+ if (value === null || typeof value !== "object") return value;
13604
+ const record = value;
13605
+ return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
13606
+ key,
13607
+ canonicalize(record[key])
13608
+ ]));
13609
+ }
13610
+ __name(canonicalize, "canonicalize");
13611
+ function canonicalJson(value) {
13612
+ return JSON.stringify(canonicalize(value));
13613
+ }
13614
+ __name(canonicalJson, "canonicalJson");
13615
+ function deriveUuidV5(name, namespace) {
13616
+ const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
13617
+ const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
13618
+ const bytes = Buffer.from(digest.subarray(0, 16));
13619
+ bytes[6] = (bytes[6] ?? 0) & 15 | 80;
13620
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
13621
+ const hex = bytes.toString("hex");
13622
+ return [
13623
+ hex.slice(0, 8),
13624
+ hex.slice(8, 12),
13625
+ hex.slice(12, 16),
13626
+ hex.slice(16, 20),
13627
+ hex.slice(20)
13628
+ ].join("-");
13629
+ }
13630
+ __name(deriveUuidV5, "deriveUuidV5");
13631
+
13632
+ // src/tools/meta-publish-attempt-id.ts
13633
+ var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
13634
+ function deriveMetaPublishAttemptId(input) {
13635
+ const operationIdentity = canonicalJson([
13636
+ input.fingerprint,
13637
+ input.toolName,
13638
+ [
13639
+ typeof input.requestId,
13640
+ input.requestId
13641
+ ],
13642
+ input.canonicalInput
13643
+ ]);
13644
+ return deriveUuidV5(operationIdentity, META_PUBLISH_NAMESPACE);
13645
+ }
13646
+ __name(deriveMetaPublishAttemptId, "deriveMetaPublishAttemptId");
13647
+
13648
+ // src/tools/publish.ts
13560
13649
  var ShopifyInput = z55.object({
13561
13650
  taskId: z55.string().min(1),
13562
13651
  shopDomain: z55.string().min(1),
@@ -13884,10 +13973,19 @@ function registerPublishTools(server, api, opts = {}) {
13884
13973
  throw translateError(e);
13885
13974
  }
13886
13975
  });
13887
- server.tool("publish_to_meta", "Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`.", MetaInput.shape, CREATES_EXTERNAL, async ({ taskId, campaignConfig, primaryText, phoneNumber, ...rest }) => {
13976
+ server.tool("publish_to_meta", "Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Crea una campa\xF1a de Meta Ads (Facebook/Instagram) desde UNA sola tarea. Antes llama `list_meta_destinations`, deja que el usuario elija el `portfolioId` (Business portfolio), el `adAccountId` y el `pageId` exactos, y env\xEDa esos tres valores; no adivines ni uses la primera p\xE1gina. Si quieres juntar creativos de VARIAS tareas usa `publish_creatives_to_meta`. La campa\xF1a se crea SIEMPRE PAUSED y el usuario la activa manualmente. `pixelId` es opcional. `angleIndexes` permite seleccionar \xE1ngulos. `campaignConfig` admite presupuesto en USD, pa\xEDses ISO-2 y nombre. `primaryText` permite reemplazar el copy y `phoneNumber` configura WhatsApp. Despu\xE9s usa `get_meta_campaign_status`. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.", MetaInput.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
13888
13977
  try {
13978
+ const parsed = MetaInput.safeParse(rawInput);
13979
+ if (!parsed.success) throw invalidInputFromZod(parsed.error);
13980
+ const { taskId, campaignConfig, primaryText, phoneNumber, ...rest } = parsed.data;
13889
13981
  const body = {
13890
- ...rest
13982
+ ...rest,
13983
+ publishAttemptId: deriveMetaPublishAttemptId({
13984
+ fingerprint: api.fingerprint(),
13985
+ toolName: "publish_to_meta",
13986
+ requestId: extra.requestId,
13987
+ canonicalInput: parsed.data
13988
+ })
13891
13989
  };
13892
13990
  if (campaignConfig) {
13893
13991
  body.campaignOverrides = toMetaCampaignOverrides(campaignConfig);
@@ -13912,7 +14010,7 @@ function registerPublishTools(server, api, opts = {}) {
13912
14010
  throw translateError(e);
13913
14011
  }
13914
14012
  });
13915
- server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, publica s\xF3lo los assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla, quedan durante el TTL para reintento. 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, CREATES_EXTERNAL, async (rawInput) => {
14013
+ server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Publica tus creativos en Meta sin gastar cr\xE9ditos de ProductMaker. El presupuesto de anuncios se paga directamente desde tu cuenta de Meta. Crear creativos o copy con IA sigue usando cr\xE9ditos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, conserva los assets confirmados, procesa s\xF3lo los pendientes y publica \xFAnicamente assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla antes de enviar una mutaci\xF3n a Meta, quedan durante el TTL. 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. Si el resultado es ambiguo, NO reenv\xEDes: revisa Meta Ads Manager.', PublishCreativesToMetaInputBase.shape, CREATES_EXTERNAL, async (rawInput, extra) => {
13916
14014
  try {
13917
14015
  const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
13918
14016
  if (!parsed.success) throw invalidInputFromZod(parsed.error);
@@ -13992,7 +14090,13 @@ function registerPublishTools(server, api, opts = {}) {
13992
14090
  landingUrl: input.landingUrl,
13993
14091
  targetCountry: input.targetCountry,
13994
14092
  campaignName: input.campaignName,
13995
- campaignConfig: input.campaignConfig
14093
+ campaignConfig: input.campaignConfig,
14094
+ publishAttemptId: deriveMetaPublishAttemptId({
14095
+ fingerprint: api.fingerprint(),
14096
+ toolName: "publish_creatives_to_meta",
14097
+ requestId: extra.requestId,
14098
+ canonicalInput: input
14099
+ })
13996
14100
  });
13997
14101
  return asText({
13998
14102
  ...r,
@@ -14879,29 +14983,12 @@ __name(formatPercent, "formatPercent");
14879
14983
  import { z as z61 } from "zod";
14880
14984
 
14881
14985
  // src/tools/product-research-idempotency.ts
14882
- import { createHash as createHash2 } from "crypto";
14883
14986
  var RESEARCH_IDEMPOTENCY_WINDOW_MS = 2 * 60 * 60 * 1e3;
14884
14987
  var NAMESPACE = "6f9d3a1e-8c47-4b52-9f10-2d7e5a3c8b64";
14885
- function uuidV5(name, namespace) {
14886
- const namespaceBytes = Buffer.from(namespace.replace(/-/g, ""), "hex");
14887
- const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
14888
- const bytes = Buffer.from(digest.subarray(0, 16));
14889
- bytes[6] = (bytes[6] ?? 0) & 15 | 80;
14890
- bytes[8] = (bytes[8] ?? 0) & 63 | 128;
14891
- const hex = bytes.toString("hex");
14892
- return [
14893
- hex.slice(0, 8),
14894
- hex.slice(8, 12),
14895
- hex.slice(12, 16),
14896
- hex.slice(16, 20),
14897
- hex.slice(20)
14898
- ].join("-");
14899
- }
14900
- __name(uuidV5, "uuidV5");
14901
14988
  function deriveIdempotencyKey(input) {
14902
14989
  const window = Math.floor(input.nowMs / RESEARCH_IDEMPOTENCY_WINDOW_MS);
14903
14990
  const name = `${input.fingerprint}:${input.targetCountry}:${input.categoryKey}:${window}`;
14904
- return uuidV5(name, NAMESPACE);
14991
+ return deriveUuidV5(name, NAMESPACE);
14905
14992
  }
14906
14993
  __name(deriveIdempotencyKey, "deriveIdempotencyKey");
14907
14994
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "3.4.2",
3
+ "version": "3.4.3",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Product Maker MCP server — cost products, generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from Claude, ChatGPT, or Gemini.",