@productmaker/mcp 3.0.0 → 3.0.2
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/http.js +66 -15
- package/dist/stdio.js +66 -15
- package/dist/ui/meta-upload-app.html +91 -28
- package/package.json +3 -3
package/dist/http.js
CHANGED
|
@@ -28,11 +28,11 @@ var ApiClient = class {
|
|
|
28
28
|
static {
|
|
29
29
|
__name(this, "ApiClient");
|
|
30
30
|
}
|
|
31
|
-
baseUrl;
|
|
32
31
|
apiKey;
|
|
32
|
+
baseUrl;
|
|
33
33
|
constructor(baseUrl, apiKey) {
|
|
34
|
-
this.baseUrl = baseUrl;
|
|
35
34
|
this.apiKey = apiKey;
|
|
35
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
36
36
|
}
|
|
37
37
|
authHeaders() {
|
|
38
38
|
return {
|
|
@@ -48,6 +48,10 @@ var ApiClient = class {
|
|
|
48
48
|
fingerprint() {
|
|
49
49
|
return createHash("sha256").update(this.apiKey).digest("hex").slice(0, 32);
|
|
50
50
|
}
|
|
51
|
+
publicUrl(path3) {
|
|
52
|
+
const normalizedPath = path3.startsWith("/") ? path3 : `/${path3}`;
|
|
53
|
+
return new URL(`${this.baseUrl}${normalizedPath}`).toString();
|
|
54
|
+
}
|
|
51
55
|
async getJson(path3) {
|
|
52
56
|
const res = await fetch(`${this.baseUrl}${path3}`, {
|
|
53
57
|
headers: this.authHeaders()
|
|
@@ -157,7 +161,7 @@ var ApiClient = class {
|
|
|
157
161
|
};
|
|
158
162
|
|
|
159
163
|
// src/index.ts
|
|
160
|
-
var MCP_VERSION = "3.0.
|
|
164
|
+
var MCP_VERSION = "3.0.2";
|
|
161
165
|
|
|
162
166
|
// src/meta-upload-app.ts
|
|
163
167
|
import { readFileSync } from "fs";
|
|
@@ -167,12 +171,25 @@ var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
|
|
|
167
171
|
var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
|
|
168
172
|
var MEDIA_UPLOAD_APP_CSP = {
|
|
169
173
|
connectDomains: [
|
|
174
|
+
"https://api.productmaker.app",
|
|
170
175
|
"https://productmaker.app",
|
|
171
176
|
"https://www.productmaker.app",
|
|
172
177
|
"https://*.r2.cloudflarestorage.com",
|
|
173
178
|
"https://*.oaiusercontent.com"
|
|
174
179
|
]
|
|
175
180
|
};
|
|
181
|
+
function buildMediaUploadAppCsp(resolveEndpoint) {
|
|
182
|
+
const resolveOrigin = new URL(resolveEndpoint).origin;
|
|
183
|
+
return {
|
|
184
|
+
connectDomains: [
|
|
185
|
+
.../* @__PURE__ */ new Set([
|
|
186
|
+
...MEDIA_UPLOAD_APP_CSP.connectDomains,
|
|
187
|
+
resolveOrigin
|
|
188
|
+
])
|
|
189
|
+
]
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
__name(buildMediaUploadAppCsp, "buildMediaUploadAppCsp");
|
|
176
193
|
function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
|
|
177
194
|
try {
|
|
178
195
|
return readFileSync(htmlUrl, "utf8");
|
|
@@ -182,14 +199,20 @@ function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
|
|
|
182
199
|
}
|
|
183
200
|
}
|
|
184
201
|
__name(readMediaUploadAppHtml, "readMediaUploadAppHtml");
|
|
185
|
-
function
|
|
186
|
-
const
|
|
202
|
+
function renderMediaUploadAppHtml(resolveEndpoint) {
|
|
203
|
+
const encodedEndpoint = JSON.stringify(resolveEndpoint).slice(1, -1).replaceAll("<", "\\u003c");
|
|
204
|
+
return readMediaUploadAppHtml().replace("__PRODUCTMAKER_RESOLVE_ENDPOINT__", encodedEndpoint);
|
|
205
|
+
}
|
|
206
|
+
__name(renderMediaUploadAppHtml, "renderMediaUploadAppHtml");
|
|
207
|
+
function registerMediaUploadAppResource(server, resolveEndpoint) {
|
|
208
|
+
const html = renderMediaUploadAppHtml(resolveEndpoint);
|
|
209
|
+
const csp = buildMediaUploadAppCsp(resolveEndpoint);
|
|
187
210
|
registerAppResource(server, MEDIA_UPLOAD_APP_NAME, MEDIA_UPLOAD_APP_RESOURCE_URI, {
|
|
188
211
|
title: "Upload media",
|
|
189
212
|
mimeType: RESOURCE_MIME_TYPE,
|
|
190
213
|
_meta: {
|
|
191
214
|
ui: {
|
|
192
|
-
csp
|
|
215
|
+
csp
|
|
193
216
|
}
|
|
194
217
|
}
|
|
195
218
|
}, async () => ({
|
|
@@ -200,7 +223,7 @@ function registerMediaUploadAppResource(server) {
|
|
|
200
223
|
text: html,
|
|
201
224
|
_meta: {
|
|
202
225
|
ui: {
|
|
203
|
-
csp
|
|
226
|
+
csp
|
|
204
227
|
}
|
|
205
228
|
}
|
|
206
229
|
}
|
|
@@ -8977,7 +9000,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
8977
9000
|
], [
|
|
8978
9001
|
"meta_upload_session"
|
|
8979
9002
|
], WHATSAPP_ONLY_CHANNEL_EXPOSURE),
|
|
8980
|
-
defineCapability("request_media_upload", "
|
|
9003
|
+
defineCapability("request_media_upload", "d7ccfffe00f8563219fb3e036b07649ff59334e03fc52f6a2f3cb0823d4aa948", "none", "creative", [
|
|
8981
9004
|
"state_change"
|
|
8982
9005
|
], [
|
|
8983
9006
|
slot("media_kind", true, [
|
|
@@ -8986,6 +9009,9 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
8986
9009
|
slot("asset_role", false, [
|
|
8987
9010
|
"assetRole"
|
|
8988
9011
|
], []),
|
|
9012
|
+
slot("upload_purpose", false, [
|
|
9013
|
+
"uploadPurpose"
|
|
9014
|
+
], []),
|
|
8989
9015
|
slot("file_limit", false, [
|
|
8990
9016
|
"maxFiles"
|
|
8991
9017
|
], []),
|
|
@@ -12324,6 +12350,11 @@ var UploadSessionInputBase = z54.object({
|
|
|
12324
12350
|
"media",
|
|
12325
12351
|
"thumbnail"
|
|
12326
12352
|
]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
|
|
12353
|
+
uploadPurpose: z54.enum([
|
|
12354
|
+
"product_reference",
|
|
12355
|
+
"creative",
|
|
12356
|
+
"generic"
|
|
12357
|
+
]).default("generic").describe("The user's purpose for these files. Use product_reference for a product photo used by tasks or landings, creative for media that will become an ad or creative, and generic only when neither applies."),
|
|
12327
12358
|
maxFiles: z54.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
|
|
12328
12359
|
});
|
|
12329
12360
|
var OpenAiFileReference = z54.object({
|
|
@@ -12404,9 +12435,20 @@ var UniversalUploadStatusResponse = z54.discriminatedUnion("status", [
|
|
|
12404
12435
|
})
|
|
12405
12436
|
]);
|
|
12406
12437
|
var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
|
|
12438
|
+
uploadPurpose: z54.enum([
|
|
12439
|
+
"product_reference",
|
|
12440
|
+
"creative",
|
|
12441
|
+
"generic"
|
|
12442
|
+
]),
|
|
12407
12443
|
checkToolName: z54.literal("check_media_upload"),
|
|
12408
|
-
resolveEndpoint: z54.
|
|
12444
|
+
resolveEndpoint: z54.string().url()
|
|
12409
12445
|
});
|
|
12446
|
+
function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
|
|
12447
|
+
const url = new URL(uploadPageUrl);
|
|
12448
|
+
url.searchParams.set("purpose", uploadPurpose);
|
|
12449
|
+
return url.toString();
|
|
12450
|
+
}
|
|
12451
|
+
__name(contextualizeUploadPageUrl, "contextualizeUploadPageUrl");
|
|
12410
12452
|
function uploadSessionText(result) {
|
|
12411
12453
|
const mediaLabel = result.mediaKind === "mixed" ? "archivos" : result.mediaKind === "video" ? "video" : "imagen";
|
|
12412
12454
|
return {
|
|
@@ -12424,10 +12466,14 @@ function uploadSessionText(result) {
|
|
|
12424
12466
|
};
|
|
12425
12467
|
}
|
|
12426
12468
|
__name(uploadSessionText, "uploadSessionText");
|
|
12469
|
+
function resolveEndpointForSession(api) {
|
|
12470
|
+
return api.publicUrl("/v1/uploaded-assets/sessions/resolve");
|
|
12471
|
+
}
|
|
12472
|
+
__name(resolveEndpointForSession, "resolveEndpointForSession");
|
|
12427
12473
|
function registerMediaUploadTools(server, api) {
|
|
12428
12474
|
registerAppTool(server, "request_media_upload", {
|
|
12429
12475
|
title: "Subir archivos",
|
|
12430
|
-
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Ll\xE1malo autom\xE1ticamente cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. Tras OK, reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
12476
|
+
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Ll\xE1malo autom\xE1ticamente y antes de preguntar por campos faltantes cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. Tras OK, reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
12431
12477
|
inputSchema: UploadSessionToolInputShape,
|
|
12432
12478
|
outputSchema: UniversalUploadStructuredResponse.shape,
|
|
12433
12479
|
_meta: {
|
|
@@ -12449,12 +12495,14 @@ function registerMediaUploadTools(server, api) {
|
|
|
12449
12495
|
}, async (rawInput) => {
|
|
12450
12496
|
try {
|
|
12451
12497
|
const input = UploadSessionToolInput.parse(rawInput);
|
|
12452
|
-
const { files: _files, ...sessionInput } = input;
|
|
12498
|
+
const { files: _files, uploadPurpose, ...sessionInput } = input;
|
|
12453
12499
|
const session = UploadSessionResponse.parse(await api.postJson("/v1/uploaded-assets/sessions", sessionInput));
|
|
12454
12500
|
const result = UniversalUploadStructuredResponse.parse({
|
|
12455
12501
|
...session,
|
|
12502
|
+
uploadPageUrl: contextualizeUploadPageUrl(session.uploadPageUrl, uploadPurpose),
|
|
12503
|
+
uploadPurpose,
|
|
12456
12504
|
checkToolName: "check_media_upload",
|
|
12457
|
-
resolveEndpoint:
|
|
12505
|
+
resolveEndpoint: resolveEndpointForSession(api)
|
|
12458
12506
|
});
|
|
12459
12507
|
return {
|
|
12460
12508
|
structuredContent: result,
|
|
@@ -13544,8 +13592,8 @@ __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
|
|
|
13544
13592
|
// src/guidance/productmaker-guidance.ts
|
|
13545
13593
|
var START_DROPSHIPPING_PROMPT_NAME = "start_dropshipping";
|
|
13546
13594
|
var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
|
|
13547
|
-
"
|
|
13548
|
-
"
|
|
13595
|
+
"When a ProductMaker request needs an image or video the user attached, pasted, or has locally and no reusable ProductMaker assetId is already available, call request_media_upload immediately before asking for any missing business inputs. Do this even when the user says not to generate or publish yet: uploading only stages the file and does not generate, publish, or consume credits, unless the user explicitly says not to upload, share, or store the file. Treat natural requests such as 'create a landing with this product', 'make creatives with this photo', or 'upload this video to Meta' as upload intent; never require the user to name the uploader.",
|
|
13596
|
+
"For requests that do not need a new media upload, understand the user's intent before choosing a ProductMaker tool.",
|
|
13549
13597
|
"For remote MCP hosts, never ask for a local path, CDN URL, bytes, or base64. Pass supported host file references to request_media_upload; otherwise let its inline App open the file picker. The external upload page is fallback only.",
|
|
13550
13598
|
"After the upload App sends its OK continuation, resume the original request with the verified assetId values, do not ask for another upload, and ask only for remaining business inputs in chat. The host-inserted continuation may contain opaque asset IDs; do not repeat them in narrative prose or expose session IDs, tokens, private URLs, file names, or JSON handoff payloads.",
|
|
13551
13599
|
"Keep focused requests direct: creatives, landing pages, Shopify publication, Meta publication, and status checks do not require a DropshippingProject.",
|
|
@@ -13612,7 +13660,7 @@ function createServer(opts) {
|
|
|
13612
13660
|
});
|
|
13613
13661
|
const api = new ApiClient(apiBaseUrl, apiKey);
|
|
13614
13662
|
registerProductMakerPrompts(server);
|
|
13615
|
-
registerMediaUploadAppResource(server);
|
|
13663
|
+
registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"));
|
|
13616
13664
|
registerProductCostingTools(server);
|
|
13617
13665
|
registerProductResearchTools(server, api);
|
|
13618
13666
|
registerDropshippingProjectTools(server, api);
|
|
@@ -13647,6 +13695,9 @@ function assertSafePublicBaseUrl(envName, url) {
|
|
|
13647
13695
|
if (u.protocol !== "https:") {
|
|
13648
13696
|
throw new Error(`${envName} must use https (got ${u.protocol})`);
|
|
13649
13697
|
}
|
|
13698
|
+
if (u.username || u.password || u.search || u.hash) {
|
|
13699
|
+
throw new Error(`${envName} must not contain credentials, query parameters, or fragments`);
|
|
13700
|
+
}
|
|
13650
13701
|
if (isPrivateHost(u.hostname)) {
|
|
13651
13702
|
throw new Error(`${envName} must not point to a private/loopback host: ${u.hostname}`);
|
|
13652
13703
|
}
|
package/dist/stdio.js
CHANGED
|
@@ -25,11 +25,11 @@ var ApiClient = class {
|
|
|
25
25
|
static {
|
|
26
26
|
__name(this, "ApiClient");
|
|
27
27
|
}
|
|
28
|
-
baseUrl;
|
|
29
28
|
apiKey;
|
|
29
|
+
baseUrl;
|
|
30
30
|
constructor(baseUrl, apiKey) {
|
|
31
|
-
this.baseUrl = baseUrl;
|
|
32
31
|
this.apiKey = apiKey;
|
|
32
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
33
33
|
}
|
|
34
34
|
authHeaders() {
|
|
35
35
|
return {
|
|
@@ -45,6 +45,10 @@ var ApiClient = class {
|
|
|
45
45
|
fingerprint() {
|
|
46
46
|
return createHash("sha256").update(this.apiKey).digest("hex").slice(0, 32);
|
|
47
47
|
}
|
|
48
|
+
publicUrl(path3) {
|
|
49
|
+
const normalizedPath = path3.startsWith("/") ? path3 : `/${path3}`;
|
|
50
|
+
return new URL(`${this.baseUrl}${normalizedPath}`).toString();
|
|
51
|
+
}
|
|
48
52
|
async getJson(path3) {
|
|
49
53
|
const res = await fetch(`${this.baseUrl}${path3}`, {
|
|
50
54
|
headers: this.authHeaders()
|
|
@@ -154,7 +158,7 @@ var ApiClient = class {
|
|
|
154
158
|
};
|
|
155
159
|
|
|
156
160
|
// src/index.ts
|
|
157
|
-
var MCP_VERSION = "3.0.
|
|
161
|
+
var MCP_VERSION = "3.0.2";
|
|
158
162
|
|
|
159
163
|
// src/meta-upload-app.ts
|
|
160
164
|
import { readFileSync } from "fs";
|
|
@@ -164,12 +168,25 @@ var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
|
|
|
164
168
|
var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
|
|
165
169
|
var MEDIA_UPLOAD_APP_CSP = {
|
|
166
170
|
connectDomains: [
|
|
171
|
+
"https://api.productmaker.app",
|
|
167
172
|
"https://productmaker.app",
|
|
168
173
|
"https://www.productmaker.app",
|
|
169
174
|
"https://*.r2.cloudflarestorage.com",
|
|
170
175
|
"https://*.oaiusercontent.com"
|
|
171
176
|
]
|
|
172
177
|
};
|
|
178
|
+
function buildMediaUploadAppCsp(resolveEndpoint) {
|
|
179
|
+
const resolveOrigin = new URL(resolveEndpoint).origin;
|
|
180
|
+
return {
|
|
181
|
+
connectDomains: [
|
|
182
|
+
.../* @__PURE__ */ new Set([
|
|
183
|
+
...MEDIA_UPLOAD_APP_CSP.connectDomains,
|
|
184
|
+
resolveOrigin
|
|
185
|
+
])
|
|
186
|
+
]
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
__name(buildMediaUploadAppCsp, "buildMediaUploadAppCsp");
|
|
173
190
|
function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
|
|
174
191
|
try {
|
|
175
192
|
return readFileSync(htmlUrl, "utf8");
|
|
@@ -179,14 +196,20 @@ function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
|
|
|
179
196
|
}
|
|
180
197
|
}
|
|
181
198
|
__name(readMediaUploadAppHtml, "readMediaUploadAppHtml");
|
|
182
|
-
function
|
|
183
|
-
const
|
|
199
|
+
function renderMediaUploadAppHtml(resolveEndpoint) {
|
|
200
|
+
const encodedEndpoint = JSON.stringify(resolveEndpoint).slice(1, -1).replaceAll("<", "\\u003c");
|
|
201
|
+
return readMediaUploadAppHtml().replace("__PRODUCTMAKER_RESOLVE_ENDPOINT__", encodedEndpoint);
|
|
202
|
+
}
|
|
203
|
+
__name(renderMediaUploadAppHtml, "renderMediaUploadAppHtml");
|
|
204
|
+
function registerMediaUploadAppResource(server, resolveEndpoint) {
|
|
205
|
+
const html = renderMediaUploadAppHtml(resolveEndpoint);
|
|
206
|
+
const csp = buildMediaUploadAppCsp(resolveEndpoint);
|
|
184
207
|
registerAppResource(server, MEDIA_UPLOAD_APP_NAME, MEDIA_UPLOAD_APP_RESOURCE_URI, {
|
|
185
208
|
title: "Upload media",
|
|
186
209
|
mimeType: RESOURCE_MIME_TYPE,
|
|
187
210
|
_meta: {
|
|
188
211
|
ui: {
|
|
189
|
-
csp
|
|
212
|
+
csp
|
|
190
213
|
}
|
|
191
214
|
}
|
|
192
215
|
}, async () => ({
|
|
@@ -197,7 +220,7 @@ function registerMediaUploadAppResource(server) {
|
|
|
197
220
|
text: html,
|
|
198
221
|
_meta: {
|
|
199
222
|
ui: {
|
|
200
|
-
csp
|
|
223
|
+
csp
|
|
201
224
|
}
|
|
202
225
|
}
|
|
203
226
|
}
|
|
@@ -8974,7 +8997,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
8974
8997
|
], [
|
|
8975
8998
|
"meta_upload_session"
|
|
8976
8999
|
], WHATSAPP_ONLY_CHANNEL_EXPOSURE),
|
|
8977
|
-
defineCapability("request_media_upload", "
|
|
9000
|
+
defineCapability("request_media_upload", "d7ccfffe00f8563219fb3e036b07649ff59334e03fc52f6a2f3cb0823d4aa948", "none", "creative", [
|
|
8978
9001
|
"state_change"
|
|
8979
9002
|
], [
|
|
8980
9003
|
slot("media_kind", true, [
|
|
@@ -8983,6 +9006,9 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
8983
9006
|
slot("asset_role", false, [
|
|
8984
9007
|
"assetRole"
|
|
8985
9008
|
], []),
|
|
9009
|
+
slot("upload_purpose", false, [
|
|
9010
|
+
"uploadPurpose"
|
|
9011
|
+
], []),
|
|
8986
9012
|
slot("file_limit", false, [
|
|
8987
9013
|
"maxFiles"
|
|
8988
9014
|
], []),
|
|
@@ -12280,6 +12306,11 @@ var UploadSessionInputBase = z54.object({
|
|
|
12280
12306
|
"media",
|
|
12281
12307
|
"thumbnail"
|
|
12282
12308
|
]).default("media").describe("Use media for the creative itself; thumbnail only for an image thumbnail."),
|
|
12309
|
+
uploadPurpose: z54.enum([
|
|
12310
|
+
"product_reference",
|
|
12311
|
+
"creative",
|
|
12312
|
+
"generic"
|
|
12313
|
+
]).default("generic").describe("The user's purpose for these files. Use product_reference for a product photo used by tasks or landings, creative for media that will become an ad or creative, and generic only when neither applies."),
|
|
12283
12314
|
maxFiles: z54.number().int().min(1).max(10).optional().describe("Optional batch limit: image accepts 1-5, video exactly 1, and mixed 1-10.")
|
|
12284
12315
|
});
|
|
12285
12316
|
var OpenAiFileReference = z54.object({
|
|
@@ -12360,9 +12391,20 @@ var UniversalUploadStatusResponse = z54.discriminatedUnion("status", [
|
|
|
12360
12391
|
})
|
|
12361
12392
|
]);
|
|
12362
12393
|
var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
|
|
12394
|
+
uploadPurpose: z54.enum([
|
|
12395
|
+
"product_reference",
|
|
12396
|
+
"creative",
|
|
12397
|
+
"generic"
|
|
12398
|
+
]),
|
|
12363
12399
|
checkToolName: z54.literal("check_media_upload"),
|
|
12364
|
-
resolveEndpoint: z54.
|
|
12400
|
+
resolveEndpoint: z54.string().url()
|
|
12365
12401
|
});
|
|
12402
|
+
function contextualizeUploadPageUrl(uploadPageUrl, uploadPurpose) {
|
|
12403
|
+
const url = new URL(uploadPageUrl);
|
|
12404
|
+
url.searchParams.set("purpose", uploadPurpose);
|
|
12405
|
+
return url.toString();
|
|
12406
|
+
}
|
|
12407
|
+
__name(contextualizeUploadPageUrl, "contextualizeUploadPageUrl");
|
|
12366
12408
|
function uploadSessionText(result) {
|
|
12367
12409
|
const mediaLabel = result.mediaKind === "mixed" ? "archivos" : result.mediaKind === "video" ? "video" : "imagen";
|
|
12368
12410
|
return {
|
|
@@ -12380,10 +12422,14 @@ function uploadSessionText(result) {
|
|
|
12380
12422
|
};
|
|
12381
12423
|
}
|
|
12382
12424
|
__name(uploadSessionText, "uploadSessionText");
|
|
12425
|
+
function resolveEndpointForSession(api) {
|
|
12426
|
+
return api.publicUrl("/v1/uploaded-assets/sessions/resolve");
|
|
12427
|
+
}
|
|
12428
|
+
__name(resolveEndpointForSession, "resolveEndpointForSession");
|
|
12383
12429
|
function registerMediaUploadTools(server, api) {
|
|
12384
12430
|
registerAppTool(server, "request_media_upload", {
|
|
12385
12431
|
title: "Subir archivos",
|
|
12386
|
-
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Ll\xE1malo autom\xE1ticamente cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. Tras OK, reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
12432
|
+
description: "Abre el selector universal de ProductMaker para im\xE1genes o videos del usuario. Ll\xE1malo autom\xE1ticamente y antes de preguntar por campos faltantes cuando una petici\xF3n natural adjunte o mencione un archivo local, por ejemplo \u2018crea una landing con este producto\u2019, \u2018haz creativos con esta foto\u2019 o \u2018sube este video a Meta\u2019; hazlo aunque el usuario diga que todav\xEDa no generes ni publiques, porque esta herramienta solo prepara el archivo y no consume cr\xE9ditos, salvo que proh\xEDba expl\xEDcitamente subir, compartir o almacenar el archivo; no pidas una ruta local ni URL. Es la \xFAnica ruta de carga para create_product_task, generate_image_creatives, generate_video_creative, generate_free_video, generate_landing y publish_creatives_to_meta. Pasa las referencias opcionales de ChatGPT en files; si el host no puede transferirlas, el mismo App muestra el selector inline. Usa uploadPurpose=product_reference para fotos de producto y landings, creative para creativos o anuncios, y generic solo cuando ninguno aplique. Usa mediaKind=image para referencias de producto y landings; mixed solo cuando el consumidor acepte ambos tipos. Tras OK, reanuda la intenci\xF3n original usando \xFAnicamente assetId y mediaKind, sin pedir que vuelva a subir ni mostrar datos t\xE9cnicos. El enlace externo es fallback solo si el App inline falla. Esta herramienta no genera, publica ni consume cr\xE9ditos.",
|
|
12387
12433
|
inputSchema: UploadSessionToolInputShape,
|
|
12388
12434
|
outputSchema: UniversalUploadStructuredResponse.shape,
|
|
12389
12435
|
_meta: {
|
|
@@ -12405,12 +12451,14 @@ function registerMediaUploadTools(server, api) {
|
|
|
12405
12451
|
}, async (rawInput) => {
|
|
12406
12452
|
try {
|
|
12407
12453
|
const input = UploadSessionToolInput.parse(rawInput);
|
|
12408
|
-
const { files: _files, ...sessionInput } = input;
|
|
12454
|
+
const { files: _files, uploadPurpose, ...sessionInput } = input;
|
|
12409
12455
|
const session = UploadSessionResponse.parse(await api.postJson("/v1/uploaded-assets/sessions", sessionInput));
|
|
12410
12456
|
const result = UniversalUploadStructuredResponse.parse({
|
|
12411
12457
|
...session,
|
|
12458
|
+
uploadPageUrl: contextualizeUploadPageUrl(session.uploadPageUrl, uploadPurpose),
|
|
12459
|
+
uploadPurpose,
|
|
12412
12460
|
checkToolName: "check_media_upload",
|
|
12413
|
-
resolveEndpoint:
|
|
12461
|
+
resolveEndpoint: resolveEndpointForSession(api)
|
|
12414
12462
|
});
|
|
12415
12463
|
return {
|
|
12416
12464
|
structuredContent: result,
|
|
@@ -13500,8 +13548,8 @@ __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
|
|
|
13500
13548
|
// src/guidance/productmaker-guidance.ts
|
|
13501
13549
|
var START_DROPSHIPPING_PROMPT_NAME = "start_dropshipping";
|
|
13502
13550
|
var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
|
|
13503
|
-
"
|
|
13504
|
-
"
|
|
13551
|
+
"When a ProductMaker request needs an image or video the user attached, pasted, or has locally and no reusable ProductMaker assetId is already available, call request_media_upload immediately before asking for any missing business inputs. Do this even when the user says not to generate or publish yet: uploading only stages the file and does not generate, publish, or consume credits, unless the user explicitly says not to upload, share, or store the file. Treat natural requests such as 'create a landing with this product', 'make creatives with this photo', or 'upload this video to Meta' as upload intent; never require the user to name the uploader.",
|
|
13552
|
+
"For requests that do not need a new media upload, understand the user's intent before choosing a ProductMaker tool.",
|
|
13505
13553
|
"For remote MCP hosts, never ask for a local path, CDN URL, bytes, or base64. Pass supported host file references to request_media_upload; otherwise let its inline App open the file picker. The external upload page is fallback only.",
|
|
13506
13554
|
"After the upload App sends its OK continuation, resume the original request with the verified assetId values, do not ask for another upload, and ask only for remaining business inputs in chat. The host-inserted continuation may contain opaque asset IDs; do not repeat them in narrative prose or expose session IDs, tokens, private URLs, file names, or JSON handoff payloads.",
|
|
13507
13555
|
"Keep focused requests direct: creatives, landing pages, Shopify publication, Meta publication, and status checks do not require a DropshippingProject.",
|
|
@@ -13568,7 +13616,7 @@ function createServer(opts) {
|
|
|
13568
13616
|
});
|
|
13569
13617
|
const api = new ApiClient(apiBaseUrl, apiKey);
|
|
13570
13618
|
registerProductMakerPrompts(server);
|
|
13571
|
-
registerMediaUploadAppResource(server);
|
|
13619
|
+
registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"));
|
|
13572
13620
|
registerProductCostingTools(server);
|
|
13573
13621
|
registerProductResearchTools(server, api);
|
|
13574
13622
|
registerDropshippingProjectTools(server, api);
|
|
@@ -13603,6 +13651,9 @@ function assertSafePublicBaseUrl(envName, url) {
|
|
|
13603
13651
|
if (u.protocol !== "https:") {
|
|
13604
13652
|
throw new Error(`${envName} must use https (got ${u.protocol})`);
|
|
13605
13653
|
}
|
|
13654
|
+
if (u.username || u.password || u.search || u.hash) {
|
|
13655
|
+
throw new Error(`${envName} must not contain credentials, query parameters, or fragments`);
|
|
13656
|
+
}
|
|
13606
13657
|
if (isPrivateHost(u.hostname)) {
|
|
13607
13658
|
throw new Error(`${envName} must not point to a private/loopback host: ${u.hostname}`);
|
|
13608
13659
|
}
|
|
@@ -147,6 +147,13 @@
|
|
|
147
147
|
padding: 18px;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
.upload-prompt {
|
|
151
|
+
margin: 0 0 12px;
|
|
152
|
+
color: var(--pm-text);
|
|
153
|
+
font-size: 15px;
|
|
154
|
+
font-weight: 700;
|
|
155
|
+
}
|
|
156
|
+
|
|
150
157
|
.media-grid {
|
|
151
158
|
display: grid;
|
|
152
159
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
@@ -435,6 +442,9 @@
|
|
|
435
442
|
<main class="app" data-testid="meta-upload-root">
|
|
436
443
|
<article class="card">
|
|
437
444
|
<section class="section" aria-label="Upload media">
|
|
445
|
+
<p class="upload-prompt" data-testid="meta-upload-prompt">
|
|
446
|
+
Sube tus archivos
|
|
447
|
+
</p>
|
|
438
448
|
<div class="media-grid">
|
|
439
449
|
<div
|
|
440
450
|
class="file-list"
|
|
@@ -535,7 +545,6 @@
|
|
|
535
545
|
const MAX_FILES = 10;
|
|
536
546
|
const MAX_FILE_SIZE_BYTES = 350 * 1024 * 1024;
|
|
537
547
|
const MAX_VIDEO_DURATION_SECONDS = 180;
|
|
538
|
-
const MIN_DIMENSION = 600;
|
|
539
548
|
const IMAGE_COMPRESSION_THRESHOLD_BYTES = 1.8 * 1024 * 1024;
|
|
540
549
|
const IMAGE_COMPRESSION_MAX_BYTES = 1.8 * 1024 * 1024;
|
|
541
550
|
const IMAGE_MAX_DIMENSION = 2048;
|
|
@@ -543,6 +552,7 @@
|
|
|
543
552
|
const TOOL_RESULT_TEXT_LIMIT = 100000;
|
|
544
553
|
const RPC_TIMEOUT_MS = 15000;
|
|
545
554
|
const APP_PROTOCOL_VERSION = "2026-01-26";
|
|
555
|
+
const CONFIGURED_RESOLVE_ENDPOINT = "__PRODUCTMAKER_RESOLVE_ENDPOINT__";
|
|
546
556
|
|
|
547
557
|
const elements = {
|
|
548
558
|
root: document.querySelector('[data-testid="meta-upload-root"]'),
|
|
@@ -568,6 +578,9 @@
|
|
|
568
578
|
completeButton: document.querySelector(
|
|
569
579
|
'[data-testid="meta-upload-complete-btn"]',
|
|
570
580
|
),
|
|
581
|
+
uploadPrompt: document.querySelector(
|
|
582
|
+
'[data-testid="meta-upload-prompt"]',
|
|
583
|
+
),
|
|
571
584
|
fileList: document.querySelector(
|
|
572
585
|
'[data-testid="meta-upload-file-list"]',
|
|
573
586
|
),
|
|
@@ -1156,6 +1169,7 @@
|
|
|
1156
1169
|
}
|
|
1157
1170
|
}
|
|
1158
1171
|
state.session = session;
|
|
1172
|
+
elements.uploadPrompt.textContent = uploadPromptForSession(session);
|
|
1159
1173
|
elements.fallbackLink.href = session.uploadPageUrl;
|
|
1160
1174
|
if (sessionExpired()) {
|
|
1161
1175
|
requireFallback(
|
|
@@ -1178,6 +1192,7 @@
|
|
|
1178
1192
|
const uploadPageUrl = boundedString(candidate.uploadPageUrl, 2000);
|
|
1179
1193
|
const mediaKind = candidate.mediaKind;
|
|
1180
1194
|
const assetRole = candidate.assetRole;
|
|
1195
|
+
const uploadPurpose = candidate.uploadPurpose;
|
|
1181
1196
|
const expiresAt = boundedString(candidate.expiresAt, 100);
|
|
1182
1197
|
const checkToolName = "check_media_upload";
|
|
1183
1198
|
const resolveEndpoint =
|
|
@@ -1189,7 +1204,10 @@
|
|
|
1189
1204
|
if (!Number.isInteger(candidate.maxFiles) || !expiresAt) return null;
|
|
1190
1205
|
try {
|
|
1191
1206
|
const parsedUrl = new URL(uploadPageUrl);
|
|
1192
|
-
|
|
1207
|
+
const parsedResolveUrl = new URL(resolveEndpoint, parsedUrl.origin);
|
|
1208
|
+
if (!safeResolveEndpointUrl(parsedResolveUrl, parsedUrl))
|
|
1209
|
+
return null;
|
|
1210
|
+
if (!safeUploadPageUrl(parsedUrl, parsedResolveUrl)) return null;
|
|
1193
1211
|
} catch {
|
|
1194
1212
|
return null;
|
|
1195
1213
|
}
|
|
@@ -1198,6 +1216,11 @@
|
|
|
1198
1216
|
uploadPageUrl,
|
|
1199
1217
|
mediaKind,
|
|
1200
1218
|
assetRole,
|
|
1219
|
+
uploadPurpose:
|
|
1220
|
+
uploadPurpose === "product_reference" ||
|
|
1221
|
+
uploadPurpose === "creative"
|
|
1222
|
+
? uploadPurpose
|
|
1223
|
+
: "generic",
|
|
1201
1224
|
maxFiles: candidate.maxFiles,
|
|
1202
1225
|
expiresAt,
|
|
1203
1226
|
checkToolName,
|
|
@@ -1205,6 +1228,26 @@
|
|
|
1205
1228
|
};
|
|
1206
1229
|
}
|
|
1207
1230
|
|
|
1231
|
+
function uploadPromptForSession(session) {
|
|
1232
|
+
if (session.uploadPurpose === "product_reference") {
|
|
1233
|
+
return session.mediaKind === "image"
|
|
1234
|
+
? "Sube la foto del producto"
|
|
1235
|
+
: session.mediaKind === "video"
|
|
1236
|
+
? "Sube el video del producto"
|
|
1237
|
+
: "Sube los archivos del producto";
|
|
1238
|
+
}
|
|
1239
|
+
if (session.uploadPurpose === "creative") {
|
|
1240
|
+
return session.mediaKind === "video"
|
|
1241
|
+
? "Sube tu video creativo"
|
|
1242
|
+
: "Sube tus creativos";
|
|
1243
|
+
}
|
|
1244
|
+
return session.mediaKind === "image"
|
|
1245
|
+
? "Sube tus imágenes"
|
|
1246
|
+
: session.mediaKind === "video"
|
|
1247
|
+
? "Sube tu video"
|
|
1248
|
+
: "Sube tus archivos";
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1208
1251
|
function mergeToolInput(input) {
|
|
1209
1252
|
state.toolInput = { ...state.toolInput, ...input };
|
|
1210
1253
|
void consumePendingBridgeFiles();
|
|
@@ -1630,15 +1673,6 @@
|
|
|
1630
1673
|
mediaKind === "image"
|
|
1631
1674
|
? await inspectImage(file)
|
|
1632
1675
|
: await inspectVideo(file);
|
|
1633
|
-
if (
|
|
1634
|
-
inspection.width < MIN_DIMENSION ||
|
|
1635
|
-
inspection.height < MIN_DIMENSION
|
|
1636
|
-
) {
|
|
1637
|
-
throw validationError(
|
|
1638
|
-
"low_resolution",
|
|
1639
|
-
"La resolución mínima es 600 × 600 px.",
|
|
1640
|
-
);
|
|
1641
|
-
}
|
|
1642
1676
|
if (
|
|
1643
1677
|
mediaKind === "video" &&
|
|
1644
1678
|
inspection.durationSeconds > MAX_VIDEO_DURATION_SECONDS
|
|
@@ -1747,12 +1781,6 @@
|
|
|
1747
1781
|
const canvas = document.createElement("canvas");
|
|
1748
1782
|
canvas.width = Math.max(1, Math.round(inspection.width * scale));
|
|
1749
1783
|
canvas.height = Math.max(1, Math.round(inspection.height * scale));
|
|
1750
|
-
if (canvas.width < MIN_DIMENSION || canvas.height < MIN_DIMENSION) {
|
|
1751
|
-
throw validationError(
|
|
1752
|
-
"low_resolution",
|
|
1753
|
-
"La resolución mínima final es 600 × 600 px.",
|
|
1754
|
-
);
|
|
1755
|
-
}
|
|
1756
1784
|
const context = canvas.getContext("2d", {
|
|
1757
1785
|
alpha: file.type !== "image/jpeg",
|
|
1758
1786
|
});
|
|
@@ -1848,17 +1876,28 @@
|
|
|
1848
1876
|
}
|
|
1849
1877
|
const error = new Error("upload_failed");
|
|
1850
1878
|
error.safeCode = "upload_failed";
|
|
1851
|
-
error.safeMessage =
|
|
1852
|
-
"La carga falló tras 3 intentos. Puedes reintentarla.";
|
|
1853
1879
|
error.safeStage =
|
|
1854
1880
|
lastError && lastError.safeStage ? lastError.safeStage : "upload";
|
|
1881
|
+
error.safeMessage =
|
|
1882
|
+
error.safeStage === "resolve"
|
|
1883
|
+
? "No pudimos preparar la carga. Reintenta o abre la carga segura."
|
|
1884
|
+
: error.safeStage === "put"
|
|
1885
|
+
? "No pudimos enviar el archivo al almacenamiento. Reintenta o abre la carga segura."
|
|
1886
|
+
: "La carga falló tras 3 intentos. Puedes reintentarla.";
|
|
1855
1887
|
throw error;
|
|
1856
1888
|
}
|
|
1857
1889
|
|
|
1858
1890
|
async function resolveUpload(file, uploadId) {
|
|
1859
1891
|
const uploadPage = new URL(state.session.uploadPageUrl);
|
|
1860
|
-
|
|
1861
|
-
|
|
1892
|
+
const resolveUrl = new URL(
|
|
1893
|
+
state.session.resolveEndpoint,
|
|
1894
|
+
uploadPage.origin,
|
|
1895
|
+
);
|
|
1896
|
+
if (
|
|
1897
|
+
!safeResolveEndpointUrl(resolveUrl, uploadPage) ||
|
|
1898
|
+
!safeUploadPageUrl(uploadPage, resolveUrl)
|
|
1899
|
+
)
|
|
1900
|
+
throw new Error("unsafe_upload_session");
|
|
1862
1901
|
const token =
|
|
1863
1902
|
uploadPage.searchParams.get("token") ||
|
|
1864
1903
|
new URLSearchParams(uploadPage.hash.slice(1)).get("token");
|
|
@@ -1867,10 +1906,6 @@
|
|
|
1867
1906
|
error.safeStage = "resolve";
|
|
1868
1907
|
throw error;
|
|
1869
1908
|
}
|
|
1870
|
-
const resolveUrl = new URL(
|
|
1871
|
-
state.session.resolveEndpoint,
|
|
1872
|
-
uploadPage.origin,
|
|
1873
|
-
);
|
|
1874
1909
|
const response = await fetch(resolveUrl.toString(), {
|
|
1875
1910
|
method: "POST",
|
|
1876
1911
|
headers: { "content-type": "application/json" },
|
|
@@ -1882,6 +1917,7 @@
|
|
|
1882
1917
|
fileSize: file.size,
|
|
1883
1918
|
}),
|
|
1884
1919
|
credentials: "omit",
|
|
1920
|
+
redirect: "error",
|
|
1885
1921
|
signal: activeAbortSignal(),
|
|
1886
1922
|
});
|
|
1887
1923
|
if (!response.ok) {
|
|
@@ -1947,7 +1983,7 @@
|
|
|
1947
1983
|
);
|
|
1948
1984
|
}
|
|
1949
1985
|
|
|
1950
|
-
function safeUploadPageUrl(url) {
|
|
1986
|
+
function safeUploadPageUrl(url, resolveUrl) {
|
|
1951
1987
|
if (url.username || url.password) return false;
|
|
1952
1988
|
if (
|
|
1953
1989
|
[
|
|
@@ -1957,11 +1993,38 @@
|
|
|
1957
1993
|
)
|
|
1958
1994
|
return true;
|
|
1959
1995
|
return (
|
|
1960
|
-
url.protocol === "http:" &&
|
|
1961
|
-
|
|
1996
|
+
(url.protocol === "http:" &&
|
|
1997
|
+
["localhost", "127.0.0.1", "::1"].includes(url.hostname)) ||
|
|
1998
|
+
(url.protocol === "https:" &&
|
|
1999
|
+
/^\/(?:[a-z]{2}\/)?upload-media\/?$/.test(url.pathname) &&
|
|
2000
|
+
isConfiguredResolveEndpoint(resolveUrl))
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
function isConfiguredResolveEndpoint(url) {
|
|
2005
|
+
return (
|
|
2006
|
+
/^https?:\/\//.test(CONFIGURED_RESOLVE_ENDPOINT) &&
|
|
2007
|
+
url.toString() === CONFIGURED_RESOLVE_ENDPOINT
|
|
1962
2008
|
);
|
|
1963
2009
|
}
|
|
1964
2010
|
|
|
2011
|
+
function safeResolveEndpointUrl(url, uploadPageUrl) {
|
|
2012
|
+
if (url.username || url.password) return false;
|
|
2013
|
+
if (url.search || url.hash) return false;
|
|
2014
|
+
const officialApi =
|
|
2015
|
+
url.origin === "https://api.productmaker.app" &&
|
|
2016
|
+
url.pathname === "/v1/uploaded-assets/sessions/resolve";
|
|
2017
|
+
const sameOriginWebProxy =
|
|
2018
|
+
url.origin === uploadPageUrl.origin &&
|
|
2019
|
+
url.pathname === "/api/upload-media/resolve";
|
|
2020
|
+
const localApi =
|
|
2021
|
+
url.protocol === "http:" &&
|
|
2022
|
+
["localhost", "127.0.0.1", "::1"].includes(url.hostname) &&
|
|
2023
|
+
url.pathname === "/v1/uploaded-assets/sessions/resolve";
|
|
2024
|
+
const configuredApi = isConfiguredResolveEndpoint(url);
|
|
2025
|
+
return officialApi || sameOriginWebProxy || localApi || configuredApi;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
1965
2028
|
function putFileWithProgress(url, file, onProgress) {
|
|
1966
2029
|
return new Promise((resolve, reject) => {
|
|
1967
2030
|
const request = new XMLHttpRequest();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productmaker/mcp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
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.",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"typescript": "^7.0.2",
|
|
66
66
|
"vitest": "^2.1.9",
|
|
67
67
|
"@packages/product-economics": "0.1.0",
|
|
68
|
-
"@packages/
|
|
69
|
-
"@packages/
|
|
68
|
+
"@packages/product-research": "0.1.0",
|
|
69
|
+
"@packages/types": "0.1.0"
|
|
70
70
|
},
|
|
71
71
|
"publishConfig": {
|
|
72
72
|
"access": "public"
|