@productmaker/mcp 8.2.0 → 8.3.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.
- package/README.md +6 -1
- package/dist/http.js +325 -65
- package/dist/stdio.js +286 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ their connector immediately. New connections should always use OAuth; once the
|
|
|
64
64
|
client supports it, replace the saved connection with the clean URL above.
|
|
65
65
|
|
|
66
66
|
In claude.ai: _Settings → Integrations → Add MCP server_.
|
|
67
|
-
In ChatGPT:
|
|
67
|
+
In ChatGPT: _Plugins (sidebar) → Add → Create MCP app_ (OAuth).
|
|
68
68
|
In Claude Desktop: _Settings → Connectors → Add custom connector_.
|
|
69
69
|
|
|
70
70
|
If your saved connection still uses an API key, it continues to use the latest
|
|
@@ -268,6 +268,11 @@ The `base64` field was removed. MCP JSON-RPC transports truncate large tool argu
|
|
|
268
268
|
|
|
269
269
|
## Version history
|
|
270
270
|
|
|
271
|
+
### 8.3.0 — Idempotencia de tools pagadas + refresh OAuth
|
|
272
|
+
|
|
273
|
+
- Tools pagadas (`create_product_task`, `create_service_video`, `generate_video_creative`, `generate_free_video`, `generate_image_creatives`, `generate_landing`, `create_creative_variant`): envían un `Idempotency-Key` derivado de los argumentos semánticos (ventana de 15 min). Un reintento con los mismos argumentos devuelve la misma tarea (`replayed: true`) sin segundo cargo; una tarea en `error` sí se reintenta.
|
|
274
|
+
- OAuth alojado: se anuncia `offline_access` + grant `refresh_token`; un token a punto de expirar responde `401` + `WWW-Authenticate` para que el host refresque en silencio (`MCP_SUBJECT_TOKEN_REAUTH_WINDOW_MS`, por defecto 180000; `0` lo desactiva).
|
|
275
|
+
|
|
271
276
|
### 8.2.0 — Fase 3 (modelos)
|
|
272
277
|
|
|
273
278
|
- Imagen: `generate_image_creatives` (setup guiado) acepta `image:1k` / `image:2k`; `gpt-image-2-5:*` y `gpt-image-2:*` se siguen aceptando.
|
package/dist/http.js
CHANGED
|
@@ -1830,6 +1830,9 @@ var VideoCreativeJobStatus = z14.object({
|
|
|
1830
1830
|
var VideoCreativeJobPayload = z14.object({
|
|
1831
1831
|
jobId: z14.string().optional(),
|
|
1832
1832
|
status: VideoCreativeJobStatusEnum.optional(),
|
|
1833
|
+
/** True when the submission replayed an existing task instead of billing
|
|
1834
|
+
* new work — callers surface the twin verbatim. Omitted on fresh creates. */
|
|
1835
|
+
replayed: z14.boolean().optional(),
|
|
1833
1836
|
videoStructure: VideoStructure.optional(),
|
|
1834
1837
|
videoQuality: VideoQuality.optional(),
|
|
1835
1838
|
videoRouteId: VideoRouteId.optional(),
|
|
@@ -1923,7 +1926,8 @@ var ImageCreativeJobStatus = z14.object({
|
|
|
1923
1926
|
});
|
|
1924
1927
|
var ImageCreativeJobPayload = z14.object({
|
|
1925
1928
|
jobId: z14.string().uuid(),
|
|
1926
|
-
status: ImageCreativeJobStatusEnum
|
|
1929
|
+
status: ImageCreativeJobStatusEnum,
|
|
1930
|
+
replayed: z14.boolean().optional()
|
|
1927
1931
|
});
|
|
1928
1932
|
var ImageCreativeHistoryEntry = z14.object({
|
|
1929
1933
|
id: z14.string().uuid(),
|
|
@@ -1950,7 +1954,8 @@ var LandingCreativeJobStatus = z14.object({
|
|
|
1950
1954
|
});
|
|
1951
1955
|
var LandingCreativeJobPayload = z14.object({
|
|
1952
1956
|
jobId: z14.string().uuid(),
|
|
1953
|
-
status: LandingCreativeJobStatusEnum
|
|
1957
|
+
status: LandingCreativeJobStatusEnum,
|
|
1958
|
+
replayed: z14.boolean().optional()
|
|
1954
1959
|
});
|
|
1955
1960
|
var LandingCreativeHistoryEntry = z14.object({
|
|
1956
1961
|
id: z14.string().uuid(),
|
|
@@ -5621,6 +5626,10 @@ var CreateShopifySubscriptionRequest = z28.object({
|
|
|
5621
5626
|
var CreateShopifyBillingChargeRequest = z28.object({
|
|
5622
5627
|
packId: z28.string().min(1)
|
|
5623
5628
|
});
|
|
5629
|
+
var ShopifyBillingCallbackQuery = z28.object({
|
|
5630
|
+
charge_id: z28.string().regex(/^\d+$/),
|
|
5631
|
+
transactionId: z28.string().uuid()
|
|
5632
|
+
});
|
|
5624
5633
|
var CancelShopifySubscriptionRequest = z28.object({
|
|
5625
5634
|
subscriptionId: z28.string().min(1)
|
|
5626
5635
|
});
|
|
@@ -10848,6 +10857,7 @@ var MixSceneResult = z48.object({
|
|
|
10848
10857
|
index: z48.number().int().positive(),
|
|
10849
10858
|
status: z48.enum([
|
|
10850
10859
|
"queued",
|
|
10860
|
+
"submitting",
|
|
10851
10861
|
"submitted",
|
|
10852
10862
|
"done",
|
|
10853
10863
|
"failed"
|
|
@@ -13446,7 +13456,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
13446
13456
|
], [
|
|
13447
13457
|
"product_costing"
|
|
13448
13458
|
]),
|
|
13449
|
-
defineCapability("create_creative_variant", "
|
|
13459
|
+
defineCapability("create_creative_variant", "3e02399a7785df328f1c4d63cfbb6d7481abaac9f91d4a5cf51d410e657d63aa", "credit_consuming", "creative", [
|
|
13450
13460
|
"credit_consuming",
|
|
13451
13461
|
"state_change"
|
|
13452
13462
|
], [
|
|
@@ -13696,7 +13706,9 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
13696
13706
|
// MCP 7.0.0 (Task 11, .agent/plans/2026-09-15-model-agnostic-video-routes.md):
|
|
13697
13707
|
// `freeProModel` retired in favor of `modelId` (registry ids). Recomputed
|
|
13698
13708
|
// from `pnpm --filter @productmaker/mcp test` printing the real digest.
|
|
13699
|
-
|
|
13709
|
+
// Re-baselined 2026-09-26: `idempotencyKey` field description corrected —
|
|
13710
|
+
// paid tools ignore caller-provided keys and derive their own semantic key.
|
|
13711
|
+
"35616a32a42a0bc062218fdfbb4cc7f4ecfa814571fb86b4f11ef9e4184855e1",
|
|
13700
13712
|
"credit_consuming",
|
|
13701
13713
|
"creative",
|
|
13702
13714
|
[
|
|
@@ -16195,6 +16207,9 @@ function buildProxyUrl(opts) {
|
|
|
16195
16207
|
}
|
|
16196
16208
|
__name(buildProxyUrl, "buildProxyUrl");
|
|
16197
16209
|
|
|
16210
|
+
// src/http.ts
|
|
16211
|
+
import { getOAuthProtectedResourceMetadataUrl as getOAuthProtectedResourceMetadataUrl2 } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
16212
|
+
|
|
16198
16213
|
// src/auth/http-oauth.ts
|
|
16199
16214
|
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
16200
16215
|
import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
@@ -16604,6 +16619,7 @@ import { createRemoteJWKSet as createRemoteJWKSet2, jwtVerify as jwtVerify2 } fr
|
|
|
16604
16619
|
import { z as z73 } from "zod";
|
|
16605
16620
|
var MCP_RESOURCE = new URL("https://mcp.productmaker.app/mcp");
|
|
16606
16621
|
var MCP_SCOPE = "mcp:access";
|
|
16622
|
+
var REFRESH_SCOPE = "offline_access";
|
|
16607
16623
|
var STATIC_CLIENT_ISSUER = new URL(MCP_RESOURCE.origin);
|
|
16608
16624
|
var oauthEnvSchema = z73.object({
|
|
16609
16625
|
MCP_REMOTE_AUTH_MODE: z73.literal("oauth"),
|
|
@@ -16987,13 +17003,10 @@ function createOAuthProxyRouter(options) {
|
|
|
16987
17003
|
});
|
|
16988
17004
|
return;
|
|
16989
17005
|
}
|
|
17006
|
+
body.set("client_secret", options.staticClient.clientSecret);
|
|
16990
17007
|
const headers = {
|
|
16991
17008
|
"content-type": "application/x-www-form-urlencoded"
|
|
16992
17009
|
};
|
|
16993
|
-
const authorization = req.headers.authorization;
|
|
16994
|
-
if (typeof authorization === "string") {
|
|
16995
|
-
headers.authorization = authorization;
|
|
16996
|
-
}
|
|
16997
17010
|
let upstream;
|
|
16998
17011
|
let payload;
|
|
16999
17012
|
try {
|
|
@@ -17026,11 +17039,12 @@ var REGISTRATION_RATE_LIMIT = {
|
|
|
17026
17039
|
windowMs: 6e4,
|
|
17027
17040
|
max: 120
|
|
17028
17041
|
};
|
|
17042
|
+
var PUBLIC_CLIENT_SECRET_PLACEHOLDER = "pm-public-client";
|
|
17029
17043
|
function buildStaticClientInformation(staticClient, requested) {
|
|
17030
17044
|
return {
|
|
17031
17045
|
client_id: staticClient.clientId,
|
|
17032
|
-
client_secret:
|
|
17033
|
-
// 0 = never expires (RFC 7591 §3.2.1); the
|
|
17046
|
+
client_secret: PUBLIC_CLIENT_SECRET_PLACEHOLDER,
|
|
17047
|
+
// 0 = never expires (RFC 7591 §3.2.1); the placeholder is a constant.
|
|
17034
17048
|
client_secret_expires_at: 0,
|
|
17035
17049
|
token_endpoint_auth_method: "client_secret_post",
|
|
17036
17050
|
redirect_uris: requested.redirect_uris,
|
|
@@ -17041,7 +17055,12 @@ function buildStaticClientInformation(staticClient, requested) {
|
|
|
17041
17055
|
response_types: [
|
|
17042
17056
|
"code"
|
|
17043
17057
|
],
|
|
17044
|
-
|
|
17058
|
+
// Clients that defer scope choice to the server get the full intended
|
|
17059
|
+
// set: `mcp:access` for the resource plus `offline_access` so the session
|
|
17060
|
+
// can refresh instead of dying an interactive re-auth every 900s access
|
|
17061
|
+
// token (the auth loop seen in production). A client that names its own
|
|
17062
|
+
// scope list is echoed verbatim — its choice wins.
|
|
17063
|
+
scope: requested.scope ?? `${MCP_SCOPE} ${REFRESH_SCOPE}`,
|
|
17045
17064
|
...requested.client_name !== void 0 ? {
|
|
17046
17065
|
client_name: requested.client_name
|
|
17047
17066
|
} : {}
|
|
@@ -17088,8 +17107,12 @@ function buildStaticClientAuthorizationServerMetadata(auth0Metadata) {
|
|
|
17088
17107
|
token_endpoint_auth_methods_supported: [
|
|
17089
17108
|
"client_secret_post"
|
|
17090
17109
|
],
|
|
17110
|
+
// `offline_access` is what lets a client ASK for a refresh token; without
|
|
17111
|
+
// it in scopes_supported, conforming clients never request it and every
|
|
17112
|
+
// 900s access-token expiry becomes an interactive re-auth loop.
|
|
17091
17113
|
scopes_supported: [
|
|
17092
|
-
MCP_SCOPE
|
|
17114
|
+
MCP_SCOPE,
|
|
17115
|
+
REFRESH_SCOPE
|
|
17093
17116
|
],
|
|
17094
17117
|
service_documentation: "https://productmaker.app/"
|
|
17095
17118
|
};
|
|
@@ -17601,7 +17624,10 @@ var ApiClient = class {
|
|
|
17601
17624
|
method: "POST",
|
|
17602
17625
|
headers: {
|
|
17603
17626
|
...authHeaders,
|
|
17604
|
-
"Content-Type": "application/json"
|
|
17627
|
+
"Content-Type": "application/json",
|
|
17628
|
+
...options?.idempotencyKey ? {
|
|
17629
|
+
"Idempotency-Key": options.idempotencyKey
|
|
17630
|
+
} : {}
|
|
17605
17631
|
},
|
|
17606
17632
|
body: JSON.stringify(body)
|
|
17607
17633
|
}, options);
|
|
@@ -17723,7 +17749,7 @@ var ApiClient = class {
|
|
|
17723
17749
|
};
|
|
17724
17750
|
|
|
17725
17751
|
// src/index.ts
|
|
17726
|
-
var MCP_VERSION = "8.
|
|
17752
|
+
var MCP_VERSION = "8.3.0";
|
|
17727
17753
|
|
|
17728
17754
|
// src/meta-upload-app.ts
|
|
17729
17755
|
import { readFileSync } from "fs";
|
|
@@ -18707,8 +18733,13 @@ async function submitCreative(api, opts) {
|
|
|
18707
18733
|
const taskId = r.taskId ?? r.jobId;
|
|
18708
18734
|
return asText({
|
|
18709
18735
|
taskId,
|
|
18710
|
-
|
|
18711
|
-
|
|
18736
|
+
// Replay-aware: a keyed retry may land on a task that is already
|
|
18737
|
+
// processing/done — report the real status, not a hopeful "queued".
|
|
18738
|
+
status: r.status ?? "queued",
|
|
18739
|
+
estimatedSeconds: opts.estimatedSeconds,
|
|
18740
|
+
...r.replayed === true ? {
|
|
18741
|
+
replayed: true
|
|
18742
|
+
} : {}
|
|
18712
18743
|
});
|
|
18713
18744
|
}
|
|
18714
18745
|
__name(submitCreative, "submitCreative");
|
|
@@ -20072,6 +20103,170 @@ ${summary}`,
|
|
|
20072
20103
|
}
|
|
20073
20104
|
__name(requirePaidCreativeConfirmation, "requirePaidCreativeConfirmation");
|
|
20074
20105
|
|
|
20106
|
+
// src/tools/stable-uuid.ts
|
|
20107
|
+
import { createHash as createHash3 } from "crypto";
|
|
20108
|
+
function canonicalize(value) {
|
|
20109
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
20110
|
+
if (value === null || typeof value !== "object") return value;
|
|
20111
|
+
const record = value;
|
|
20112
|
+
return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
|
|
20113
|
+
key,
|
|
20114
|
+
canonicalize(record[key])
|
|
20115
|
+
]));
|
|
20116
|
+
}
|
|
20117
|
+
__name(canonicalize, "canonicalize");
|
|
20118
|
+
function canonicalJson(value) {
|
|
20119
|
+
return JSON.stringify(canonicalize(value));
|
|
20120
|
+
}
|
|
20121
|
+
__name(canonicalJson, "canonicalJson");
|
|
20122
|
+
function deriveUuidV5(name, namespace) {
|
|
20123
|
+
const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
|
|
20124
|
+
const digest = createHash3("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
20125
|
+
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
20126
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
20127
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
20128
|
+
const hex = bytes.toString("hex");
|
|
20129
|
+
return [
|
|
20130
|
+
hex.slice(0, 8),
|
|
20131
|
+
hex.slice(8, 12),
|
|
20132
|
+
hex.slice(12, 16),
|
|
20133
|
+
hex.slice(16, 20),
|
|
20134
|
+
hex.slice(20)
|
|
20135
|
+
].join("-");
|
|
20136
|
+
}
|
|
20137
|
+
__name(deriveUuidV5, "deriveUuidV5");
|
|
20138
|
+
|
|
20139
|
+
// src/tools/request-key.ts
|
|
20140
|
+
var PAID_REQUEST_KEY_WINDOW_MS = 15 * 60 * 1e3;
|
|
20141
|
+
var PAID_REQUEST_KEY_NAMESPACE = "8f3d6b1c-2a47-4e5b-9c0d-6e7f8a9b0c1d";
|
|
20142
|
+
var PARAPHRASE_PRONE_FIELDS = /* @__PURE__ */ new Set([
|
|
20143
|
+
"additionalContext",
|
|
20144
|
+
"productContext",
|
|
20145
|
+
"scriptNotes",
|
|
20146
|
+
"creativeDirection",
|
|
20147
|
+
"buyerPersona",
|
|
20148
|
+
"actorDescription",
|
|
20149
|
+
"description",
|
|
20150
|
+
"idempotencyKey",
|
|
20151
|
+
"clientRequestId"
|
|
20152
|
+
]);
|
|
20153
|
+
var URL_FIELDS = /* @__PURE__ */ new Set([
|
|
20154
|
+
"landingUrl",
|
|
20155
|
+
"productUrl",
|
|
20156
|
+
"url",
|
|
20157
|
+
"serviceLandingUrl"
|
|
20158
|
+
]);
|
|
20159
|
+
var TRACKING_PARAM_PATTERN = /^(utm_\w+|fbclid|gclid|msclkid|mc_cid|mc_eid|igshid)$/;
|
|
20160
|
+
var LANGUAGE_FIELDS = /* @__PURE__ */ new Set([
|
|
20161
|
+
"language",
|
|
20162
|
+
"lang"
|
|
20163
|
+
]);
|
|
20164
|
+
var CASE_FOLD_FIELDS = /* @__PURE__ */ new Set([
|
|
20165
|
+
"country",
|
|
20166
|
+
"language",
|
|
20167
|
+
"lang"
|
|
20168
|
+
]);
|
|
20169
|
+
var ORDER_INSIGNIFICANT_ARRAY_FIELDS = /* @__PURE__ */ new Set([
|
|
20170
|
+
"outputs",
|
|
20171
|
+
"selectedOutputs",
|
|
20172
|
+
"features"
|
|
20173
|
+
]);
|
|
20174
|
+
function sortCanonical(items) {
|
|
20175
|
+
return [
|
|
20176
|
+
...items
|
|
20177
|
+
].sort((a, b) => {
|
|
20178
|
+
const ka = canonicalJson(a);
|
|
20179
|
+
const kb = canonicalJson(b);
|
|
20180
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
20181
|
+
});
|
|
20182
|
+
}
|
|
20183
|
+
__name(sortCanonical, "sortCanonical");
|
|
20184
|
+
function isRecord3(value) {
|
|
20185
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20186
|
+
}
|
|
20187
|
+
__name(isRecord3, "isRecord");
|
|
20188
|
+
function collectLanguageCodes(value, out) {
|
|
20189
|
+
if (Array.isArray(value)) {
|
|
20190
|
+
const items = value;
|
|
20191
|
+
for (const item of items) collectLanguageCodes(item, out);
|
|
20192
|
+
return out;
|
|
20193
|
+
}
|
|
20194
|
+
if (!isRecord3(value)) return out;
|
|
20195
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
20196
|
+
if (LANGUAGE_FIELDS.has(key) && typeof entry === "string") {
|
|
20197
|
+
const base = entry.trim().split(/[-_]/)[0]?.toLowerCase();
|
|
20198
|
+
if (base) out.add(base);
|
|
20199
|
+
}
|
|
20200
|
+
collectLanguageCodes(entry, out);
|
|
20201
|
+
}
|
|
20202
|
+
return out;
|
|
20203
|
+
}
|
|
20204
|
+
__name(collectLanguageCodes, "collectLanguageCodes");
|
|
20205
|
+
function normalizeSearchForKey(search) {
|
|
20206
|
+
if (!search) return "";
|
|
20207
|
+
const params = [
|
|
20208
|
+
...new URLSearchParams(search).entries()
|
|
20209
|
+
].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);
|
|
20210
|
+
if (!params.length) return "";
|
|
20211
|
+
const out = new URLSearchParams();
|
|
20212
|
+
for (const [key, value] of params) out.append(key, value);
|
|
20213
|
+
return `?${out.toString()}`;
|
|
20214
|
+
}
|
|
20215
|
+
__name(normalizeSearchForKey, "normalizeSearchForKey");
|
|
20216
|
+
function normalizeUrlForKey(value, languages) {
|
|
20217
|
+
const trimmed = value.trim();
|
|
20218
|
+
let url;
|
|
20219
|
+
try {
|
|
20220
|
+
url = new URL(trimmed);
|
|
20221
|
+
} catch {
|
|
20222
|
+
return trimmed.toLowerCase();
|
|
20223
|
+
}
|
|
20224
|
+
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
20225
|
+
const port = url.port ? `:${url.port}` : "";
|
|
20226
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
20227
|
+
const soleSegment = segments.length === 1 ? segments[0] : void 0;
|
|
20228
|
+
if (soleSegment !== void 0 && languages.has(soleSegment.toLowerCase())) {
|
|
20229
|
+
segments.pop();
|
|
20230
|
+
}
|
|
20231
|
+
const path3 = segments.length ? `/${segments.join("/")}` : "";
|
|
20232
|
+
return host + port + path3 + normalizeSearchForKey(url.search);
|
|
20233
|
+
}
|
|
20234
|
+
__name(normalizeUrlForKey, "normalizeUrlForKey");
|
|
20235
|
+
function normalizeForKey(value, languages) {
|
|
20236
|
+
if (Array.isArray(value)) {
|
|
20237
|
+
const items = value;
|
|
20238
|
+
return items.map((item) => normalizeForKey(item, languages));
|
|
20239
|
+
}
|
|
20240
|
+
if (!isRecord3(value)) return value;
|
|
20241
|
+
const out = {};
|
|
20242
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
20243
|
+
if (PARAPHRASE_PRONE_FIELDS.has(key)) {
|
|
20244
|
+
if (key === "description" && value.mode === "described") {
|
|
20245
|
+
out[key] = normalizeForKey(entry, languages);
|
|
20246
|
+
}
|
|
20247
|
+
continue;
|
|
20248
|
+
}
|
|
20249
|
+
out[key] = URL_FIELDS.has(key) && typeof entry === "string" ? normalizeUrlForKey(entry, languages) : CASE_FOLD_FIELDS.has(key) && typeof entry === "string" ? (
|
|
20250
|
+
// separator too or a host alternating between them splits the key.
|
|
20251
|
+
entry.trim().toLowerCase().replace(/_/g, "-")
|
|
20252
|
+
) : ORDER_INSIGNIFICANT_ARRAY_FIELDS.has(key) && Array.isArray(entry) ? sortCanonical(entry.map((item) => normalizeForKey(item, languages))) : normalizeForKey(entry, languages);
|
|
20253
|
+
}
|
|
20254
|
+
return out;
|
|
20255
|
+
}
|
|
20256
|
+
__name(normalizeForKey, "normalizeForKey");
|
|
20257
|
+
function derivePaidRequestKey(args) {
|
|
20258
|
+
const languages = collectLanguageCodes(args.input, /* @__PURE__ */ new Set());
|
|
20259
|
+
const semanticInput = normalizeForKey(args.input, languages);
|
|
20260
|
+
const bucket = Math.floor(args.nowMs / PAID_REQUEST_KEY_WINDOW_MS);
|
|
20261
|
+
return deriveUuidV5(canonicalJson({
|
|
20262
|
+
user: args.fingerprint,
|
|
20263
|
+
tool: args.tool,
|
|
20264
|
+
input: semanticInput,
|
|
20265
|
+
bucket
|
|
20266
|
+
}), PAID_REQUEST_KEY_NAMESPACE);
|
|
20267
|
+
}
|
|
20268
|
+
__name(derivePaidRequestKey, "derivePaidRequestKey");
|
|
20269
|
+
|
|
20075
20270
|
// src/tools/winning-product-image.ts
|
|
20076
20271
|
import { z as z76 } from "zod";
|
|
20077
20272
|
var WinnerReferenceInput = z76.object({
|
|
@@ -20608,10 +20803,25 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
20608
20803
|
};
|
|
20609
20804
|
const r = await api.postMultipart("/v1/ingest", {
|
|
20610
20805
|
images: parts
|
|
20611
|
-
}, fields
|
|
20806
|
+
}, fields, {
|
|
20807
|
+
// LLM hosts retry paid tools with rephrased arguments — the API's
|
|
20808
|
+
// byte-fingerprint can't see the duplicate, this key can. Derived
|
|
20809
|
+
// from the semantic inputs, not the wire fields, for the same
|
|
20810
|
+
// reason (see request-key.ts, which also canonicalizes the
|
|
20811
|
+
// order-insignificant `outputs` set).
|
|
20812
|
+
idempotencyKey: derivePaidRequestKey({
|
|
20813
|
+
fingerprint: api.fingerprint(),
|
|
20814
|
+
tool: "create_product_task",
|
|
20815
|
+
input,
|
|
20816
|
+
nowMs: Date.now()
|
|
20817
|
+
})
|
|
20818
|
+
});
|
|
20612
20819
|
return asText({
|
|
20613
20820
|
taskId: r.taskId,
|
|
20614
|
-
status: "queued",
|
|
20821
|
+
status: r.status ?? "queued",
|
|
20822
|
+
...r.replayed ? {
|
|
20823
|
+
replayed: true
|
|
20824
|
+
} : {},
|
|
20615
20825
|
estimatedSeconds: 300,
|
|
20616
20826
|
outputs: selectedOutputs,
|
|
20617
20827
|
outputMode: "single_task",
|
|
@@ -20648,26 +20858,56 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
20648
20858
|
assets: []
|
|
20649
20859
|
},
|
|
20650
20860
|
appDemoVideos: []
|
|
20861
|
+
}, {
|
|
20862
|
+
// The 2026-09-25 incident: four retries of this exact tool created
|
|
20863
|
+
// four charged service tasks because the host rephrased
|
|
20864
|
+
// `additionalContext` and flipped the landing between `/` and
|
|
20865
|
+
// `/es`. The derived key is stable across both rewrites (see
|
|
20866
|
+
// request-key.ts).
|
|
20867
|
+
idempotencyKey: derivePaidRequestKey({
|
|
20868
|
+
fingerprint: api.fingerprint(),
|
|
20869
|
+
tool: "create_service_video",
|
|
20870
|
+
input,
|
|
20871
|
+
nowMs: Date.now()
|
|
20872
|
+
})
|
|
20651
20873
|
});
|
|
20652
20874
|
const angles = await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles`, {});
|
|
20653
20875
|
const first = angles[0];
|
|
20876
|
+
const replayed = created.replayed === true;
|
|
20654
20877
|
if (input.reviewMode === "automatic") {
|
|
20655
20878
|
if (!first) throw new Error("ProductMaker no pudo crear un concepto para este servicio.");
|
|
20656
|
-
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
|
|
20660
|
-
|
|
20879
|
+
if (!(replayed && created.status !== "queued")) {
|
|
20880
|
+
await api.postJson(`/v1/tasks/${encodeURIComponent(created.taskId)}/service-angles/select`, {
|
|
20881
|
+
angleIds: [
|
|
20882
|
+
first.id
|
|
20883
|
+
]
|
|
20884
|
+
});
|
|
20885
|
+
}
|
|
20661
20886
|
return asText({
|
|
20662
20887
|
taskId: created.taskId,
|
|
20663
|
-
status: "processing",
|
|
20888
|
+
status: replayed ? created.status ?? "processing" : "processing",
|
|
20889
|
+
...replayed ? {
|
|
20890
|
+
replayed: true
|
|
20891
|
+
} : {},
|
|
20664
20892
|
reviewMode: input.reviewMode,
|
|
20665
20893
|
selectedConcept: first
|
|
20666
20894
|
});
|
|
20667
20895
|
}
|
|
20896
|
+
if (replayed && created.status !== "queued") {
|
|
20897
|
+
return asText({
|
|
20898
|
+
taskId: created.taskId,
|
|
20899
|
+
status: created.status,
|
|
20900
|
+
replayed: true,
|
|
20901
|
+
reviewMode: input.reviewMode,
|
|
20902
|
+
angles
|
|
20903
|
+
});
|
|
20904
|
+
}
|
|
20668
20905
|
return asText({
|
|
20669
20906
|
taskId: created.taskId,
|
|
20670
20907
|
status: "waiting_for_user",
|
|
20908
|
+
...replayed ? {
|
|
20909
|
+
replayed: true
|
|
20910
|
+
} : {},
|
|
20671
20911
|
reviewMode: input.reviewMode,
|
|
20672
20912
|
pendingUserAction: {
|
|
20673
20913
|
kind: "service_angle_approval",
|
|
@@ -21243,7 +21483,6 @@ ANTES de invocar, resume outputs, cantidad de videos y datos comerciales, explic
|
|
|
21243
21483
|
__name(registerTaskTools, "registerTaskTools");
|
|
21244
21484
|
|
|
21245
21485
|
// src/tools/creatives.ts
|
|
21246
|
-
import { randomUUID } from "crypto";
|
|
21247
21486
|
import { z as z78 } from "zod";
|
|
21248
21487
|
var generateFreeVideoGuidance = getProductMakerCapabilityModelGuidance("generate_free_video");
|
|
21249
21488
|
var generateVideoCreativeGuidance = getProductMakerCapabilityModelGuidance("generate_video_creative");
|
|
@@ -21364,7 +21603,7 @@ var FreeVideoInput = z78.object({
|
|
|
21364
21603
|
videoResolution: VideoResolutionEnum.describe("Resoluci\xF3n del video: 480p o 720p."),
|
|
21365
21604
|
generateAiMusic: z78.boolean().default(false).describe("Opcional. Agrega m\xFAsica instrumental IA debajo del audio nativo. Default false."),
|
|
21366
21605
|
addSubtitles: z78.boolean().default(false).describe("Opcional. Quema subt\xEDtulos usando el rawPrompt exacto. Default false."),
|
|
21367
|
-
idempotencyKey: z78.string().uuid().optional().describe("Opcional.
|
|
21606
|
+
idempotencyKey: z78.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.")
|
|
21368
21607
|
}).strict();
|
|
21369
21608
|
var imageStyleList = ImageCreativeStyleEnum.options.join(" | ");
|
|
21370
21609
|
var ImageCreativeSetup = z78.discriminatedUnion("mode", [
|
|
@@ -21586,7 +21825,16 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
21586
21825
|
addSubtitles: input.addSubtitles
|
|
21587
21826
|
},
|
|
21588
21827
|
allowEmptyImages: true,
|
|
21589
|
-
|
|
21828
|
+
// Always derived — never `input.idempotencyKey ??`: a host that
|
|
21829
|
+
// mints a fresh UUID per retry would defeat the dedupe entirely.
|
|
21830
|
+
// The semantic key is also a strictly better retry identity: a
|
|
21831
|
+
// reused caller key still lands on it (see request-key.ts).
|
|
21832
|
+
idempotencyKey: derivePaidRequestKey({
|
|
21833
|
+
fingerprint: api.fingerprint(),
|
|
21834
|
+
tool: "generate_free_video",
|
|
21835
|
+
input,
|
|
21836
|
+
nowMs: Date.now()
|
|
21837
|
+
}),
|
|
21590
21838
|
transport
|
|
21591
21839
|
});
|
|
21592
21840
|
} catch (e) {
|
|
@@ -21651,6 +21899,12 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
21651
21899
|
path: "/v1/creatives/videos",
|
|
21652
21900
|
estimatedSeconds: 300,
|
|
21653
21901
|
input: wirePayload,
|
|
21902
|
+
idempotencyKey: derivePaidRequestKey({
|
|
21903
|
+
fingerprint: api.fingerprint(),
|
|
21904
|
+
tool: "generate_video_creative",
|
|
21905
|
+
input,
|
|
21906
|
+
nowMs: Date.now()
|
|
21907
|
+
}),
|
|
21654
21908
|
transport
|
|
21655
21909
|
});
|
|
21656
21910
|
} catch (e) {
|
|
@@ -21685,6 +21939,12 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
21685
21939
|
pauseForAngleReview: true
|
|
21686
21940
|
}
|
|
21687
21941
|
},
|
|
21942
|
+
idempotencyKey: derivePaidRequestKey({
|
|
21943
|
+
fingerprint: api.fingerprint(),
|
|
21944
|
+
tool: "generate_image_creatives",
|
|
21945
|
+
input,
|
|
21946
|
+
nowMs: Date.now()
|
|
21947
|
+
}),
|
|
21688
21948
|
transport
|
|
21689
21949
|
});
|
|
21690
21950
|
} catch (e) {
|
|
@@ -21728,6 +21988,12 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
21728
21988
|
pauseForAngleReview: true
|
|
21729
21989
|
}
|
|
21730
21990
|
},
|
|
21991
|
+
idempotencyKey: derivePaidRequestKey({
|
|
21992
|
+
fingerprint: api.fingerprint(),
|
|
21993
|
+
tool: "generate_landing",
|
|
21994
|
+
input,
|
|
21995
|
+
nowMs: Date.now()
|
|
21996
|
+
}),
|
|
21731
21997
|
transport
|
|
21732
21998
|
});
|
|
21733
21999
|
} catch (e) {
|
|
@@ -21859,7 +22125,7 @@ var CreateCreativeVariantInputBase = z79.object({
|
|
|
21859
22125
|
sourceTaskId: z79.string().uuid().describe("ID de la tarea original completada."),
|
|
21860
22126
|
kind: CreativeVariantKindEnum,
|
|
21861
22127
|
confirm: z79.literal(true).describe("Confirmaci\xF3n expl\xEDcita del usuario despu\xE9s de mostrarle el estimado de cr\xE9ditos."),
|
|
21862
|
-
clientRequestId: z79.string().uuid().describe("UUID estable
|
|
22128
|
+
clientRequestId: z79.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."),
|
|
21863
22129
|
angle: z79.string().trim().min(1).max(500).optional().describe("\xC1ngulo nuevo para video o imagen."),
|
|
21864
22130
|
buyerPersona: z79.string().trim().min(1).max(360).optional().describe("Buyer persona coherente con el \xE1ngulo."),
|
|
21865
22131
|
scriptNotes: z79.string().trim().min(1).max(2e3).optional().describe("Direcci\xF3n breve del guion para una variante de video."),
|
|
@@ -21981,7 +22247,7 @@ function registerCreativeVariantTools(server, api) {
|
|
|
21981
22247
|
throw translateError(error);
|
|
21982
22248
|
}
|
|
21983
22249
|
});
|
|
21984
|
-
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
|
|
22250
|
+
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) => {
|
|
21985
22251
|
try {
|
|
21986
22252
|
const validatedInput = parseVariantInput(CreateCreativeVariantInput, input);
|
|
21987
22253
|
const estimateInput = EstimateCreativeVariantInput.parse({
|
|
@@ -22008,8 +22274,16 @@ function registerCreativeVariantTools(server, api) {
|
|
|
22008
22274
|
});
|
|
22009
22275
|
sectionKey = LandingSectionKeyEnum.parse(recommendation.value);
|
|
22010
22276
|
}
|
|
22277
|
+
const derivedClientRequestId = derivePaidRequestKey({
|
|
22278
|
+
fingerprint: api.fingerprint(),
|
|
22279
|
+
tool: "create_creative_variant",
|
|
22280
|
+
input: {
|
|
22281
|
+
...validatedInput
|
|
22282
|
+
},
|
|
22283
|
+
nowMs: Date.now()
|
|
22284
|
+
});
|
|
22011
22285
|
const payload = {
|
|
22012
|
-
clientRequestId:
|
|
22286
|
+
clientRequestId: derivedClientRequestId,
|
|
22013
22287
|
kind: validatedInput.kind,
|
|
22014
22288
|
...validatedInput.kind !== "landing" ? {
|
|
22015
22289
|
angle: validatedInput.angle,
|
|
@@ -22035,7 +22309,7 @@ function registerCreativeVariantTools(server, api) {
|
|
|
22035
22309
|
};
|
|
22036
22310
|
const created = await api.postJson(`/v1/tasks/${encodeURIComponent(validatedInput.sourceTaskId)}/creative-variants`, payload);
|
|
22037
22311
|
if (created.status === "error") {
|
|
22038
|
-
throw mcp(McpErrorCode.CREATIVE_VARIANT_FAILED, "La variante anterior termin\xF3 con error. Pide confirmaci\xF3n para un intento nuevo
|
|
22312
|
+
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.", {
|
|
22039
22313
|
variantTaskId: created.variantTaskId
|
|
22040
22314
|
});
|
|
22041
22315
|
}
|
|
@@ -22250,39 +22524,6 @@ __name(registerActorTools, "registerActorTools");
|
|
|
22250
22524
|
// src/tools/publish.ts
|
|
22251
22525
|
import { z as z83 } from "zod";
|
|
22252
22526
|
|
|
22253
|
-
// src/tools/stable-uuid.ts
|
|
22254
|
-
import { createHash as createHash3 } from "crypto";
|
|
22255
|
-
function canonicalize(value) {
|
|
22256
|
-
if (Array.isArray(value)) return value.map(canonicalize);
|
|
22257
|
-
if (value === null || typeof value !== "object") return value;
|
|
22258
|
-
const record = value;
|
|
22259
|
-
return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [
|
|
22260
|
-
key,
|
|
22261
|
-
canonicalize(record[key])
|
|
22262
|
-
]));
|
|
22263
|
-
}
|
|
22264
|
-
__name(canonicalize, "canonicalize");
|
|
22265
|
-
function canonicalJson(value) {
|
|
22266
|
-
return JSON.stringify(canonicalize(value));
|
|
22267
|
-
}
|
|
22268
|
-
__name(canonicalJson, "canonicalJson");
|
|
22269
|
-
function deriveUuidV5(name, namespace) {
|
|
22270
|
-
const namespaceBytes = Buffer.from(namespace.replaceAll("-", ""), "hex");
|
|
22271
|
-
const digest = createHash3("sha1").update(namespaceBytes).update(name, "utf8").digest();
|
|
22272
|
-
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
22273
|
-
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
22274
|
-
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
22275
|
-
const hex = bytes.toString("hex");
|
|
22276
|
-
return [
|
|
22277
|
-
hex.slice(0, 8),
|
|
22278
|
-
hex.slice(8, 12),
|
|
22279
|
-
hex.slice(12, 16),
|
|
22280
|
-
hex.slice(16, 20),
|
|
22281
|
-
hex.slice(20)
|
|
22282
|
-
].join("-");
|
|
22283
|
-
}
|
|
22284
|
-
__name(deriveUuidV5, "deriveUuidV5");
|
|
22285
|
-
|
|
22286
22527
|
// src/tools/meta-publish-attempt-id.ts
|
|
22287
22528
|
var META_PUBLISH_NAMESPACE = "7b19c9ac-b4eb-4db1-970e-c2f24ce30b98";
|
|
22288
22529
|
function deriveMetaPublishAttemptId(input) {
|
|
@@ -24931,6 +25172,16 @@ function firstHeader(value) {
|
|
|
24931
25172
|
}
|
|
24932
25173
|
__name(firstHeader, "firstHeader");
|
|
24933
25174
|
var PROXY_FETCH_TIMEOUT_MS = 3e4;
|
|
25175
|
+
var DEFAULT_SUBJECT_TOKEN_REAUTH_WINDOW_MS = 18e4;
|
|
25176
|
+
function subjectTokenReauthWindowMs(env) {
|
|
25177
|
+
const raw = env.MCP_SUBJECT_TOKEN_REAUTH_WINDOW_MS;
|
|
25178
|
+
if (raw === void 0 || raw.trim() === "") {
|
|
25179
|
+
return DEFAULT_SUBJECT_TOKEN_REAUTH_WINDOW_MS;
|
|
25180
|
+
}
|
|
25181
|
+
const parsed = Number.parseInt(raw, 10);
|
|
25182
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_SUBJECT_TOKEN_REAUTH_WINDOW_MS;
|
|
25183
|
+
}
|
|
25184
|
+
__name(subjectTokenReauthWindowMs, "subjectTokenReauthWindowMs");
|
|
24934
25185
|
var INLINE_RENDERABLE_MIMES = /* @__PURE__ */ new Set([
|
|
24935
25186
|
"image/jpeg",
|
|
24936
25187
|
"image/png",
|
|
@@ -25061,7 +25312,8 @@ function resolveDownstreamAuth(req, opts, oauthAnalytics) {
|
|
|
25061
25312
|
credential,
|
|
25062
25313
|
subject,
|
|
25063
25314
|
clientId,
|
|
25064
|
-
isLegacy: false
|
|
25315
|
+
isLegacy: false,
|
|
25316
|
+
subjectExpiresAt: expiresAt
|
|
25065
25317
|
};
|
|
25066
25318
|
}
|
|
25067
25319
|
__name(resolveDownstreamAuth, "resolveDownstreamAuth");
|
|
@@ -25085,6 +25337,14 @@ function createAuthenticatedMcpHandler(opts, oauthAnalytics) {
|
|
|
25085
25337
|
return;
|
|
25086
25338
|
}
|
|
25087
25339
|
const { credential, subject, clientId, isLegacy } = authContext;
|
|
25340
|
+
const reauthWindowMs = subjectTokenReauthWindowMs(process.env);
|
|
25341
|
+
if (reauthWindowMs > 0 && !isLegacy && typeof authContext.subjectExpiresAt === "number" && authContext.subjectExpiresAt * 1e3 <= Date.now() + reauthWindowMs) {
|
|
25342
|
+
res.status(401).set("WWW-Authenticate", `Bearer error="invalid_token", error_description="Access token expired or expiring soon", scope="${MCP_SCOPE}", resource_metadata="${getOAuthProtectedResourceMetadataUrl2(MCP_RESOURCE)}"`).json({
|
|
25343
|
+
error: "invalid_token",
|
|
25344
|
+
error_description: "Access token expired or expiring soon; refresh or re-authenticate."
|
|
25345
|
+
});
|
|
25346
|
+
return;
|
|
25347
|
+
}
|
|
25088
25348
|
const abortController = new AbortController();
|
|
25089
25349
|
const abortRequest = /* @__PURE__ */ __name(() => abortController.abort(), "abortRequest");
|
|
25090
25350
|
req.once("aborted", abortRequest);
|
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", "
|
|
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
|
-
|
|
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.
|
|
16120
|
+
var MCP_VERSION = "8.3.0";
|
|
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
|
-
|
|
17090
|
-
|
|
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
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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.
|
|
3
|
+
"version": "8.3.0",
|
|
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.",
|