@productmaker/mcp 8.2.0 → 8.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/stdio.js CHANGED
@@ -1852,6 +1852,9 @@ var VideoCreativeJobStatus = z14.object({
1852
1852
  var VideoCreativeJobPayload = z14.object({
1853
1853
  jobId: z14.string().optional(),
1854
1854
  status: VideoCreativeJobStatusEnum.optional(),
1855
+ /** True when the submission replayed an existing task instead of billing
1856
+ * new work — callers surface the twin verbatim. Omitted on fresh creates. */
1857
+ replayed: z14.boolean().optional(),
1855
1858
  videoStructure: VideoStructure.optional(),
1856
1859
  videoQuality: VideoQuality.optional(),
1857
1860
  videoRouteId: VideoRouteId.optional(),
@@ -1945,7 +1948,8 @@ var ImageCreativeJobStatus = z14.object({
1945
1948
  });
1946
1949
  var ImageCreativeJobPayload = z14.object({
1947
1950
  jobId: z14.string().uuid(),
1948
- status: ImageCreativeJobStatusEnum
1951
+ status: ImageCreativeJobStatusEnum,
1952
+ replayed: z14.boolean().optional()
1949
1953
  });
1950
1954
  var ImageCreativeHistoryEntry = z14.object({
1951
1955
  id: z14.string().uuid(),
@@ -1972,7 +1976,8 @@ var LandingCreativeJobStatus = z14.object({
1972
1976
  });
1973
1977
  var LandingCreativeJobPayload = z14.object({
1974
1978
  jobId: z14.string().uuid(),
1975
- status: LandingCreativeJobStatusEnum
1979
+ status: LandingCreativeJobStatusEnum,
1980
+ replayed: z14.boolean().optional()
1976
1981
  });
1977
1982
  var LandingCreativeHistoryEntry = z14.object({
1978
1983
  id: z14.string().uuid(),
@@ -5643,6 +5648,10 @@ var CreateShopifySubscriptionRequest = z28.object({
5643
5648
  var CreateShopifyBillingChargeRequest = z28.object({
5644
5649
  packId: z28.string().min(1)
5645
5650
  });
5651
+ var ShopifyBillingCallbackQuery = z28.object({
5652
+ charge_id: z28.string().regex(/^\d+$/),
5653
+ transactionId: z28.string().uuid()
5654
+ });
5646
5655
  var CancelShopifySubscriptionRequest = z28.object({
5647
5656
  subscriptionId: z28.string().min(1)
5648
5657
  });
@@ -10870,6 +10879,7 @@ var MixSceneResult = z48.object({
10870
10879
  index: z48.number().int().positive(),
10871
10880
  status: z48.enum([
10872
10881
  "queued",
10882
+ "submitting",
10873
10883
  "submitted",
10874
10884
  "done",
10875
10885
  "failed"
@@ -13468,7 +13478,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
13468
13478
  ], [
13469
13479
  "product_costing"
13470
13480
  ]),
13471
- defineCapability("create_creative_variant", "6a777fbe7ad17966d16502e8872e15e4db2dcfacdfd2992d24853340d4f22a00", "credit_consuming", "creative", [
13481
+ defineCapability("create_creative_variant", "3e02399a7785df328f1c4d63cfbb6d7481abaac9f91d4a5cf51d410e657d63aa", "credit_consuming", "creative", [
13472
13482
  "credit_consuming",
13473
13483
  "state_change"
13474
13484
  ], [
@@ -13718,7 +13728,9 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
13718
13728
  // MCP 7.0.0 (Task 11, .agent/plans/2026-09-15-model-agnostic-video-routes.md):
13719
13729
  // `freeProModel` retired in favor of `modelId` (registry ids). Recomputed
13720
13730
  // from `pnpm --filter @productmaker/mcp test` printing the real digest.
13721
- "ead1f563a1915452157628d023ba203ecfa8f502a48259a5b5b15be4c84bf768",
13731
+ // Re-baselined 2026-09-26: `idempotencyKey` field description corrected —
13732
+ // paid tools ignore caller-provided keys and derive their own semantic key.
13733
+ "35616a32a42a0bc062218fdfbb4cc7f4ecfa814571fb86b4f11ef9e4184855e1",
13722
13734
  "credit_consuming",
13723
13735
  "creative",
13724
13736
  [
@@ -15980,7 +15992,10 @@ var ApiClient = class {
15980
15992
  method: "POST",
15981
15993
  headers: {
15982
15994
  ...authHeaders,
15983
- "Content-Type": "application/json"
15995
+ "Content-Type": "application/json",
15996
+ ...options?.idempotencyKey ? {
15997
+ "Idempotency-Key": options.idempotencyKey
15998
+ } : {}
15984
15999
  },
15985
16000
  body: JSON.stringify(body)
15986
16001
  }, options);
@@ -16102,7 +16117,7 @@ var ApiClient = class {
16102
16117
  };
16103
16118
 
16104
16119
  // src/index.ts
16105
- var MCP_VERSION = "8.2.0";
16120
+ var MCP_VERSION = "8.3.1";
16106
16121
 
16107
16122
  // src/meta-upload-app.ts
16108
16123
  import { readFileSync } from "fs";
@@ -17086,8 +17101,13 @@ async function submitCreative(api, opts) {
17086
17101
  const taskId = r.taskId ?? r.jobId;
17087
17102
  return asText({
17088
17103
  taskId,
17089
- status: "queued",
17090
- estimatedSeconds: opts.estimatedSeconds
17104
+ // Replay-aware: a keyed retry may land on a task that is already
17105
+ // processing/done — report the real status, not a hopeful "queued".
17106
+ status: r.status ?? "queued",
17107
+ estimatedSeconds: opts.estimatedSeconds,
17108
+ ...r.replayed === true ? {
17109
+ replayed: true
17110
+ } : {}
17091
17111
  });
17092
17112
  }
17093
17113
  __name(submitCreative, "submitCreative");
@@ -18504,6 +18524,170 @@ ${summary}`,
18504
18524
  }
18505
18525
  __name(requirePaidCreativeConfirmation, "requirePaidCreativeConfirmation");
18506
18526
 
18527
+ // src/tools/stable-uuid.ts
18528
+ import { createHash as createHash2 } from "crypto";
18529
+ function canonicalize(value) {
18530
+ if (Array.isArray(value)) return value.map(canonicalize);
18531
+ if (value === null || typeof value !== "object") return value;
18532
+ const record = value;
18533
+ return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
18534
+ key,
18535
+ canonicalize(record[key])
18536
+ ]));
18537
+ }
18538
+ __name(canonicalize, "canonicalize");
18539
+ function canonicalJson(value) {
18540
+ return JSON.stringify(canonicalize(value));
18541
+ }
18542
+ __name(canonicalJson, "canonicalJson");
18543
+ function deriveUuidV5(name, namespace) {
18544
+ const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
18545
+ const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
18546
+ const bytes = Buffer.from(digest.subarray(0, 16));
18547
+ bytes[6] = (bytes[6] ?? 0) & 15 | 80;
18548
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
18549
+ const hex = bytes.toString("hex");
18550
+ return [
18551
+ hex.slice(0, 8),
18552
+ hex.slice(8, 12),
18553
+ hex.slice(12, 16),
18554
+ hex.slice(16, 20),
18555
+ hex.slice(20)
18556
+ ].join("-");
18557
+ }
18558
+ __name(deriveUuidV5, "deriveUuidV5");
18559
+
18560
+ // src/tools/request-key.ts
18561
+ var PAID_REQUEST_KEY_WINDOW_MS = 15 * 60 * 1e3;
18562
+ var PAID_REQUEST_KEY_NAMESPACE = "8f3d6b1c-2a47-4e5b-9c0d-6e7f8a9b0c1d";
18563
+ var PARAPHRASE_PRONE_FIELDS = /* @__PURE__ */ new Set([
18564
+ "additionalContext",
18565
+ "productContext",
18566
+ "scriptNotes",
18567
+ "creativeDirection",
18568
+ "buyerPersona",
18569
+ "actorDescription",
18570
+ "description",
18571
+ "idempotencyKey",
18572
+ "clientRequestId"
18573
+ ]);
18574
+ var URL_FIELDS = /* @__PURE__ */ new Set([
18575
+ "landingUrl",
18576
+ "productUrl",
18577
+ "url",
18578
+ "serviceLandingUrl"
18579
+ ]);
18580
+ var TRACKING_PARAM_PATTERN = /^(utm_\w+|fbclid|gclid|msclkid|mc_cid|mc_eid|igshid)$/;
18581
+ var LANGUAGE_FIELDS = /* @__PURE__ */ new Set([
18582
+ "language",
18583
+ "lang"
18584
+ ]);
18585
+ var CASE_FOLD_FIELDS = /* @__PURE__ */ new Set([
18586
+ "country",
18587
+ "language",
18588
+ "lang"
18589
+ ]);
18590
+ var ORDER_INSIGNIFICANT_ARRAY_FIELDS = /* @__PURE__ */ new Set([
18591
+ "outputs",
18592
+ "selectedOutputs",
18593
+ "features"
18594
+ ]);
18595
+ function sortCanonical(items) {
18596
+ return [
18597
+ ...items
18598
+ ].sort((a, b) => {
18599
+ const ka = canonicalJson(a);
18600
+ const kb = canonicalJson(b);
18601
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
18602
+ });
18603
+ }
18604
+ __name(sortCanonical, "sortCanonical");
18605
+ function isRecord3(value) {
18606
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18607
+ }
18608
+ __name(isRecord3, "isRecord");
18609
+ function collectLanguageCodes(value, out) {
18610
+ if (Array.isArray(value)) {
18611
+ const items = value;
18612
+ for (const item of items) collectLanguageCodes(item, out);
18613
+ return out;
18614
+ }
18615
+ if (!isRecord3(value)) return out;
18616
+ for (const [key, entry] of Object.entries(value)) {
18617
+ if (LANGUAGE_FIELDS.has(key) && typeof entry === "string") {
18618
+ const base = entry.trim().split(/[-_]/)[0]?.toLowerCase();
18619
+ if (base) out.add(base);
18620
+ }
18621
+ collectLanguageCodes(entry, out);
18622
+ }
18623
+ return out;
18624
+ }
18625
+ __name(collectLanguageCodes, "collectLanguageCodes");
18626
+ function normalizeSearchForKey(search) {
18627
+ if (!search) return "";
18628
+ const params = [
18629
+ ...new URLSearchParams(search).entries()
18630
+ ].filter(([key]) => !TRACKING_PARAM_PATTERN.test(key.toLowerCase())).sort(([ka, va], [kb, vb]) => ka === kb ? va < vb ? -1 : va > vb ? 1 : 0 : ka < kb ? -1 : 1);
18631
+ if (!params.length) return "";
18632
+ const out = new URLSearchParams();
18633
+ for (const [key, value] of params) out.append(key, value);
18634
+ return `?${out.toString()}`;
18635
+ }
18636
+ __name(normalizeSearchForKey, "normalizeSearchForKey");
18637
+ function normalizeUrlForKey(value, languages) {
18638
+ const trimmed = value.trim();
18639
+ let url;
18640
+ try {
18641
+ url = new URL(trimmed);
18642
+ } catch {
18643
+ return trimmed.toLowerCase();
18644
+ }
18645
+ const host = url.hostname.toLowerCase().replace(/^www\./, "");
18646
+ const port = url.port ? `:${url.port}` : "";
18647
+ const segments = url.pathname.split("/").filter(Boolean);
18648
+ const soleSegment = segments.length === 1 ? segments[0] : void 0;
18649
+ if (soleSegment !== void 0 && languages.has(soleSegment.toLowerCase())) {
18650
+ segments.pop();
18651
+ }
18652
+ const path3 = segments.length ? `/${segments.join("/")}` : "";
18653
+ return host + port + path3 + normalizeSearchForKey(url.search);
18654
+ }
18655
+ __name(normalizeUrlForKey, "normalizeUrlForKey");
18656
+ function normalizeForKey(value, languages) {
18657
+ if (Array.isArray(value)) {
18658
+ const items = value;
18659
+ return items.map((item) => normalizeForKey(item, languages));
18660
+ }
18661
+ if (!isRecord3(value)) return value;
18662
+ const out = {};
18663
+ for (const [key, entry] of Object.entries(value)) {
18664
+ if (PARAPHRASE_PRONE_FIELDS.has(key)) {
18665
+ if (key === "description" && value.mode === "described") {
18666
+ out[key] = normalizeForKey(entry, languages);
18667
+ }
18668
+ continue;
18669
+ }
18670
+ out[key] = URL_FIELDS.has(key) && typeof entry === "string" ? normalizeUrlForKey(entry, languages) : CASE_FOLD_FIELDS.has(key) && typeof entry === "string" ? (
18671
+ // separator too or a host alternating between them splits the key.
18672
+ entry.trim().toLowerCase().replace(/_/g, "-")
18673
+ ) : ORDER_INSIGNIFICANT_ARRAY_FIELDS.has(key) && Array.isArray(entry) ? sortCanonical(entry.map((item) => normalizeForKey(item, languages))) : normalizeForKey(entry, languages);
18674
+ }
18675
+ return out;
18676
+ }
18677
+ __name(normalizeForKey, "normalizeForKey");
18678
+ function derivePaidRequestKey(args) {
18679
+ const languages = collectLanguageCodes(args.input, /* @__PURE__ */ new Set());
18680
+ const semanticInput = normalizeForKey(args.input, languages);
18681
+ const bucket = Math.floor(args.nowMs / PAID_REQUEST_KEY_WINDOW_MS);
18682
+ return deriveUuidV5(canonicalJson({
18683
+ user: args.fingerprint,
18684
+ tool: args.tool,
18685
+ input: semanticInput,
18686
+ bucket
18687
+ }), PAID_REQUEST_KEY_NAMESPACE);
18688
+ }
18689
+ __name(derivePaidRequestKey, "derivePaidRequestKey");
18690
+
18507
18691
  // src/tools/winning-product-image.ts
18508
18692
  import { z as z75 } from "zod";
18509
18693
  var WinnerReferenceInput = z75.object({
@@ -19040,10 +19224,25 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
19040
19224
  };
19041
19225
  const r = await api.postMultipart("/v1/ingest", {
19042
19226
  images: parts
19043
- }, fields);
19227
+ }, fields, {
19228
+ // LLM hosts retry paid tools with rephrased arguments — the API's
19229
+ // byte-fingerprint can't see the duplicate, this key can. Derived
19230
+ // from the semantic inputs, not the wire fields, for the same
19231
+ // reason (see request-key.ts, which also canonicalizes the
19232
+ // order-insignificant `outputs` set).
19233
+ idempotencyKey: derivePaidRequestKey({
19234
+ fingerprint: api.fingerprint(),
19235
+ tool: "create_product_task",
19236
+ input,
19237
+ nowMs: Date.now()
19238
+ })
19239
+ });
19044
19240
  return asText({
19045
19241
  taskId: r.taskId,
19046
- status: "queued",
19242
+ status: r.status ?? "queued",
19243
+ ...r.replayed ? {
19244
+ replayed: true
19245
+ } : {},
19047
19246
  estimatedSeconds: 300,
19048
19247
  outputs: selectedOutputs,
19049
19248
  outputMode: "single_task",
@@ -19080,26 +19279,56 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
19080
19279
  assets: []
19081
19280
  },
19082
19281
  appDemoVideos: []
19282
+ }, {
19283
+ // The 2026-09-25 incident: four retries of this exact tool created
19284
+ // four charged service tasks because the host rephrased
19285
+ // `additionalContext` and flipped the landing between `/` and
19286
+ // `/es`. The derived key is stable across both rewrites (see
19287
+ // request-key.ts).
19288
+ idempotencyKey: derivePaidRequestKey({
19289
+ fingerprint: api.fingerprint(),
19290
+ tool: "create_service_video",
19291
+ input,
19292
+ nowMs: Date.now()
19293
+ })
19083
19294
  });
19084
19295
  const angles = await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles`, {});
19085
19296
  const first = angles[0];
19297
+ const replayed = created.replayed === true;
19086
19298
  if (input.reviewMode === "automatic") {
19087
19299
  if (!first) throw new Error("ProductMaker no pudo crear un concepto para este servicio.");
19088
- await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles/select`, {
19089
- angleIds: [
19090
- first.id
19091
- ]
19092
- });
19300
+ if (!(replayed && created.status !== "queued")) {
19301
+ await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles/select`, {
19302
+ angleIds: [
19303
+ first.id
19304
+ ]
19305
+ });
19306
+ }
19093
19307
  return asText({
19094
19308
  taskId: created.taskId,
19095
- status: "processing",
19309
+ status: replayed ? created.status ?? "processing" : "processing",
19310
+ ...replayed ? {
19311
+ replayed: true
19312
+ } : {},
19096
19313
  reviewMode: input.reviewMode,
19097
19314
  selectedConcept: first
19098
19315
  });
19099
19316
  }
19317
+ if (replayed && created.status !== "queued") {
19318
+ return asText({
19319
+ taskId: created.taskId,
19320
+ status: created.status,
19321
+ replayed: true,
19322
+ reviewMode: input.reviewMode,
19323
+ angles
19324
+ });
19325
+ }
19100
19326
  return asText({
19101
19327
  taskId: created.taskId,
19102
19328
  status: "waiting_for_user",
19329
+ ...replayed ? {
19330
+ replayed: true
19331
+ } : {},
19103
19332
  reviewMode: input.reviewMode,
19104
19333
  pendingUserAction: {
19105
19334
  kind: "service_angle_approval",
@@ -19675,7 +19904,6 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
19675
19904
  __name(registerTaskTools, "registerTaskTools");
19676
19905
 
19677
19906
  // src/tools/creatives.ts
19678
- import { randomUUID } from "crypto";
19679
19907
  import { z as z77 } from "zod";
19680
19908
  var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
19681
19909
  var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
@@ -19796,7 +20024,7 @@ var FreeVideoInput = z77.object({
19796
20024
  videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
19797
20025
  generateAiMusic: z77.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
19798
20026
  addSubtitles: z77.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
19799
- idempotencyKey: z77.string().uuid().optional().describe("Opcional. UUID para reintentar exactamente esta misma solicitud sin crear ni cobrar otro video. Reutiliza el mismo valor si el cliente no recibi\xF3 la respuesta.")
20027
+ idempotencyKey: z77.string().uuid().optional().describe("Opcional. Se acepta por compatibilidad pero se ignora: la identidad del reintento se deriva autom\xE1ticamente de los argumentos sem\xE1nticos, as\xED que un retry con los mismos argumentos ya es idempotente sin este campo.")
19800
20028
  }).strict();
19801
20029
  var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
19802
20030
  var ImageCreativeSetup = z77.discriminatedUnion("mode", [
@@ -20018,7 +20246,16 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
20018
20246
  addSubtitles: input.addSubtitles
20019
20247
  },
20020
20248
  allowEmptyImages: true,
20021
- idempotencyKey: input.idempotencyKey ?? randomUUID(),
20249
+ // Always derived — never `input.idempotencyKey ??`: a host that
20250
+ // mints a fresh UUID per retry would defeat the dedupe entirely.
20251
+ // The semantic key is also a strictly better retry identity: a
20252
+ // reused caller key still lands on it (see request-key.ts).
20253
+ idempotencyKey: derivePaidRequestKey({
20254
+ fingerprint: api.fingerprint(),
20255
+ tool: "generate_free_video",
20256
+ input,
20257
+ nowMs: Date.now()
20258
+ }),
20022
20259
  transport
20023
20260
  });
20024
20261
  } catch (e) {
@@ -20083,6 +20320,12 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
20083
20320
  path: "/v1/creatives/videos",
20084
20321
  estimatedSeconds: 300,
20085
20322
  input: wirePayload,
20323
+ idempotencyKey: derivePaidRequestKey({
20324
+ fingerprint: api.fingerprint(),
20325
+ tool: "generate_video_creative",
20326
+ input,
20327
+ nowMs: Date.now()
20328
+ }),
20086
20329
  transport
20087
20330
  });
20088
20331
  } catch (e) {
@@ -20117,6 +20360,12 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
20117
20360
  pauseForAngleReview: true
20118
20361
  }
20119
20362
  },
20363
+ idempotencyKey: derivePaidRequestKey({
20364
+ fingerprint: api.fingerprint(),
20365
+ tool: "generate_image_creatives",
20366
+ input,
20367
+ nowMs: Date.now()
20368
+ }),
20120
20369
  transport
20121
20370
  });
20122
20371
  } catch (e) {
@@ -20160,6 +20409,12 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
20160
20409
  pauseForAngleReview: true
20161
20410
  }
20162
20411
  },
20412
+ idempotencyKey: derivePaidRequestKey({
20413
+ fingerprint: api.fingerprint(),
20414
+ tool: "generate_landing",
20415
+ input,
20416
+ nowMs: Date.now()
20417
+ }),
20163
20418
  transport
20164
20419
  });
20165
20420
  } catch (e) {
@@ -20291,7 +20546,7 @@ var CreateCreativeVariantInputBase = z78.object({
20291
20546
  sourceTaskId: z78.string().uuid().describe("ID de la tarea original completada."),
20292
20547
  kind: CreativeVariantKindEnum,
20293
20548
  confirm: z78.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
20294
- clientRequestId: z78.string().uuid().describe("UUID estable y obligatorio para esta intenci\xF3n. Reutil\xEDzalo ante timeouts o transporte ambiguo. Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n y un UUID nuevo."),
20549
+ clientRequestId: z78.string().uuid().describe("UUID estable para esta intenci\xF3n (obligatorio por compatibilidad de contrato; el servidor deriva la identidad real del request desde los argumentos sem\xE1nticos, as\xED que un UUID nuevo en un retry NO crea una variante duplicada). Si la API devuelve status=error, una nueva generaci\xF3n exige otra confirmaci\xF3n."),
20295
20550
  angle: z78.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
20296
20551
  buyerPersona: z78.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
20297
20552
  scriptNotes: z78.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
@@ -20413,7 +20668,7 @@ function registerCreativeVariantTools(server, api) {
20413
20668
  throw translateError(error);
20414
20669
  }
20415
20670
  });
20416
- server.tool("create_creative_variant", createCreativeVariantGuidance.summary + " ES UNA ACCI\xD3N DE PAGO: primero llama estimate_creative_variant, muestra el rango de cr\xE9ditos al usuario y solo despu\xE9s invoca este tool con confirm=true. Las recomendaciones IA tambi\xE9n son acciones pagas a 2x del costo real. clientRequestId es obligatorio: genera un UUID por intenci\xF3n y reutiliza el mismo ante timeouts o transporte ambiguo. Si recibes status=error, pide confirmaci\xF3n para un intento nuevo y usa otro UUID. Reutiliza el producto y referencias del task origen; crea un child task independiente y devuelve variantTaskId. Para landing puedes enviar instructions en texto libre y omitir sectionKey: el backend lo clasifica con IA. No expone ni permite cambiar videoModel, videoTier, videoStyle o narrativeStyle; esos valores se heredan del task origen.", CreateCreativeVariantInputBase.shape, CREATES_PAID, async (input) => {
20671
+ server.tool("create_creative_variant", createCreativeVariantGuidance.summary + " ES UNA ACCI\xD3N DE PAGO: primero llama estimate_creative_variant, muestra el rango de cr\xE9ditos al usuario y solo despu\xE9s invoca este tool con confirm=true. Las recomendaciones IA tambi\xE9n son acciones pagas a 2x del costo real. clientRequestId es obligatorio por contrato: genera un UUID por intenci\xF3n. El servidor deriva la identidad real del request desde los argumentos sem\xE1nticos, as\xED que un UUID nuevo NO desbloquea un intento distinto \u2014 si recibes status=error, pide confirmaci\xF3n al usuario y reinvoca con los mismos argumentos. Reutiliza el producto y referencias del task origen; crea un child task independiente y devuelve variantTaskId. Para landing puedes enviar instructions en texto libre y omitir sectionKey: el backend lo clasifica con IA. No expone ni permite cambiar videoModel, videoTier, videoStyle o narrativeStyle; esos valores se heredan del task origen.", CreateCreativeVariantInputBase.shape, CREATES_PAID, async (input) => {
20417
20672
  try {
20418
20673
  const validatedInput = parseVariantInput(CreateCreativeVariantInput, input);
20419
20674
  const estimateInput = EstimateCreativeVariantInput.parse({
@@ -20440,8 +20695,16 @@ function registerCreativeVariantTools(server, api) {
20440
20695
  });
20441
20696
  sectionKey = LandingSectionKeyEnum.parse(recommendation.value);
20442
20697
  }
20698
+ const derivedClientRequestId = derivePaidRequestKey({
20699
+ fingerprint: api.fingerprint(),
20700
+ tool: "create_creative_variant",
20701
+ input: {
20702
+ ...validatedInput
20703
+ },
20704
+ nowMs: Date.now()
20705
+ });
20443
20706
  const payload = {
20444
- clientRequestId: validatedInput.clientRequestId,
20707
+ clientRequestId: derivedClientRequestId,
20445
20708
  kind: validatedInput.kind,
20446
20709
  ...validatedInput.kind !== "landing" ? {
20447
20710
  angle: validatedInput.angle,
@@ -20467,7 +20730,7 @@ function registerCreativeVariantTools(server, api) {
20467
20730
  };
20468
20731
  const created = await api.postJson(`/v1/tasks/${encodeURIComponent(validatedInput.sourceTaskId)}/creative-variants`, payload);
20469
20732
  if (created.status === "error") {
20470
- throw mcp(McpErrorCode.CREATIVE_VARIANT_FAILED, "La variante anterior termin\xF3 con error. Pide confirmaci\xF3n para un intento nuevo y usa otro clientRequestId.", {
20733
+ throw mcp(McpErrorCode.CREATIVE_VARIANT_FAILED, "La variante anterior termin\xF3 con error. Pide confirmaci\xF3n al usuario y reinvoca con los mismos argumentos para un intento nuevo.", {
20471
20734
  variantTaskId: created.variantTaskId
20472
20735
  });
20473
20736
  }
@@ -20682,39 +20945,6 @@ __name(registerActorTools, "registerActorTools");
20682
20945
  // src/tools/publish.ts
20683
20946
  import { z as z82 } from "zod";
20684
20947
 
20685
- // src/tools/stable-uuid.ts
20686
- import { createHash as createHash2 } from "crypto";
20687
- function canonicalize(value) {
20688
- if (Array.isArray(value)) return value.map(canonicalize);
20689
- if (value === null || typeof value !== "object") return value;
20690
- const record = value;
20691
- return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
20692
- key,
20693
- canonicalize(record[key])
20694
- ]));
20695
- }
20696
- __name(canonicalize, "canonicalize");
20697
- function canonicalJson(value) {
20698
- return JSON.stringify(canonicalize(value));
20699
- }
20700
- __name(canonicalJson, "canonicalJson");
20701
- function deriveUuidV5(name, namespace) {
20702
- const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
20703
- const digest = createHash2("sha1").update(namespaceBytes).update(name, "utf8").digest();
20704
- const bytes = Buffer.from(digest.subarray(0, 16));
20705
- bytes[6] = (bytes[6] ?? 0) & 15 | 80;
20706
- bytes[8] = (bytes[8] ?? 0) & 63 | 128;
20707
- const hex = bytes.toString("hex");
20708
- return [
20709
- hex.slice(0, 8),
20710
- hex.slice(8, 12),
20711
- hex.slice(12, 16),
20712
- hex.slice(16, 20),
20713
- hex.slice(20)
20714
- ].join("-");
20715
- }
20716
- __name(deriveUuidV5, "deriveUuidV5");
20717
-
20718
20948
  // src/tools/meta-publish-attempt-id.ts
20719
20949
  var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
20720
20950
  function deriveMetaPublishAttemptId(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "8.2.0",
3
+ "version": "8.3.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "ProductMaker MCP server — cost products, generate landing pages, UGC videos, and ad creatives, and publish to Shopify, Meta Ads, and TikTok Ads.",