@productmaker/mcp 3.1.0 → 3.1.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/README.md +45 -21
- package/dist/http.js +2959 -1527
- package/dist/stdio.js +225 -58
- package/dist/ui/meta-upload-app.html +10 -2
- package/package.json +4 -2
package/dist/stdio.js
CHANGED
|
@@ -5,11 +5,88 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
5
5
|
// src/stdio.ts
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
|
|
8
|
+
// src/auth/downstream-credential.ts
|
|
9
|
+
import { createHash } from "crypto";
|
|
10
|
+
var StaticApiKeyCredential = class {
|
|
11
|
+
static {
|
|
12
|
+
__name(this, "StaticApiKeyCredential");
|
|
13
|
+
}
|
|
14
|
+
apiKey;
|
|
15
|
+
constructor(apiKey) {
|
|
16
|
+
this.apiKey = apiKey;
|
|
17
|
+
}
|
|
18
|
+
async getAccessToken() {
|
|
19
|
+
return this.apiKey;
|
|
20
|
+
}
|
|
21
|
+
fingerprint() {
|
|
22
|
+
return createHash("sha256").update(this.apiKey).digest("hex").slice(0, 32);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
8
26
|
// src/server.ts
|
|
9
27
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
28
|
|
|
29
|
+
// src/response-body.ts
|
|
30
|
+
var ResponseBodyTooLargeError = class extends Error {
|
|
31
|
+
static {
|
|
32
|
+
__name(this, "ResponseBodyTooLargeError");
|
|
33
|
+
}
|
|
34
|
+
maxBytes;
|
|
35
|
+
constructor(maxBytes) {
|
|
36
|
+
super(`Response body exceeds the ${maxBytes}-byte limit`), this.maxBytes = maxBytes;
|
|
37
|
+
this.name = "ResponseBodyTooLargeError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function cancelResponseBody(response) {
|
|
41
|
+
if (!response.body) return;
|
|
42
|
+
void response.body.cancel().catch(() => void 0);
|
|
43
|
+
}
|
|
44
|
+
__name(cancelResponseBody, "cancelResponseBody");
|
|
45
|
+
async function readResponseBytes(response, maxBytes) {
|
|
46
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
47
|
+
throw new RangeError("maxBytes must be a positive safe integer");
|
|
48
|
+
}
|
|
49
|
+
if (!response.body) return new Uint8Array();
|
|
50
|
+
const contentLength = response.headers.get("content-length");
|
|
51
|
+
const declaredLength = contentLength === null ? NaN : Number(contentLength);
|
|
52
|
+
if (Number.isSafeInteger(declaredLength) && declaredLength > maxBytes) {
|
|
53
|
+
cancelResponseBody(response);
|
|
54
|
+
throw new ResponseBodyTooLargeError(maxBytes);
|
|
55
|
+
}
|
|
56
|
+
const reader = response.body.getReader();
|
|
57
|
+
const chunks = [];
|
|
58
|
+
let totalBytes = 0;
|
|
59
|
+
try {
|
|
60
|
+
while (true) {
|
|
61
|
+
const { done, value } = await reader.read();
|
|
62
|
+
if (done) break;
|
|
63
|
+
totalBytes += value.byteLength;
|
|
64
|
+
if (totalBytes > maxBytes) {
|
|
65
|
+
void reader.cancel().catch(() => void 0);
|
|
66
|
+
throw new ResponseBodyTooLargeError(maxBytes);
|
|
67
|
+
}
|
|
68
|
+
chunks.push(value);
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
reader.releaseLock();
|
|
72
|
+
}
|
|
73
|
+
const body = new Uint8Array(totalBytes);
|
|
74
|
+
let offset = 0;
|
|
75
|
+
for (const chunk of chunks) {
|
|
76
|
+
body.set(chunk, offset);
|
|
77
|
+
offset += chunk.byteLength;
|
|
78
|
+
}
|
|
79
|
+
return body;
|
|
80
|
+
}
|
|
81
|
+
__name(readResponseBytes, "readResponseBytes");
|
|
82
|
+
async function readResponseText(response, maxBytes) {
|
|
83
|
+
return new TextDecoder().decode(await readResponseBytes(response, maxBytes));
|
|
84
|
+
}
|
|
85
|
+
__name(readResponseText, "readResponseText");
|
|
86
|
+
|
|
11
87
|
// src/api-client.ts
|
|
12
|
-
|
|
88
|
+
var DOWNSTREAM_REQUEST_TIMEOUT_MS = 3e4;
|
|
89
|
+
var MAX_DOWNSTREAM_RESPONSE_BYTES = 1048576;
|
|
13
90
|
var ApiHttpError = class extends Error {
|
|
14
91
|
static {
|
|
15
92
|
__name(this, "ApiHttpError");
|
|
@@ -25,88 +102,95 @@ var ApiClient = class {
|
|
|
25
102
|
static {
|
|
26
103
|
__name(this, "ApiClient");
|
|
27
104
|
}
|
|
28
|
-
apiKey;
|
|
29
105
|
baseUrl;
|
|
30
|
-
|
|
31
|
-
|
|
106
|
+
credential;
|
|
107
|
+
signal;
|
|
108
|
+
requestTimeoutMs;
|
|
109
|
+
constructor(baseUrl, credential, options = {}) {
|
|
32
110
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
111
|
+
this.credential = credential;
|
|
112
|
+
this.signal = options.signal;
|
|
113
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? DOWNSTREAM_REQUEST_TIMEOUT_MS;
|
|
33
114
|
}
|
|
34
|
-
authHeaders() {
|
|
115
|
+
async authHeaders() {
|
|
35
116
|
return {
|
|
36
|
-
Authorization: `Bearer ${this.
|
|
117
|
+
Authorization: `Bearer ${await this.credential.getAccessToken()}`
|
|
37
118
|
};
|
|
38
119
|
}
|
|
39
120
|
/**
|
|
40
|
-
* Stable per-
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
121
|
+
* Stable per-credential identifier. The MCP never sees the userId, so this
|
|
122
|
+
* stands in as the user component of derived idempotency keys. Never log or
|
|
123
|
+
* return the raw credential material to a caller: implementations hash or
|
|
124
|
+
* otherwise derive a safe fingerprint.
|
|
44
125
|
*/
|
|
45
126
|
fingerprint() {
|
|
46
|
-
return
|
|
127
|
+
return this.credential.fingerprint();
|
|
47
128
|
}
|
|
48
129
|
publicUrl(path3) {
|
|
49
130
|
const normalizedPath = path3.startsWith("/") ? path3 : `/${path3}`;
|
|
50
131
|
return new URL(`${this.baseUrl}${normalizedPath}`).toString();
|
|
51
132
|
}
|
|
52
|
-
async getJson(path3) {
|
|
53
|
-
const res = await
|
|
54
|
-
headers: this.authHeaders()
|
|
55
|
-
});
|
|
133
|
+
async getJson(path3, options) {
|
|
134
|
+
const res = await this.request(`${this.baseUrl}${path3}`, {
|
|
135
|
+
headers: await this.authHeaders()
|
|
136
|
+
}, options);
|
|
56
137
|
return this.parse(res);
|
|
57
138
|
}
|
|
58
|
-
async postJson(path3, body) {
|
|
59
|
-
const
|
|
139
|
+
async postJson(path3, body, options) {
|
|
140
|
+
const authHeaders = await this.authHeaders();
|
|
141
|
+
const res = await this.request(`${this.baseUrl}${path3}`, {
|
|
60
142
|
method: "POST",
|
|
61
143
|
headers: {
|
|
62
|
-
...
|
|
144
|
+
...authHeaders,
|
|
63
145
|
"Content-Type": "application/json"
|
|
64
146
|
},
|
|
65
147
|
body: JSON.stringify(body)
|
|
66
|
-
});
|
|
148
|
+
}, options);
|
|
67
149
|
return this.parse(res);
|
|
68
150
|
}
|
|
69
|
-
async patchJson(path3, body) {
|
|
70
|
-
const
|
|
151
|
+
async patchJson(path3, body, options) {
|
|
152
|
+
const authHeaders = await this.authHeaders();
|
|
153
|
+
const res = await this.request(`${this.baseUrl}${path3}`, {
|
|
71
154
|
method: "PATCH",
|
|
72
155
|
headers: {
|
|
73
|
-
...
|
|
156
|
+
...authHeaders,
|
|
74
157
|
"Content-Type": "application/json"
|
|
75
158
|
},
|
|
76
159
|
body: JSON.stringify(body)
|
|
77
|
-
});
|
|
160
|
+
}, options);
|
|
78
161
|
return this.parse(res);
|
|
79
162
|
}
|
|
80
|
-
async putJson(path3, body) {
|
|
81
|
-
const
|
|
163
|
+
async putJson(path3, body, options) {
|
|
164
|
+
const authHeaders = await this.authHeaders();
|
|
165
|
+
const res = await this.request(`${this.baseUrl}${path3}`, {
|
|
82
166
|
method: "PUT",
|
|
83
167
|
headers: {
|
|
84
|
-
...
|
|
168
|
+
...authHeaders,
|
|
85
169
|
"Content-Type": "application/json"
|
|
86
170
|
},
|
|
87
171
|
body: JSON.stringify(body)
|
|
88
|
-
});
|
|
172
|
+
}, options);
|
|
89
173
|
return this.parse(res);
|
|
90
174
|
}
|
|
91
|
-
async deleteJson(path3) {
|
|
92
|
-
const res = await
|
|
175
|
+
async deleteJson(path3, options) {
|
|
176
|
+
const res = await this.request(`${this.baseUrl}${path3}`, {
|
|
93
177
|
method: "DELETE",
|
|
94
|
-
headers: this.authHeaders()
|
|
95
|
-
});
|
|
178
|
+
headers: await this.authHeaders()
|
|
179
|
+
}, options);
|
|
96
180
|
return this.parse(res);
|
|
97
181
|
}
|
|
98
182
|
/** Upload bytes to a presigned URL; the URL itself does not need API auth. */
|
|
99
|
-
async putBytes(url, body, contentType) {
|
|
100
|
-
const res = await
|
|
183
|
+
async putBytes(url, body, contentType, options) {
|
|
184
|
+
const res = await this.request(url, {
|
|
101
185
|
method: "PUT",
|
|
102
186
|
headers: {
|
|
103
187
|
"Content-Type": contentType,
|
|
104
188
|
"Content-Length": String(body.byteLength)
|
|
105
189
|
},
|
|
106
190
|
body: new Uint8Array(body)
|
|
107
|
-
});
|
|
191
|
+
}, options);
|
|
108
192
|
if (!res.ok) {
|
|
109
|
-
throw new ApiHttpError(res.status, await res
|
|
193
|
+
throw new ApiHttpError(res.status, await readResponseText(res, MAX_DOWNSTREAM_RESPONSE_BYTES).catch(() => ""), `HTTP ${res.status} uploading media`);
|
|
110
194
|
}
|
|
111
195
|
}
|
|
112
196
|
async postMultipart(path3, files, fields, options) {
|
|
@@ -131,39 +215,62 @@ var ApiClient = class {
|
|
|
131
215
|
form.append(k, v);
|
|
132
216
|
}
|
|
133
217
|
}
|
|
134
|
-
const
|
|
218
|
+
const authHeaders = await this.authHeaders();
|
|
219
|
+
const res = await this.request(`${this.baseUrl}${path3}`, {
|
|
135
220
|
method: "POST",
|
|
136
221
|
headers: {
|
|
137
|
-
...
|
|
222
|
+
...authHeaders,
|
|
138
223
|
...options?.idempotencyKey ? {
|
|
139
224
|
"Idempotency-Key": options.idempotencyKey
|
|
140
225
|
} : {}
|
|
141
226
|
},
|
|
142
227
|
body: form
|
|
143
|
-
});
|
|
228
|
+
}, options);
|
|
144
229
|
return this.parse(res);
|
|
145
230
|
}
|
|
231
|
+
request(input, init, options) {
|
|
232
|
+
const signals = [
|
|
233
|
+
AbortSignal.timeout(this.requestTimeoutMs),
|
|
234
|
+
this.signal,
|
|
235
|
+
options?.signal
|
|
236
|
+
].filter((signal2) => signal2 !== void 0);
|
|
237
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
238
|
+
return fetch(input, {
|
|
239
|
+
...init,
|
|
240
|
+
signal
|
|
241
|
+
});
|
|
242
|
+
}
|
|
146
243
|
async parse(res) {
|
|
244
|
+
let bodyText;
|
|
245
|
+
try {
|
|
246
|
+
bodyText = await readResponseText(res, MAX_DOWNSTREAM_RESPONSE_BYTES);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (!res.ok) {
|
|
249
|
+
throw new ApiHttpError(res.status, "[response body unavailable]", `HTTP ${res.status} from ${res.url}`);
|
|
250
|
+
}
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
147
253
|
if (!res.ok) {
|
|
148
254
|
let body;
|
|
149
255
|
try {
|
|
150
|
-
body =
|
|
256
|
+
body = JSON.parse(bodyText);
|
|
151
257
|
} catch {
|
|
152
|
-
body =
|
|
258
|
+
body = bodyText;
|
|
153
259
|
}
|
|
154
260
|
throw new ApiHttpError(res.status, body, `HTTP ${res.status} from ${res.url}`);
|
|
155
261
|
}
|
|
156
|
-
return
|
|
262
|
+
return JSON.parse(bodyText);
|
|
157
263
|
}
|
|
158
264
|
};
|
|
159
265
|
|
|
160
266
|
// src/index.ts
|
|
161
|
-
var MCP_VERSION = "3.1.
|
|
267
|
+
var MCP_VERSION = "3.1.1";
|
|
162
268
|
|
|
163
269
|
// src/meta-upload-app.ts
|
|
164
270
|
import { readFileSync } from "fs";
|
|
165
271
|
import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
|
166
272
|
var MEDIA_UPLOAD_APP_RESOURCE_URI = "ui://media-upload/v1.html";
|
|
273
|
+
var MEDIA_UPLOAD_APP_DOMAIN = "https://productmaker.app";
|
|
167
274
|
var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
|
|
168
275
|
var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
|
|
169
276
|
var MEDIA_UPLOAD_APP_CSP = {
|
|
@@ -209,6 +316,7 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
|
|
|
209
316
|
mimeType: RESOURCE_MIME_TYPE,
|
|
210
317
|
_meta: {
|
|
211
318
|
ui: {
|
|
319
|
+
domain: MEDIA_UPLOAD_APP_DOMAIN,
|
|
212
320
|
csp
|
|
213
321
|
}
|
|
214
322
|
}
|
|
@@ -220,6 +328,7 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
|
|
|
220
328
|
text: html,
|
|
221
329
|
_meta: {
|
|
222
330
|
ui: {
|
|
331
|
+
domain: MEDIA_UPLOAD_APP_DOMAIN,
|
|
223
332
|
csp
|
|
224
333
|
}
|
|
225
334
|
}
|
|
@@ -425,8 +534,15 @@ async function resolveImageUrl(rawUrl) {
|
|
|
425
534
|
}
|
|
426
535
|
const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
|
|
427
536
|
if (!ALLOWED_MIMES.has(ct)) throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `content-type ${ct}`);
|
|
428
|
-
|
|
429
|
-
|
|
537
|
+
let buf;
|
|
538
|
+
try {
|
|
539
|
+
buf = Buffer.from(await readResponseBytes(res, MAX_BYTES));
|
|
540
|
+
} catch (error) {
|
|
541
|
+
if (error instanceof ResponseBodyTooLargeError) {
|
|
542
|
+
throw new ResolveImageError("IMAGE_TOO_LARGE", `image URL exceeds ${MAX_BYTES} bytes`);
|
|
543
|
+
}
|
|
544
|
+
throw new ResolveImageError("IMAGE_DOWNLOAD_FAILED", "image response could not be read");
|
|
545
|
+
}
|
|
430
546
|
const sniffed = sniffMime(buf);
|
|
431
547
|
if (!sniffed || sniffed !== ct) {
|
|
432
548
|
throw new ResolveImageError("IMAGE_TYPE_UNSUPPORTED", `magic bytes (${sniffed ?? "unknown"}) do not match content-type (${ct})`);
|
|
@@ -6871,6 +6987,16 @@ var DEFAULT_PRICING = {
|
|
|
6871
6987
|
ourCostCents: 0.15,
|
|
6872
6988
|
userPriceCents: 0.3
|
|
6873
6989
|
},
|
|
6990
|
+
// ⚠️ Both call sites below moved from gemini-3-flash-preview to gemini-3.7-flash on
|
|
6991
|
+
// 2026-08-15 (#1418) and these rates predate that. They are NOT re-measured, and the
|
|
6992
|
+
// numbers are LEFT UNCHANGED by the same owner decision that governs the whole table
|
|
6993
|
+
// (launch pricing is expected to rise; real user billing derives from actual
|
|
6994
|
+
// usage.cost, so these only feed the estimator and the pre-flight quote ceiling).
|
|
6995
|
+
// - product-accuracy: ZERO calls in the last 20 days of prod billing, so there is no
|
|
6996
|
+
// measurement to compare against and the gate may not be reachable at all.
|
|
6997
|
+
// - wearable-product-semantics: measured 0.4303¢/call over 121 prod calls (4,970
|
|
6998
|
+
// prompt tokens) on flash-preview — this 0.11¢ fallback already UNDER-quotes by
|
|
6999
|
+
// ~4x, independently of the model move. Re-measure after #1418 is deployed.
|
|
6874
7000
|
"openrouter:product-accuracy": {
|
|
6875
7001
|
ourCostCents: 0.15,
|
|
6876
7002
|
userPriceCents: 0.3
|
|
@@ -9182,13 +9308,21 @@ var WHATSAPP_ONLY_CHANNEL_EXPOSURE = {
|
|
|
9182
9308
|
mcp: "unavailable",
|
|
9183
9309
|
whatsapp: "adapted"
|
|
9184
9310
|
};
|
|
9185
|
-
function defineCapability(name, schemaHash, confirmation, routingDomain, effects, inputSlots, consumes, produces, channelExposure = SHARED_CHANNEL_EXPOSURE) {
|
|
9311
|
+
function defineCapability(name, schemaHash, confirmation, routingDomain, effects, inputSlots, consumes, produces, channelExposure = SHARED_CHANNEL_EXPOSURE, conversationInputs) {
|
|
9186
9312
|
return {
|
|
9187
9313
|
name,
|
|
9188
9314
|
schemaHash,
|
|
9189
9315
|
confirmation,
|
|
9190
9316
|
routingDomain,
|
|
9191
9317
|
inputSlots,
|
|
9318
|
+
conversation: {
|
|
9319
|
+
inputs: conversationInputs ?? inputSlots.map((input) => ({
|
|
9320
|
+
slot: input.name,
|
|
9321
|
+
askBeforePaidExecution: input.required,
|
|
9322
|
+
explicitOmissionAllowed: !input.required,
|
|
9323
|
+
fieldCompletion: "any"
|
|
9324
|
+
}))
|
|
9325
|
+
},
|
|
9192
9326
|
effects,
|
|
9193
9327
|
artifacts: {
|
|
9194
9328
|
consumes,
|
|
@@ -9686,6 +9820,11 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9686
9820
|
"compareAtPrice",
|
|
9687
9821
|
"offer",
|
|
9688
9822
|
"paymentMethods"
|
|
9823
|
+
], [
|
|
9824
|
+
"price",
|
|
9825
|
+
"offer",
|
|
9826
|
+
"compareAtPrice",
|
|
9827
|
+
"paymentMethods"
|
|
9689
9828
|
]),
|
|
9690
9829
|
slot("product_context", false, [
|
|
9691
9830
|
"productContext",
|
|
@@ -9707,6 +9846,25 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9707
9846
|
"product_reference"
|
|
9708
9847
|
], [
|
|
9709
9848
|
"landing"
|
|
9849
|
+
], SHARED_CHANNEL_EXPOSURE, [
|
|
9850
|
+
{
|
|
9851
|
+
slot: "product_reference",
|
|
9852
|
+
askBeforePaidExecution: true,
|
|
9853
|
+
explicitOmissionAllowed: false,
|
|
9854
|
+
fieldCompletion: "any"
|
|
9855
|
+
},
|
|
9856
|
+
{
|
|
9857
|
+
slot: "locale",
|
|
9858
|
+
askBeforePaidExecution: true,
|
|
9859
|
+
explicitOmissionAllowed: false,
|
|
9860
|
+
fieldCompletion: "all"
|
|
9861
|
+
},
|
|
9862
|
+
{
|
|
9863
|
+
slot: "commercial_offer",
|
|
9864
|
+
askBeforePaidExecution: true,
|
|
9865
|
+
explicitOmissionAllowed: true,
|
|
9866
|
+
fieldCompletion: "all"
|
|
9867
|
+
}
|
|
9710
9868
|
]),
|
|
9711
9869
|
defineCapability("generate_video_creative", "6bcd073df66023f8108a671b3027e1f35e9596cf9481ddf13a0282b4c56f5f34", "credit_consuming", "creative", [
|
|
9712
9870
|
"credit_consuming",
|
|
@@ -10081,7 +10239,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
10081
10239
|
], [
|
|
10082
10240
|
"meta_upload_session"
|
|
10083
10241
|
], WHATSAPP_ONLY_CHANNEL_EXPOSURE),
|
|
10084
|
-
defineCapability("request_media_upload", "
|
|
10242
|
+
defineCapability("request_media_upload", "e91cda1b2d9e44cecf28990b8018ce535ae4e9e6594fa80f503a1bf69d7146ee", "none", "creative", [
|
|
10085
10243
|
"state_change"
|
|
10086
10244
|
], [
|
|
10087
10245
|
slot("media_kind", true, [
|
|
@@ -10389,7 +10547,7 @@ async function readResponseWithCap(response) {
|
|
|
10389
10547
|
if (next.done) break;
|
|
10390
10548
|
total += next.value.byteLength;
|
|
10391
10549
|
if (total > MAX_MEDIA_BYTES) {
|
|
10392
|
-
|
|
10550
|
+
void reader.cancel().catch(() => void 0);
|
|
10393
10551
|
throw new ResolveMediaError("MEDIA_TOO_LARGE", `media URL exceeds ${MAX_MEDIA_BYTES} bytes`);
|
|
10394
10552
|
}
|
|
10395
10553
|
chunks.push(Buffer.from(next.value));
|
|
@@ -10537,6 +10695,7 @@ async function resolveMediaRef(ref, opts) {
|
|
|
10537
10695
|
if (!response.ok) throw new ResolveMediaError("MEDIA_URL_UNAVAILABLE", `media URL returned HTTP ${response.status}`);
|
|
10538
10696
|
const contentLength = Number(response.headers.get("content-length") ?? 0);
|
|
10539
10697
|
if (contentLength > MAX_MEDIA_BYTES) {
|
|
10698
|
+
void response.body?.cancel().catch(() => void 0);
|
|
10540
10699
|
throw new ResolveMediaError("MEDIA_TOO_LARGE", `media URL exceeds ${MAX_MEDIA_BYTES} bytes`);
|
|
10541
10700
|
}
|
|
10542
10701
|
const buffer = await readResponseWithCap(response);
|
|
@@ -11450,8 +11609,8 @@ function sanitizeFilename(s) {
|
|
|
11450
11609
|
return s.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 64) || "asset";
|
|
11451
11610
|
}
|
|
11452
11611
|
__name(sanitizeFilename, "sanitizeFilename");
|
|
11453
|
-
async function fetchTaskOutputs(api, taskId) {
|
|
11454
|
-
const raw = await api.getJson(`/v1/results/${encodeURIComponent(taskId)}?signedUrls=true
|
|
11612
|
+
async function fetchTaskOutputs(api, taskId, options) {
|
|
11613
|
+
const raw = await api.getJson(`/v1/results/${encodeURIComponent(taskId)}?signedUrls=true`, options);
|
|
11455
11614
|
const videos = (raw.videoGenerators ?? []).map((vg) => {
|
|
11456
11615
|
const variants2 = (vg.variants ?? []).filter((v) => Boolean(v.kind) && Boolean(v.url)).map((v) => ({
|
|
11457
11616
|
kind: v.kind,
|
|
@@ -11952,12 +12111,16 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
11952
12111
|
});
|
|
11953
12112
|
server.tool("get_task_status", "Devuelve el estado y URLs de outputs de una tarea. Si waitSeconds>0, hace polling hasta que cambie la fase, aparezca `pendingUserAction`, o llegue al timeout (max 120s). Cuando la tarea queda en `done`, incluye enlaces a sus outputs.\n\nSi aparece `pendingUserAction`, el pipeline est\xE1 PARADO: muestra el draft y llama `resolve_pending_action` repitiendo el mismo `kind`. Kinds: `product_reference_set_approval`, `product_reference_confirmation`, `angle_review_approval`, `service_angle_approval` y `service_script_approval`.", GetTaskStatusInput.shape, async (input, extra) => {
|
|
11954
12113
|
try {
|
|
11955
|
-
const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`), "fetcher");
|
|
11956
12114
|
const signal = extra?.signal;
|
|
12115
|
+
const fetcher = /* @__PURE__ */ __name(() => api.getJson(`/v1/status/${encodeURIComponent(input.taskId)}`, {
|
|
12116
|
+
signal
|
|
12117
|
+
}), "fetcher");
|
|
11957
12118
|
const snapshot = await pollStatus(fetcher, input.waitSeconds ?? 0, signal);
|
|
11958
12119
|
if (snapshot.status !== "done") return asText(snapshot);
|
|
11959
12120
|
if (snapshot.kind === "service") {
|
|
11960
|
-
const result = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-result
|
|
12121
|
+
const result = await api.getJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/service-result`, {
|
|
12122
|
+
signal
|
|
12123
|
+
});
|
|
11961
12124
|
return {
|
|
11962
12125
|
content: [
|
|
11963
12126
|
{
|
|
@@ -11977,7 +12140,9 @@ function registerTaskTools(server, api, { proxyBaseUrl = null, transport = "http
|
|
|
11977
12140
|
};
|
|
11978
12141
|
}
|
|
11979
12142
|
try {
|
|
11980
|
-
const rawOutputs = await fetchTaskOutputs(api, input.taskId
|
|
12143
|
+
const rawOutputs = await fetchTaskOutputs(api, input.taskId, {
|
|
12144
|
+
signal
|
|
12145
|
+
});
|
|
11981
12146
|
const outputs = proxifyOutputs(rawOutputs, proxyBaseUrl);
|
|
11982
12147
|
return {
|
|
11983
12148
|
content: [
|
|
@@ -13513,11 +13678,11 @@ var UploadSessionInputBase = z55.object({
|
|
|
13513
13678
|
maxFiles: z55.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
|
|
13514
13679
|
});
|
|
13515
13680
|
var OpenAiFileReference = z55.object({
|
|
13516
|
-
download_url: z55.string().url(),
|
|
13681
|
+
download_url: z55.string().url().optional().describe("Optional host-provided download URL. The App prefers getFileDownloadUrl(file_id) and the server never fetches this URL."),
|
|
13517
13682
|
file_id: z55.string().min(1),
|
|
13518
13683
|
mime_type: z55.string().min(1).optional(),
|
|
13519
13684
|
file_name: z55.string().min(1).optional()
|
|
13520
|
-
})
|
|
13685
|
+
});
|
|
13521
13686
|
var UploadSessionToolInputShape = {
|
|
13522
13687
|
...UploadSessionInputBase.shape,
|
|
13523
13688
|
files: z55.array(OpenAiFileReference).max(10).optional().describe("Optional OpenAI-hosted file references provided by ChatGPT. They are only metadata for the UI bridge and are never forwarded to ProductMaker.")
|
|
@@ -14800,7 +14965,7 @@ __name(registerProductMakerPrompts, "registerProductMakerPrompts");
|
|
|
14800
14965
|
|
|
14801
14966
|
// src/server.ts
|
|
14802
14967
|
function createServer(opts) {
|
|
14803
|
-
const {
|
|
14968
|
+
const { apiBaseUrl, proxyBaseUrl = null, transport = "http", instructions = PRODUCTMAKER_SERVER_INSTRUCTIONS } = opts;
|
|
14804
14969
|
const server = new McpServer({
|
|
14805
14970
|
name: "productmaker",
|
|
14806
14971
|
version: MCP_VERSION
|
|
@@ -14811,9 +14976,11 @@ function createServer(opts) {
|
|
|
14811
14976
|
},
|
|
14812
14977
|
logging: {}
|
|
14813
14978
|
},
|
|
14814
|
-
instructions
|
|
14979
|
+
instructions
|
|
14980
|
+
});
|
|
14981
|
+
const api = new ApiClient(apiBaseUrl, opts.credential, {
|
|
14982
|
+
signal: opts.abortSignal
|
|
14815
14983
|
});
|
|
14816
|
-
const api = new ApiClient(apiBaseUrl, apiKey);
|
|
14817
14984
|
registerProductMakerPrompts(server);
|
|
14818
14985
|
registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"));
|
|
14819
14986
|
registerProductCostingTools(server);
|
|
@@ -14878,7 +15045,7 @@ async function main() {
|
|
|
14878
15045
|
process.exit(1);
|
|
14879
15046
|
}
|
|
14880
15047
|
const server = createServer({
|
|
14881
|
-
apiKey,
|
|
15048
|
+
credential: new StaticApiKeyCredential(apiKey),
|
|
14882
15049
|
apiBaseUrl,
|
|
14883
15050
|
transport: "stdio"
|
|
14884
15051
|
});
|
|
@@ -2273,19 +2273,27 @@
|
|
|
2273
2273
|
status.status === "pending" ||
|
|
2274
2274
|
(state.items.length > 0 &&
|
|
2275
2275
|
status.assets.length < state.items.length);
|
|
2276
|
+
const canContinueInChat = canMessageHost();
|
|
2276
2277
|
if (partial) {
|
|
2277
2278
|
requireFallback(
|
|
2278
2279
|
"Carga parcial verificada. Puedes continuar desde el enlace seguro si algún archivo no terminó.",
|
|
2279
2280
|
"warning",
|
|
2280
2281
|
);
|
|
2282
|
+
} else if (!canContinueInChat) {
|
|
2283
|
+
requireFallback(
|
|
2284
|
+
"Carga completada. Este chat no puede continuar desde el widget. Abre el enlace seguro para seguir.",
|
|
2285
|
+
"warning",
|
|
2286
|
+
);
|
|
2281
2287
|
} else {
|
|
2282
2288
|
setFallbackVisibility(state.fallbackRequired);
|
|
2283
2289
|
}
|
|
2284
2290
|
setSessionStatus(
|
|
2285
2291
|
partial
|
|
2286
2292
|
? "Carga parcial verificada. Continuaremos solo con los assets listos."
|
|
2287
|
-
:
|
|
2288
|
-
|
|
2293
|
+
: canContinueInChat
|
|
2294
|
+
? "Carga completada."
|
|
2295
|
+
: "Carga completada. Abre el enlace seguro para seguir.",
|
|
2296
|
+
partial || !canContinueInChat ? "warning" : "success",
|
|
2289
2297
|
);
|
|
2290
2298
|
} catch (error) {
|
|
2291
2299
|
const expired =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
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.",
|
|
@@ -53,7 +53,9 @@
|
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
54
54
|
"express": "^4.19.2",
|
|
55
55
|
"express-rate-limit": "^7.4.0",
|
|
56
|
+
"jose": "^5.2.0",
|
|
56
57
|
"pino": "^9.0.0",
|
|
58
|
+
"posthog-node": "^5.24.11",
|
|
57
59
|
"zod": "^3.23.8"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
@@ -64,8 +66,8 @@
|
|
|
64
66
|
"tsx": "^4.16.0",
|
|
65
67
|
"typescript": "^7.0.2",
|
|
66
68
|
"vitest": "^2.1.9",
|
|
67
|
-
"@packages/product-research": "0.1.0",
|
|
68
69
|
"@packages/product-economics": "0.1.0",
|
|
70
|
+
"@packages/product-research": "0.1.0",
|
|
69
71
|
"@packages/types": "0.1.0"
|
|
70
72
|
},
|
|
71
73
|
"publishConfig": {
|