@productmaker/mcp 2.10.1 → 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/README.md +7 -15
- package/dist/http.js +204 -200
- package/dist/stdio.js +202 -198
- package/dist/ui/meta-upload-app.html +108 -102
- package/package.json +1 -1
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,63 +158,76 @@ var ApiClient = class {
|
|
|
154
158
|
};
|
|
155
159
|
|
|
156
160
|
// src/index.ts
|
|
157
|
-
var MCP_VERSION = "
|
|
161
|
+
var MCP_VERSION = "3.0.2";
|
|
158
162
|
|
|
159
163
|
// src/meta-upload-app.ts
|
|
160
164
|
import { readFileSync } from "fs";
|
|
161
165
|
import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
|
162
|
-
var META_UPLOAD_APP_RESOURCE_URI = "ui://meta-upload/v1.html";
|
|
163
166
|
var MEDIA_UPLOAD_APP_RESOURCE_URI = "ui://media-upload/v1.html";
|
|
164
|
-
var
|
|
165
|
-
var
|
|
166
|
-
var
|
|
167
|
+
var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
|
|
168
|
+
var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
|
|
169
|
+
var MEDIA_UPLOAD_APP_CSP = {
|
|
167
170
|
connectDomains: [
|
|
171
|
+
"https://api.productmaker.app",
|
|
168
172
|
"https://productmaker.app",
|
|
169
173
|
"https://www.productmaker.app",
|
|
170
174
|
"https://*.r2.cloudflarestorage.com",
|
|
171
175
|
"https://*.oaiusercontent.com"
|
|
172
176
|
]
|
|
173
177
|
};
|
|
174
|
-
function
|
|
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");
|
|
190
|
+
function readMediaUploadAppHtml(htmlUrl = MEDIA_UPLOAD_APP_HTML_URL) {
|
|
175
191
|
try {
|
|
176
192
|
return readFileSync(htmlUrl, "utf8");
|
|
177
193
|
} catch (error) {
|
|
178
194
|
const message = error instanceof Error ? error.message : String(error);
|
|
179
|
-
throw new Error(`Missing packaged
|
|
195
|
+
throw new Error(`Missing packaged media upload app HTML at ${htmlUrl.pathname}. Rebuild @productmaker/mcp so ${MEDIA_UPLOAD_APP_RESOURCE_URI} is bundled. Original error: ${message}`);
|
|
180
196
|
}
|
|
181
197
|
}
|
|
182
|
-
__name(
|
|
183
|
-
function
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
__name(readMediaUploadAppHtml, "readMediaUploadAppHtml");
|
|
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);
|
|
207
|
+
registerAppResource(server, MEDIA_UPLOAD_APP_NAME, MEDIA_UPLOAD_APP_RESOURCE_URI, {
|
|
208
|
+
title: "Upload media",
|
|
209
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
210
|
+
_meta: {
|
|
211
|
+
ui: {
|
|
212
|
+
csp
|
|
196
213
|
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
214
|
+
}
|
|
215
|
+
}, async () => ({
|
|
216
|
+
contents: [
|
|
217
|
+
{
|
|
218
|
+
uri: MEDIA_UPLOAD_APP_RESOURCE_URI,
|
|
219
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
220
|
+
text: html,
|
|
221
|
+
_meta: {
|
|
222
|
+
ui: {
|
|
223
|
+
csp
|
|
207
224
|
}
|
|
208
225
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}));
|
|
212
229
|
}
|
|
213
|
-
__name(
|
|
230
|
+
__name(registerMediaUploadAppResource, "registerMediaUploadAppResource");
|
|
214
231
|
|
|
215
232
|
// src/tools/tasks.ts
|
|
216
233
|
import { z as z47 } from "zod";
|
|
@@ -4908,46 +4925,47 @@ var DEFAULT_PRICING = {
|
|
|
4908
4925
|
},
|
|
4909
4926
|
// Grok Imagine I2V pricing by duration/resolution (grok-imagine/image-to-video).
|
|
4910
4927
|
// Provider unified ALL grok-imagine-1.5 renders (silent multi-clip AND native-audio
|
|
4911
|
-
// single-call) onto ONE per-second rate on 2026-06-22
|
|
4912
|
-
//
|
|
4928
|
+
// single-call) onto ONE per-second rate on 2026-06-22 (then $0.008/s @ 480p,
|
|
4929
|
+
// $0.015/s @ 720p). Provider RAISED that unified rate on 2026-08-06 to
|
|
4930
|
+
// $0.012/s @ 480p and $0.0225/s @ 720p (+50%) — the rates below.
|
|
4913
4931
|
// Flat duration keys below = perSecondRate × seconds; userPrice = GROK_USER_MARKUP × cost.
|
|
4914
4932
|
//
|
|
4915
|
-
//
|
|
4916
|
-
//
|
|
4933
|
+
// Grok bills at GROK_USER_MARKUP, which is the 2× house rate again since
|
|
4934
|
+
// 2026-08-06 (the 2.5× exception was reverted — see that constant above).
|
|
4917
4935
|
"kie:video-generation:grok-6s-480p": {
|
|
4918
|
-
ourCostCents:
|
|
4919
|
-
userPriceCents:
|
|
4936
|
+
ourCostCents: 7.2,
|
|
4937
|
+
userPriceCents: 14.4
|
|
4920
4938
|
},
|
|
4921
4939
|
"kie:video-generation:grok-6s-720p": {
|
|
4922
|
-
ourCostCents:
|
|
4923
|
-
userPriceCents:
|
|
4940
|
+
ourCostCents: 13.5,
|
|
4941
|
+
userPriceCents: 27
|
|
4924
4942
|
},
|
|
4925
4943
|
"kie:video-generation:grok-10s-480p": {
|
|
4926
|
-
ourCostCents:
|
|
4927
|
-
userPriceCents:
|
|
4944
|
+
ourCostCents: 12,
|
|
4945
|
+
userPriceCents: 24
|
|
4928
4946
|
},
|
|
4929
4947
|
"kie:video-generation:grok-10s-720p": {
|
|
4930
|
-
ourCostCents:
|
|
4931
|
-
userPriceCents:
|
|
4948
|
+
ourCostCents: 22.5,
|
|
4949
|
+
userPriceCents: 45
|
|
4932
4950
|
},
|
|
4933
4951
|
"kie:video-generation:grok-15s-480p": {
|
|
4934
|
-
ourCostCents:
|
|
4935
|
-
userPriceCents:
|
|
4952
|
+
ourCostCents: 18,
|
|
4953
|
+
userPriceCents: 36
|
|
4936
4954
|
},
|
|
4937
4955
|
"kie:video-generation:grok-15s-720p": {
|
|
4938
|
-
ourCostCents:
|
|
4939
|
-
userPriceCents:
|
|
4956
|
+
ourCostCents: 33.75,
|
|
4957
|
+
userPriceCents: 67.5
|
|
4940
4958
|
},
|
|
4941
4959
|
// Grok Imagine Video 1.5 PER-SECOND (rapid/standard multi-clip path; SAME unified
|
|
4942
|
-
// $/s as the flat keys above: $0.
|
|
4960
|
+
// $/s as the flat keys above: $0.012/s 480p, $0.0225/s 720p). Billed units = perClipSeconds.
|
|
4943
4961
|
// userPrice must stay flat_key ÷ 15 so a 15s render costs the same on either path.
|
|
4944
4962
|
"kie:video-generation:grok15-persec-480p": {
|
|
4945
|
-
ourCostCents:
|
|
4946
|
-
userPriceCents: 2
|
|
4963
|
+
ourCostCents: 1.2,
|
|
4964
|
+
userPriceCents: 2.4
|
|
4947
4965
|
},
|
|
4948
4966
|
"kie:video-generation:grok15-persec-720p": {
|
|
4949
|
-
ourCostCents:
|
|
4950
|
-
userPriceCents:
|
|
4967
|
+
ourCostCents: 2.25,
|
|
4968
|
+
userPriceCents: 4.5
|
|
4951
4969
|
},
|
|
4952
4970
|
// Free-video subtitle burn: one final Whisper transcription per rendered second.
|
|
4953
4971
|
// The worker uses this exact action with units=Math.ceil(finalAudioDuration).
|
|
@@ -4998,11 +5016,11 @@ var DEFAULT_PRICING = {
|
|
|
4998
5016
|
// rate (same as kie: key above); ourCost = real Fal cost (we absorb the premium on rare failover).
|
|
4999
5017
|
"fal:video-generation:grok-15s-480p": {
|
|
5000
5018
|
ourCostCents: 121,
|
|
5001
|
-
userPriceCents:
|
|
5019
|
+
userPriceCents: 36
|
|
5002
5020
|
},
|
|
5003
5021
|
"fal:video-generation:grok-15s-720p": {
|
|
5004
5022
|
ourCostCents: 211,
|
|
5005
|
-
userPriceCents:
|
|
5023
|
+
userPriceCents: 67.5
|
|
5006
5024
|
},
|
|
5007
5025
|
// Fal Grok Imagine 1.5 PER-SECOND failover pricing (rapid/STANDARD multi-clip path —
|
|
5008
5026
|
// mirrors kie:video-generation:grok15-persec-* above, SAME $0.08/s (480p) / $0.14/s
|
|
@@ -5012,11 +5030,11 @@ var DEFAULT_PRICING = {
|
|
|
5012
5030
|
// .superpowers/sdd/fal-grok-rapid-brief.md.
|
|
5013
5031
|
"fal:video-generation:grok15-persec-480p": {
|
|
5014
5032
|
ourCostCents: 8,
|
|
5015
|
-
userPriceCents: 2
|
|
5033
|
+
userPriceCents: 2.4
|
|
5016
5034
|
},
|
|
5017
5035
|
"fal:video-generation:grok15-persec-720p": {
|
|
5018
5036
|
ourCostCents: 14,
|
|
5019
|
-
userPriceCents:
|
|
5037
|
+
userPriceCents: 4.5
|
|
5020
5038
|
},
|
|
5021
5039
|
"kie:video-generation:wan": {
|
|
5022
5040
|
ourCostCents: 30,
|
|
@@ -5883,18 +5901,18 @@ var DEFAULT_PRICING = {
|
|
|
5883
5901
|
},
|
|
5884
5902
|
// ─── Video Regenerate pricing keys ────────────────────────────────────────
|
|
5885
5903
|
// Billed by priceVideoRegenerateSection() below; keys kept for audit trail.
|
|
5886
|
-
// multi_shot: one grok i2v clip per scene (~3s @ 480p:
|
|
5887
|
-
// single_call: one 15s native-audio grok-1.5 whole-video render (
|
|
5904
|
+
// multi_shot: one grok i2v clip per scene (~3s @ 480p: 1.2¢/s × 3s = 3.6¢ our / 7.2¢ user)
|
|
5905
|
+
// single_call: one 15s native-audio grok-1.5 whole-video render (18¢ our / 36¢ user)
|
|
5888
5906
|
// Both are grok renders, so both carry GROK_USER_MARKUP — and both match what the
|
|
5889
|
-
// initial-render keys charge for the identical clip (grok15-persec-480p × 3s =
|
|
5890
|
-
// grok-15s-480p =
|
|
5907
|
+
// initial-render keys charge for the identical clip (grok15-persec-480p × 3s = 7.2¢;
|
|
5908
|
+
// grok-15s-480p = 36¢).
|
|
5891
5909
|
"kie:video-regen:multi_shot-per-scene": {
|
|
5892
|
-
ourCostCents:
|
|
5893
|
-
userPriceCents:
|
|
5910
|
+
ourCostCents: 3.6,
|
|
5911
|
+
userPriceCents: 7.2
|
|
5894
5912
|
},
|
|
5895
5913
|
"kie:video-regen:single_call": {
|
|
5896
|
-
ourCostCents:
|
|
5897
|
-
userPriceCents:
|
|
5914
|
+
ourCostCents: 18,
|
|
5915
|
+
userPriceCents: 36
|
|
5898
5916
|
},
|
|
5899
5917
|
// ─── Competitor Video Transcripts (OpenAI Whisper) ────────────────────────
|
|
5900
5918
|
// Unit = one successfully transcribed video ad opening (~8s of audio).
|
|
@@ -8068,11 +8086,19 @@ var ServiceMediaAnalysisSchema = z43.object({
|
|
|
8068
8086
|
});
|
|
8069
8087
|
|
|
8070
8088
|
// ../../packages/types/src/productmaker-capability-registry.ts
|
|
8071
|
-
var
|
|
8089
|
+
var SHARED_CHANNEL_EXPOSURE = {
|
|
8072
8090
|
mcp: "direct",
|
|
8073
8091
|
whatsapp: "adapted"
|
|
8074
8092
|
};
|
|
8075
|
-
|
|
8093
|
+
var MCP_ONLY_CHANNEL_EXPOSURE = {
|
|
8094
|
+
mcp: "direct",
|
|
8095
|
+
whatsapp: "unavailable"
|
|
8096
|
+
};
|
|
8097
|
+
var WHATSAPP_ONLY_CHANNEL_EXPOSURE = {
|
|
8098
|
+
mcp: "unavailable",
|
|
8099
|
+
whatsapp: "adapted"
|
|
8100
|
+
};
|
|
8101
|
+
function defineCapability(name, schemaHash, confirmation, routingDomain, effects, inputSlots, consumes, produces, channelExposure = SHARED_CHANNEL_EXPOSURE) {
|
|
8076
8102
|
return {
|
|
8077
8103
|
name,
|
|
8078
8104
|
schemaHash,
|
|
@@ -8084,7 +8110,7 @@ function defineCapability(name, schemaHash, confirmation, routingDomain, effects
|
|
|
8084
8110
|
consumes,
|
|
8085
8111
|
produces
|
|
8086
8112
|
},
|
|
8087
|
-
channelExposure
|
|
8113
|
+
channelExposure
|
|
8088
8114
|
};
|
|
8089
8115
|
}
|
|
8090
8116
|
__name(defineCapability, "defineCapability");
|
|
@@ -8168,7 +8194,18 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
8168
8194
|
"meta_upload_session"
|
|
8169
8195
|
], [
|
|
8170
8196
|
"staging_asset"
|
|
8171
|
-
]),
|
|
8197
|
+
], WHATSAPP_ONLY_CHANNEL_EXPOSURE),
|
|
8198
|
+
defineCapability("check_media_upload", "daf202e0ff3a7a14425d164a7be6a81922b12233e85dbb898894e7c3ba6c5876", "none", "read_only", [
|
|
8199
|
+
"read"
|
|
8200
|
+
], [
|
|
8201
|
+
slot("upload_session", true, [
|
|
8202
|
+
"sessionId"
|
|
8203
|
+
], [])
|
|
8204
|
+
], [
|
|
8205
|
+
"media_upload_session"
|
|
8206
|
+
], [
|
|
8207
|
+
"uploaded_asset"
|
|
8208
|
+
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
8172
8209
|
defineCapability("cost_dropshipping_project", "746797b5400b170ba3a0704d432afff635794f0a7ae0b9da07b26d49e3b82242", "state_change", "business", [
|
|
8173
8210
|
"state_change"
|
|
8174
8211
|
], [
|
|
@@ -8449,7 +8486,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
8449
8486
|
], [
|
|
8450
8487
|
"product_research_run"
|
|
8451
8488
|
]),
|
|
8452
|
-
defineCapability("generate_free_video", "
|
|
8489
|
+
defineCapability("generate_free_video", "01f5e412c4ae3a8e1d97ded33f5cf38c4505f02fcf301dad90c37655c8de783f", "credit_consuming", "creative", [
|
|
8453
8490
|
"credit_consuming",
|
|
8454
8491
|
"state_change"
|
|
8455
8492
|
], [
|
|
@@ -8790,7 +8827,7 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
8790
8827
|
], [
|
|
8791
8828
|
"tiktok_advertiser"
|
|
8792
8829
|
]),
|
|
8793
|
-
defineCapability("publish_creatives_to_meta", "
|
|
8830
|
+
defineCapability("publish_creatives_to_meta", "b3a487134a1f5392d7a526348a03e952a317ae5d9ff01bae10bbe24ec5fe3fc8", "external_mutation", "publishing", [
|
|
8794
8831
|
"external_mutation",
|
|
8795
8832
|
"state_change"
|
|
8796
8833
|
], [
|
|
@@ -8959,7 +8996,30 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
8959
8996
|
"media_upload_request"
|
|
8960
8997
|
], [
|
|
8961
8998
|
"meta_upload_session"
|
|
8962
|
-
]),
|
|
8999
|
+
], WHATSAPP_ONLY_CHANNEL_EXPOSURE),
|
|
9000
|
+
defineCapability("request_media_upload", "d7ccfffe00f8563219fb3e036b07649ff59334e03fc52f6a2f3cb0823d4aa948", "none", "creative", [
|
|
9001
|
+
"state_change"
|
|
9002
|
+
], [
|
|
9003
|
+
slot("media_kind", true, [
|
|
9004
|
+
"mediaKind"
|
|
9005
|
+
], []),
|
|
9006
|
+
slot("asset_role", false, [
|
|
9007
|
+
"assetRole"
|
|
9008
|
+
], []),
|
|
9009
|
+
slot("upload_purpose", false, [
|
|
9010
|
+
"uploadPurpose"
|
|
9011
|
+
], []),
|
|
9012
|
+
slot("file_limit", false, [
|
|
9013
|
+
"maxFiles"
|
|
9014
|
+
], []),
|
|
9015
|
+
slot("host_files", false, [
|
|
9016
|
+
"files"
|
|
9017
|
+
], [])
|
|
9018
|
+
], [
|
|
9019
|
+
"media_upload_request"
|
|
9020
|
+
], [
|
|
9021
|
+
"media_upload_session"
|
|
9022
|
+
], MCP_ONLY_CHANNEL_EXPOSURE),
|
|
8963
9023
|
defineCapability("resolve_pending_action", "2fae350d14ba3fdab63914ca3b442472a002d97cffc85b6f207b1cc7e320ecdb", "credit_consuming", "creative", [
|
|
8964
9024
|
"credit_consuming",
|
|
8965
9025
|
"state_change"
|
|
@@ -9017,6 +9077,9 @@ var PRODUCTMAKER_CAPABILITY_REGISTRY = deepFreeze([
|
|
|
9017
9077
|
])
|
|
9018
9078
|
]);
|
|
9019
9079
|
var PRODUCTMAKER_CAPABILITY_NAMES = PRODUCTMAKER_CAPABILITY_REGISTRY.map((capability) => capability.name);
|
|
9080
|
+
var PRODUCTMAKER_MCP_CAPABILITY_REGISTRY = PRODUCTMAKER_CAPABILITY_REGISTRY.filter((capability) => capability.channelExposure.mcp === "direct");
|
|
9081
|
+
var PRODUCTMAKER_WHATSAPP_CAPABILITY_REGISTRY = PRODUCTMAKER_CAPABILITY_REGISTRY.filter((capability) => capability.channelExposure.whatsapp === "adapted");
|
|
9082
|
+
var PRODUCTMAKER_WHATSAPP_CAPABILITY_NAMES = PRODUCTMAKER_WHATSAPP_CAPABILITY_REGISTRY.map((capability) => capability.name);
|
|
9020
9083
|
|
|
9021
9084
|
// ../../packages/types/src/productmaker-chat-capabilities.ts
|
|
9022
9085
|
function toLegacyCapabilityCatalog(catalog) {
|
|
@@ -9027,7 +9090,8 @@ function toLegacyCapabilityCatalog(catalog) {
|
|
|
9027
9090
|
}));
|
|
9028
9091
|
}
|
|
9029
9092
|
__name(toLegacyCapabilityCatalog, "toLegacyCapabilityCatalog");
|
|
9030
|
-
var PRODUCTMAKER_MCP_CAPABILITIES = toLegacyCapabilityCatalog(
|
|
9093
|
+
var PRODUCTMAKER_MCP_CAPABILITIES = toLegacyCapabilityCatalog(PRODUCTMAKER_MCP_CAPABILITY_REGISTRY);
|
|
9094
|
+
var PRODUCTMAKER_MCP_CAPABILITY_NAMES = PRODUCTMAKER_MCP_CAPABILITY_REGISTRY.map((capability) => capability.name);
|
|
9031
9095
|
|
|
9032
9096
|
// ../../packages/types/src/actor.ts
|
|
9033
9097
|
import { z as z44 } from "zod";
|
|
@@ -11097,7 +11161,7 @@ var FREE_VIDEO_DURATIONS_BY_QUALITY = {
|
|
|
11097
11161
|
]
|
|
11098
11162
|
};
|
|
11099
11163
|
var FreeVideoInput = z48.object({
|
|
11100
|
-
images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Om\xEDtelo para generar desde texto puro."),
|
|
11164
|
+
images: ImagesInput.optional().describe("Opcional. 1-5 fotos del producto. Si el usuario adjunt\xF3 o mencion\xF3 fotos locales y no existe un assetId reutilizable, llama primero request_media_upload con mediaKind=image y usa los assetId devueltos; no pidas rutas ni URLs. Om\xEDtelo para generar desde texto puro."),
|
|
11101
11165
|
rawPrompt: z48.string().trim().min(1).max(2e3).describe("Prompt o guion exacto del video. Si activas subt\xEDtulos, este mismo texto se usa como guion de subt\xEDtulos."),
|
|
11102
11166
|
quality: FreeVideoQualityEnum.describe('Calidad del video: "economical" = Econ\xF3mico o "high_detail" = Alto detalle.'),
|
|
11103
11167
|
durationSeconds: z48.number().int().min(1).max(15).describe("Duraci\xF3n exacta. Econ\xF3mico permite 6, 8, 10 o 15s; Alto detalle permite 4, 8, 12 o 15s."),
|
|
@@ -11169,7 +11233,7 @@ var LandingInput = z48.object({
|
|
|
11169
11233
|
subheadline: CreativeRefs.subheadline
|
|
11170
11234
|
});
|
|
11171
11235
|
function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
11172
|
-
server.tool("generate_free_video", 'Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economical" = Econ\xF3mico y "high_detail" = Alto detalle. La duraci\xF3n debe pertenecer a la lista admitida por la calidad escogida; si el usuario pide otra, preg\xFAntale una duraci\xF3n v\xE1lida.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.', FreeVideoInput.shape, async (input) => {
|
|
11236
|
+
server.tool("generate_free_video", 'Genera UN video libre con prompt/guion exacto, opcionalmente con imagen de referencia. Si el usuario adjunta o menciona una imagen local, llama primero request_media_upload con mediaKind=image y usa el assetId devuelto; no pidas path ni URL. Devuelve un taskId y procesa el job de forma as\xEDncrona.\n\nREQUERIDOS: rawPrompt, quality, durationSeconds y videoResolution.\n\nCALIDAD: "economical" = Econ\xF3mico y "high_detail" = Alto detalle. La duraci\xF3n debe pertenecer a la lista admitida por la calidad escogida; si el usuario pide otra, preg\xFAntale una duraci\xF3n v\xE1lida.\n\nSUBT\xCDTULOS: si addSubtitles=true, el rawPrompt completo se usa como texto exacto de subt\xEDtulos y debe tener m\xE1ximo 500 caracteres; no existe una pregunta adicional de transcripci\xF3n.\n\nPara videos normales con narrativa/\xE1ngulo usa generate_video_creative. Para tareas combinadas de producto usa create_product_task.', FreeVideoInput.shape, async (input) => {
|
|
11173
11237
|
try {
|
|
11174
11238
|
const allowedDurations = FREE_VIDEO_DURATIONS_BY_QUALITY[input.quality];
|
|
11175
11239
|
if (!allowedDurations.includes(input.durationSeconds)) {
|
|
@@ -11199,7 +11263,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
11199
11263
|
throw translateError(e);
|
|
11200
11264
|
}
|
|
11201
11265
|
});
|
|
11202
|
-
server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nCOMPETENCIA Y GAP REPLICATION: con videoPreset="auto" (default) y competitorScanEnabled=true (default), el sistema analiza anuncios de competencia. Si encuentra un winner de video coherente, ancla el primaryAngle y genera autom\xE1ticamente la narrativa `gap_replication` para copiar su estructura audiovisual. No la selecciones manualmente: es un resultado del an\xE1lisis. Si competitorScanEnabled=false, esta b\xFAsqueda se omite.\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, async (input) => {
|
|
11266
|
+
server.tool("generate_video_creative", 'Genera UN video creativo independiente. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), title, primaryAngle, language, country, aspectRatio. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price y offer si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para price/offer, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults sensatos): durationSeconds (15 o 30), videoPreset (tipo/narrativa del video), features, cta, scriptNotes (dirige el guion Y el ambiente/escenario/est\xE9tica del video, ej. "en una playa, opulento").\n\nCOMPETENCIA Y GAP REPLICATION: con videoPreset="auto" (default) y competitorScanEnabled=true (default), el sistema analiza anuncios de competencia. Si encuentra un winner de video coherente, ancla el primaryAngle y genera autom\xE1ticamente la narrativa `gap_replication` para copiar su estructura audiovisual. No la selecciones manualmente: es un resultado del an\xE1lisis. Si competitorScanEnabled=false, esta b\xFAsqueda se omite.\n\nESTILO: 15s usa "Realista" (single_call); 30s usa "Multiescena" (multi-toma, aproximadamente 30s). Multiescena se factura por escena, as\xED que avisa el costo antes de activarlo.\n\nGUION: el usuario NO escribe el guion desde cero \u2014 la IA lo redacta. Si quiere REVISARLO/EDITARLO antes de renderizar (igual que en la web), pasa `pauseForVideoScriptReview: true`; cuando la tarea quede pausada, usa get_video_script para leer las escenas, ofr\xE9celas al usuario, y llama approve_video_script con las ediciones. Para dirigir el guion ANTES de generar (sin pausar), usa scriptNotes. Para revisar el \xE1ngulo y decidir duraci\xF3n tras el an\xE1lisis de competencia, pasa `pauseForAngleReview: true` y luego resuelve `angle_review_approval` con `gapDurationSeconds`.', VideoInput.shape, async (input) => {
|
|
11203
11267
|
try {
|
|
11204
11268
|
const resolved = resolveVideoPreset(input.videoPreset);
|
|
11205
11269
|
const wireTier = resolveWireVideoTier(input.videoTier, input.durationSeconds);
|
|
@@ -11224,7 +11288,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
11224
11288
|
throw translateError(e);
|
|
11225
11289
|
}
|
|
11226
11290
|
});
|
|
11227
|
-
server.tool("generate_image_creatives", 'Genera N variantes de imagen creativa (ads est\xE1ticos). Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), productTitle, primaryAngle, language, country.\n\nANTES de invocar, PREG\xDANTALE al usuario:\n- price y offer (se renderizan en la imagen \u2014 nunca los asumas; si dice "t\xFA elige", om\xEDtelos).\n- compareAtPrice si hay descuento (ej. "$129.000" para tachar).\n- creativeArchetype y visualTreatment si quiere un look espec\xEDfico (performance vs premium DTC). Si no le importa, om\xEDtelos.\n\nNO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente.\n\nOTROS OPCIONALES: buyerPersona, nVariants (default 3), imageModel, imageStyle, styleMix, aspectRatio (default 4:5), generateAdCopy, pauseForAngleReview, features, brandColors, isCombo, creativeDirection.', ImageInput.shape, async (input) => {
|
|
11291
|
+
server.tool("generate_image_creatives", 'Genera N variantes de imagen creativa (ads est\xE1ticos). Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), productTitle, primaryAngle, language, country. Si el usuario adjunta o menciona fotos locales y a\xFAn no hay assetId, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario:\n- price y offer (se renderizan en la imagen \u2014 nunca los asumas; si dice "t\xFA elige", om\xEDtelos).\n- compareAtPrice si hay descuento (ej. "$129.000" para tachar).\n- creativeArchetype y visualTreatment si quiere un look espec\xEDfico (performance vs premium DTC). Si no le importa, om\xEDtelos.\n\nNO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente.\n\nOTROS OPCIONALES: buyerPersona, nVariants (default 3), imageModel, imageStyle, styleMix, aspectRatio (default 4:5), generateAdCopy, pauseForAngleReview, features, brandColors, isCombo, creativeDirection.', ImageInput.shape, async (input) => {
|
|
11228
11292
|
try {
|
|
11229
11293
|
const { projectId, ...rest } = input;
|
|
11230
11294
|
return await submitCreative(api, {
|
|
@@ -11253,7 +11317,7 @@ function registerCreativeTools(server, api, { transport = "http" } = {}) {
|
|
|
11253
11317
|
throw translateError(e);
|
|
11254
11318
|
}
|
|
11255
11319
|
});
|
|
11256
|
-
server.tool("generate_landing", 'Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection, landingStrategy.', LandingInput.shape, async (input) => {
|
|
11320
|
+
server.tool("generate_landing", 'Genera una landing page cl\xE1sica basada en im\xE1genes, lista para Shopify. Devuelve un taskId.\n\nREQUERIDOS: images (array de 1-5 fotos), language, country. Si el usuario dice algo natural como \u201Ccrea una landing con este producto\u201D y adjunta o menciona fotos locales, llama primero `request_media_upload` con mediaKind=image y despu\xE9s usa los assetId en images; no pidas path ni URL.\n\nANTES de invocar, PREG\xDANTALE al usuario por price, offer y paymentMethods si no los mencion\xF3 \u2014 son los inputs de mayor impacto en conversi\xF3n y nunca debes asumirlos. Si hay descuento, pregunta tambi\xE9n compareAtPrice. NO defaultes country/language desde el contexto: preg\xFAntale expl\xEDcitamente. Si el usuario te dice "t\xFA elige" para esos campos, om\xEDtelos.\n\nOTROS OPCIONALES (puedes invocar sin ellos y dejar que la IA elija defaults): salesAngle, buyerPersona, isCombo, creativeDirection, landingStrategy.', LandingInput.shape, async (input) => {
|
|
11257
11321
|
try {
|
|
11258
11322
|
const { projectId, ...rest } = input;
|
|
11259
11323
|
return await submitCreative(api, {
|
|
@@ -11809,12 +11873,6 @@ var GroupedMediaSource = z53.object({
|
|
|
11809
11873
|
}).refine((value) => !(value.thumbnailUrl && value.thumbnailPath), {
|
|
11810
11874
|
message: "media source needs at most one of `thumbnailUrl` or `thumbnailPath`"
|
|
11811
11875
|
});
|
|
11812
|
-
var GroupedStagedMediaSource = z53.object({
|
|
11813
|
-
type: z53.literal("staged"),
|
|
11814
|
-
/** Legacy source returned by check_meta_media_upload. */
|
|
11815
|
-
stagingAssetId: z53.string().uuid(),
|
|
11816
|
-
thumbnailAssetId: z53.string().uuid().optional()
|
|
11817
|
-
});
|
|
11818
11876
|
var GroupedUploadedAssetSource = z53.object({
|
|
11819
11877
|
type: z53.literal("asset"),
|
|
11820
11878
|
/** Returned by check_media_upload. ProductMaker resolves ownership server-side. */
|
|
@@ -11829,7 +11887,6 @@ var GroupedCreative = z53.object({
|
|
|
11829
11887
|
source: z53.union([
|
|
11830
11888
|
GroupedTaskSource,
|
|
11831
11889
|
GroupedMediaSource,
|
|
11832
|
-
GroupedStagedMediaSource,
|
|
11833
11890
|
GroupedUploadedAssetSource
|
|
11834
11891
|
]),
|
|
11835
11892
|
title: z53.string().max(120).optional(),
|
|
@@ -12061,7 +12118,7 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
12061
12118
|
throw translateError(e);
|
|
12062
12119
|
}
|
|
12063
12120
|
});
|
|
12064
|
-
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos.
|
|
12121
|
+
server.tool("publish_creatives_to_meta", "Crea UNA campa\xF1a de Meta Ads PAUSED agrupando hasta " + CAMPAIGN_LIMITS.MAX_CREATIVES + ' creativos. Si la petici\xF3n natural adjunta o menciona archivos locales, llama primero `request_media_upload`; tras OK usa cada resultado como `source.type="asset"` con su `assetId` y el `type` que coincide con `mediaKind`. No pidas path ni URL. Despu\xE9s llama `list_meta_destinations` y pregunta en el chat por el `portfolioId`, `adAccountId` y `pageId` exactos; el App es solo para archivos. Si la carga fue parcial, publica s\xF3lo los assets confirmados; nunca inventes assets ni superes diez creativos. Las rutas locales s\xF3lo funcionan con MCP stdio. Los medios propios se eliminan despu\xE9s de publicar correctamente; si falla, quedan durante el TTL para reintento. Para videos la miniatura es opcional y ProductMaker puede generarla con FFmpeg. `campaignConfig.destination` acepta landing o WhatsApp. Para landing basta `campaignConfig.destination.landingUrl`; no dupliques la URL arriba. Si omites `budgetMode`, se infiere `ad_set` cuando cada ad set trae `dailyBudget`, o `campaign` cuando env\xEDas `campaignDailyBudget`. No actives la campa\xF1a autom\xE1ticamente.', PublishCreativesToMetaInputBase.shape, async (rawInput) => {
|
|
12065
12122
|
try {
|
|
12066
12123
|
const parsed = PublishCreativesToMetaInput.safeParse(rawInput);
|
|
12067
12124
|
if (!parsed.success) throw invalidInputFromZod(parsed.error);
|
|
@@ -12087,23 +12144,6 @@ function registerPublishTools(server, api, opts = {}) {
|
|
|
12087
12144
|
});
|
|
12088
12145
|
continue;
|
|
12089
12146
|
}
|
|
12090
|
-
if (creative.source.type === "staged") {
|
|
12091
|
-
creatives.push({
|
|
12092
|
-
type: creative.type,
|
|
12093
|
-
source: {
|
|
12094
|
-
type: "media",
|
|
12095
|
-
mediaAssetId: creative.source.stagingAssetId,
|
|
12096
|
-
...creative.source.thumbnailAssetId ? {
|
|
12097
|
-
thumbnailAssetId: creative.source.thumbnailAssetId
|
|
12098
|
-
} : {}
|
|
12099
|
-
},
|
|
12100
|
-
title: creative.title,
|
|
12101
|
-
description: creative.description,
|
|
12102
|
-
cta: creative.cta,
|
|
12103
|
-
creativeId
|
|
12104
|
-
});
|
|
12105
|
-
continue;
|
|
12106
|
-
}
|
|
12107
12147
|
if (creative.source.type === "asset") {
|
|
12108
12148
|
creatives.push({
|
|
12109
12149
|
type: creative.type,
|
|
@@ -12256,16 +12296,21 @@ __name(toTikTokCampaignConfig, "toTikTokCampaignConfig");
|
|
|
12256
12296
|
// src/tools/meta-upload.ts
|
|
12257
12297
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
12258
12298
|
import { z as z54 } from "zod";
|
|
12259
|
-
var
|
|
12299
|
+
var UploadSessionInputBase = z54.object({
|
|
12260
12300
|
mediaKind: z54.enum([
|
|
12261
12301
|
"image",
|
|
12262
12302
|
"video",
|
|
12263
12303
|
"mixed"
|
|
12264
|
-
]).default("mixed").describe("The kind of file the user will select
|
|
12304
|
+
]).default("mixed").describe("The kind of file the user will select. Use image for product references and landing pages, video for a single video, and mixed only when the consumer supports both."),
|
|
12265
12305
|
assetRole: z54.enum([
|
|
12266
12306
|
"media",
|
|
12267
12307
|
"thumbnail"
|
|
12268
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."),
|
|
12269
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.")
|
|
12270
12315
|
});
|
|
12271
12316
|
var OpenAiFileReference = z54.object({
|
|
@@ -12274,18 +12319,18 @@ var OpenAiFileReference = z54.object({
|
|
|
12274
12319
|
mime_type: z54.string().min(1).optional(),
|
|
12275
12320
|
file_name: z54.string().min(1).optional()
|
|
12276
12321
|
}).strict();
|
|
12277
|
-
var
|
|
12278
|
-
...
|
|
12322
|
+
var UploadSessionToolInputShape = {
|
|
12323
|
+
...UploadSessionInputBase.shape,
|
|
12279
12324
|
files: z54.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.")
|
|
12280
12325
|
};
|
|
12281
|
-
var
|
|
12326
|
+
var UploadSessionToolInput = z54.object(UploadSessionToolInputShape).superRefine((value, ctx) => {
|
|
12282
12327
|
if (value.assetRole === "thumbnail" && value.mediaKind !== "image") {
|
|
12283
12328
|
ctx.addIssue({
|
|
12284
12329
|
code: z54.ZodIssueCode.custom,
|
|
12285
12330
|
path: [
|
|
12286
12331
|
"mediaKind"
|
|
12287
12332
|
],
|
|
12288
|
-
message: "
|
|
12333
|
+
message: "Thumbnails must be images"
|
|
12289
12334
|
});
|
|
12290
12335
|
}
|
|
12291
12336
|
if (value.mediaKind === "video" && value.maxFiles !== void 0 && value.maxFiles !== 1) {
|
|
@@ -12307,7 +12352,7 @@ var MetaUploadSessionToolInput = z54.object(MetaUploadSessionToolInputShape).sup
|
|
|
12307
12352
|
});
|
|
12308
12353
|
}
|
|
12309
12354
|
});
|
|
12310
|
-
var
|
|
12355
|
+
var UploadSessionResponse = z54.object({
|
|
12311
12356
|
sessionId: z54.string().uuid(),
|
|
12312
12357
|
uploadPageUrl: z54.string().url(),
|
|
12313
12358
|
mediaKind: z54.enum([
|
|
@@ -12322,23 +12367,8 @@ var MetaUploadSessionResponse2 = z54.object({
|
|
|
12322
12367
|
maxFiles: z54.number().int().positive(),
|
|
12323
12368
|
expiresAt: z54.string().datetime()
|
|
12324
12369
|
});
|
|
12325
|
-
var
|
|
12326
|
-
sessionId: z54.string().uuid().describe("The sessionId returned by
|
|
12327
|
-
});
|
|
12328
|
-
var MetaUploadStatusResponse = z54.object({
|
|
12329
|
-
sessionId: z54.string().uuid(),
|
|
12330
|
-
status: z54.enum([
|
|
12331
|
-
"pending",
|
|
12332
|
-
"uploaded"
|
|
12333
|
-
]),
|
|
12334
|
-
assets: z54.array(z54.object({
|
|
12335
|
-
stagingAssetId: z54.string().uuid(),
|
|
12336
|
-
mediaKind: z54.enum([
|
|
12337
|
-
"image",
|
|
12338
|
-
"video"
|
|
12339
|
-
])
|
|
12340
|
-
})).max(10).default([]),
|
|
12341
|
-
stagingAssetId: z54.string().uuid().optional()
|
|
12370
|
+
var UploadStatusInput = z54.object({
|
|
12371
|
+
sessionId: z54.string().uuid().describe("The sessionId returned by request_media_upload.")
|
|
12342
12372
|
});
|
|
12343
12373
|
var UniversalUploadStatusResponse = z54.discriminatedUnion("status", [
|
|
12344
12374
|
z54.object({
|
|
@@ -12360,10 +12390,21 @@ var UniversalUploadStatusResponse = z54.discriminatedUnion("status", [
|
|
|
12360
12390
|
})).min(1).max(10)
|
|
12361
12391
|
})
|
|
12362
12392
|
]);
|
|
12363
|
-
var UniversalUploadStructuredResponse =
|
|
12393
|
+
var UniversalUploadStructuredResponse = UploadSessionResponse.extend({
|
|
12394
|
+
uploadPurpose: z54.enum([
|
|
12395
|
+
"product_reference",
|
|
12396
|
+
"creative",
|
|
12397
|
+
"generic"
|
|
12398
|
+
]),
|
|
12364
12399
|
checkToolName: z54.literal("check_media_upload"),
|
|
12365
|
-
resolveEndpoint: z54.
|
|
12400
|
+
resolveEndpoint: z54.string().url()
|
|
12366
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");
|
|
12367
12408
|
function uploadSessionText(result) {
|
|
12368
12409
|
const mediaLabel = result.mediaKind === "mixed" ? "archivos" : result.mediaKind === "video" ? "video" : "imagen";
|
|
12369
12410
|
return {
|
|
@@ -12381,11 +12422,15 @@ function uploadSessionText(result) {
|
|
|
12381
12422
|
};
|
|
12382
12423
|
}
|
|
12383
12424
|
__name(uploadSessionText, "uploadSessionText");
|
|
12384
|
-
function
|
|
12425
|
+
function resolveEndpointForSession(api) {
|
|
12426
|
+
return api.publicUrl("/v1/uploaded-assets/sessions/resolve");
|
|
12427
|
+
}
|
|
12428
|
+
__name(resolveEndpointForSession, "resolveEndpointForSession");
|
|
12429
|
+
function registerMediaUploadTools(server, api) {
|
|
12385
12430
|
registerAppTool(server, "request_media_upload", {
|
|
12386
12431
|
title: "Subir archivos",
|
|
12387
|
-
description: "Abre el selector
|
|
12388
|
-
inputSchema:
|
|
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.",
|
|
12433
|
+
inputSchema: UploadSessionToolInputShape,
|
|
12389
12434
|
outputSchema: UniversalUploadStructuredResponse.shape,
|
|
12390
12435
|
_meta: {
|
|
12391
12436
|
ui: {
|
|
@@ -12405,13 +12450,15 @@ function registerMetaUploadTools(server, api) {
|
|
|
12405
12450
|
}
|
|
12406
12451
|
}, async (rawInput) => {
|
|
12407
12452
|
try {
|
|
12408
|
-
const input =
|
|
12409
|
-
const { files: _files, ...sessionInput } = input;
|
|
12410
|
-
const session =
|
|
12453
|
+
const input = UploadSessionToolInput.parse(rawInput);
|
|
12454
|
+
const { files: _files, uploadPurpose, ...sessionInput } = input;
|
|
12455
|
+
const session = UploadSessionResponse.parse(await api.postJson("/v1/uploaded-assets/sessions", sessionInput));
|
|
12411
12456
|
const result = UniversalUploadStructuredResponse.parse({
|
|
12412
12457
|
...session,
|
|
12458
|
+
uploadPageUrl: contextualizeUploadPageUrl(session.uploadPageUrl, uploadPurpose),
|
|
12459
|
+
uploadPurpose,
|
|
12413
12460
|
checkToolName: "check_media_upload",
|
|
12414
|
-
resolveEndpoint:
|
|
12461
|
+
resolveEndpoint: resolveEndpointForSession(api)
|
|
12415
12462
|
});
|
|
12416
12463
|
return {
|
|
12417
12464
|
structuredContent: result,
|
|
@@ -12423,69 +12470,20 @@ function registerMetaUploadTools(server, api) {
|
|
|
12423
12470
|
});
|
|
12424
12471
|
server.registerTool("check_media_upload", {
|
|
12425
12472
|
description: "Comprueba la carga universal. Si status=uploaded, conserva assets[] para la petici\xF3n anterior y pasa cada assetId \xFAnicamente a un consumidor compatible. Un asset de video solo sirve donde se admite video; las referencias de producto, imagen y landing requieren mediaKind=image. No muestres IDs t\xE9cnicos como JSON al usuario.",
|
|
12426
|
-
inputSchema:
|
|
12473
|
+
inputSchema: UploadStatusInput.shape,
|
|
12427
12474
|
_meta: {
|
|
12428
12475
|
"openai/widgetAccessible": true
|
|
12429
12476
|
}
|
|
12430
12477
|
}, async (rawInput) => {
|
|
12431
12478
|
try {
|
|
12432
|
-
const input =
|
|
12479
|
+
const input = UploadStatusInput.parse(rawInput);
|
|
12433
12480
|
return asText(UniversalUploadStatusResponse.parse(await api.getJson(`/v1/uploaded-assets/sessions/${encodeURIComponent(input.sessionId)}`)));
|
|
12434
12481
|
} catch (error) {
|
|
12435
12482
|
throw translateError(error);
|
|
12436
12483
|
}
|
|
12437
12484
|
});
|
|
12438
|
-
registerAppTool(server, "request_meta_media_upload", {
|
|
12439
|
-
title: "Solicitar carga segura de Meta",
|
|
12440
|
-
description: "Inicia una carga segura desde la laptop del usuario para creativos propios de Meta. Esta es la ruta DIRECTA para peticiones puntuales como \u2018sube esta imagen/video a Meta Ads\u2019 o \u2018publica este creativo\u2019; no conviertas esa intenci\xF3n en una generaci\xF3n completa de producto. No llames `guide_dropshipping_launch` para esta operaci\xF3n puntual. Por defecto crea una sesi\xF3n `mixed` de hasta diez archivos; tambi\xE9n admite una sola imagen o video. Si el usuario adjunta una foto o video en el mismo mensaje de ChatGPT, pasa esas referencias opcionales en `files` para que el MCP App inline las consuma dentro del chat. ProductMaker conserva `files` solo como metadata del UI bridge: NO los descarga y NO los reenv\xEDa. No pidas URL CDN, ruta local, bytes ni base64. El App es exclusivamente para cargar archivos: nunca debe pedir ni mostrar portafolio, cuenta publicitaria, p\xE1gina, IDs o JSON. El enlace externo de ProductMaker queda como fallback para hosts sin bridge de archivos. El usuario pulsa OK cuando termina; el App inserta en el chat los asset_id verificados para continuar la petici\xF3n anterior. Luego llama `list_meta_destinations` y pregunta en el chat qu\xE9 `portfolioId`, `adAccountId` y `pageId` desea usar. No muestres tokens, URLs temporales, `sessionId` ni un bloque JSON. Esta herramienta no publica ni consume cr\xE9ditos.",
|
|
12441
|
-
inputSchema: MetaUploadSessionToolInputShape,
|
|
12442
|
-
outputSchema: MetaUploadSessionResponse2.shape,
|
|
12443
|
-
_meta: {
|
|
12444
|
-
ui: {
|
|
12445
|
-
resourceUri: META_UPLOAD_APP_RESOURCE_URI,
|
|
12446
|
-
visibility: [
|
|
12447
|
-
"model",
|
|
12448
|
-
"app"
|
|
12449
|
-
]
|
|
12450
|
-
},
|
|
12451
|
-
"openai/outputTemplate": META_UPLOAD_APP_RESOURCE_URI,
|
|
12452
|
-
"openai/widgetAccessible": true,
|
|
12453
|
-
"openai/fileParams": [
|
|
12454
|
-
"files"
|
|
12455
|
-
],
|
|
12456
|
-
"openai/toolInvocation/invoking": "Preparando carga segura\u2026",
|
|
12457
|
-
"openai/toolInvocation/invoked": "Carga segura lista"
|
|
12458
|
-
}
|
|
12459
|
-
}, async (rawInput) => {
|
|
12460
|
-
try {
|
|
12461
|
-
const input = MetaUploadSessionToolInput.parse(rawInput);
|
|
12462
|
-
const { files: _files, ...sessionInput } = input;
|
|
12463
|
-
const result = MetaUploadSessionResponse2.parse(await api.postJson("/v1/ads/meta-upload-sessions", sessionInput));
|
|
12464
|
-
return {
|
|
12465
|
-
structuredContent: result,
|
|
12466
|
-
...uploadSessionText(result)
|
|
12467
|
-
};
|
|
12468
|
-
} catch (error) {
|
|
12469
|
-
throw translateError(error);
|
|
12470
|
-
}
|
|
12471
|
-
});
|
|
12472
|
-
server.registerTool("check_meta_media_upload", {
|
|
12473
|
-
description: 'Comprueba si el usuario ya termin\xF3 de subir desde el App o enlace de ProductMaker. Si status=uploaded, conserva `assets[]` privadamente, llama `list_meta_destinations` y pregunta en lenguaje natural dentro del chat qu\xE9 portafolio, cuenta publicitaria y p\xE1gina desea usar; nunca muestres `sessionId`, `stagingAssetId`, IDs t\xE9cnicos ni un bloque JSON. Despu\xE9s crea un creativo `source.type="staged"` por cada asset, usando su `mediaKind` como tipo, hasta diez en el batch. Si la carga fue parcial, usa \xFAnicamente los assets confirmados. Conserva el `stagingAssetId` legado si aparece. Si status=pending, pide al usuario que termine; no publiques todav\xEDa.',
|
|
12474
|
-
inputSchema: MetaUploadStatusInput.shape,
|
|
12475
|
-
_meta: {
|
|
12476
|
-
"openai/widgetAccessible": true
|
|
12477
|
-
}
|
|
12478
|
-
}, async (rawInput) => {
|
|
12479
|
-
try {
|
|
12480
|
-
const input = MetaUploadStatusInput.parse(rawInput);
|
|
12481
|
-
const result = MetaUploadStatusResponse.parse(await api.getJson(`/v1/ads/meta-upload-sessions/${encodeURIComponent(input.sessionId)}`));
|
|
12482
|
-
return asText(result);
|
|
12483
|
-
} catch (error) {
|
|
12484
|
-
throw translateError(error);
|
|
12485
|
-
}
|
|
12486
|
-
});
|
|
12487
12485
|
}
|
|
12488
|
-
__name(
|
|
12486
|
+
__name(registerMediaUploadTools, "registerMediaUploadTools");
|
|
12489
12487
|
|
|
12490
12488
|
// src/tools/campaigns.ts
|
|
12491
12489
|
import { z as z55 } from "zod";
|
|
@@ -13528,13 +13526,13 @@ function registerDropshippingProjectTools(server, api) {
|
|
|
13528
13526
|
throw translateError(error);
|
|
13529
13527
|
}
|
|
13530
13528
|
});
|
|
13531
|
-
server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `
|
|
13529
|
+
server.tool("guide_dropshipping_launch", "Gu\xEDa read-only de un recorrido COMPLETO de dropshipping, desde producto/costeo hasta creativos, tienda y publicidad. No uses este tool para subir o publicar un creativo puntual en Meta: llama directamente `request_media_upload`, `list_meta_destinations` y `publish_creatives_to_meta`. Sin projectId explica cu\xE1ndo usar un flujo directo versus uno guiado; con projectId devuelve la fase y la \xFAnica nextAction calculada por ProductMaker. Despu\xE9s de cada mutaci\xF3n vuelve a llamar este tool o lee el proyecto. Nunca activa Meta.", GuideInput.shape, async (input) => {
|
|
13532
13530
|
try {
|
|
13533
13531
|
if (!input.projectId) {
|
|
13534
13532
|
return asText({
|
|
13535
13533
|
mode: "choose",
|
|
13536
13534
|
guided: "Usa start_dropshipping_project cuando el usuario quiera encontrar un producto y dejarlo listo para publicar.",
|
|
13537
|
-
direct: "En modo directo usa la herramienta puntual. Para subir/publicar un creativo propio en Meta:
|
|
13535
|
+
direct: "En modo directo usa la herramienta puntual. Para subir/publicar un creativo propio en Meta: request_media_upload \u2192 list_meta_destinations \u2192 publish_creatives_to_meta. No crees un proyecto guiado.",
|
|
13538
13536
|
confirmation: "Antes de acciones pagadas o externas, muestra el estimado y pide confirmaci\xF3n.",
|
|
13539
13537
|
safety: "Las campa\xF1as Meta quedan PAUSED; el usuario las activa manualmente en Ads Manager."
|
|
13540
13538
|
});
|
|
@@ -13550,7 +13548,10 @@ __name(registerDropshippingProjectTools, "registerDropshippingProjectTools");
|
|
|
13550
13548
|
// src/guidance/productmaker-guidance.ts
|
|
13551
13549
|
var START_DROPSHIPPING_PROMPT_NAME = "start_dropshipping";
|
|
13552
13550
|
var PRODUCTMAKER_SERVER_INSTRUCTIONS = [
|
|
13553
|
-
"
|
|
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.",
|
|
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.",
|
|
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.",
|
|
13554
13555
|
"Keep focused requests direct: creatives, landing pages, Shopify publication, Meta publication, and status checks do not require a DropshippingProject.",
|
|
13555
13556
|
"Use the guided journey only for an explicit end-to-end launch.",
|
|
13556
13557
|
"In guided mode, create a project only after a product is chosen, then call guide_dropshipping_launch after every mutation and follow its nextAction.",
|
|
@@ -13615,7 +13616,7 @@ function createServer(opts) {
|
|
|
13615
13616
|
});
|
|
13616
13617
|
const api = new ApiClient(apiBaseUrl, apiKey);
|
|
13617
13618
|
registerProductMakerPrompts(server);
|
|
13618
|
-
|
|
13619
|
+
registerMediaUploadAppResource(server, api.publicUrl("/v1/uploaded-assets/sessions/resolve"));
|
|
13619
13620
|
registerProductCostingTools(server);
|
|
13620
13621
|
registerProductResearchTools(server, api);
|
|
13621
13622
|
registerDropshippingProjectTools(server, api);
|
|
@@ -13633,7 +13634,7 @@ function createServer(opts) {
|
|
|
13633
13634
|
registerPublishTools(server, api, {
|
|
13634
13635
|
transport
|
|
13635
13636
|
});
|
|
13636
|
-
|
|
13637
|
+
registerMediaUploadTools(server, api);
|
|
13637
13638
|
registerCampaignTools(server, api);
|
|
13638
13639
|
return server;
|
|
13639
13640
|
}
|
|
@@ -13650,6 +13651,9 @@ function assertSafePublicBaseUrl(envName, url) {
|
|
|
13650
13651
|
if (u.protocol !== "https:") {
|
|
13651
13652
|
throw new Error(`${envName} must use https (got ${u.protocol})`);
|
|
13652
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
|
+
}
|
|
13653
13657
|
if (isPrivateHost(u.hostname)) {
|
|
13654
13658
|
throw new Error(`${envName} must not point to a private/loopback host: ${u.hostname}`);
|
|
13655
13659
|
}
|