@socialneuron/mcp-server 1.9.0 → 1.9.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/CHANGELOG.md +26 -1
- package/dist/http.js +341 -117
- package/dist/index.js +343 -123
- package/dist/sn.js +343 -123
- package/package.json +1 -1
- package/tools.lock.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var MCP_VERSION;
|
|
|
19
19
|
var init_version = __esm({
|
|
20
20
|
"src/lib/version.ts"() {
|
|
21
21
|
"use strict";
|
|
22
|
-
MCP_VERSION = "1.9.
|
|
22
|
+
MCP_VERSION = "1.9.2";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -2285,26 +2285,35 @@ var init_constants = __esm({
|
|
|
2285
2285
|
constants_default = {
|
|
2286
2286
|
ZERO_WIDTH_CHARS: "[\\u200B\\u200C\\u200D\\u2060\\uFEFF\\u{E0020}-\\u{E007F}]",
|
|
2287
2287
|
INSTRUCTION_PHRASES: [
|
|
2288
|
-
"(?:^|[^A-Za-z])ignore (?:all|previous|prior)
|
|
2289
|
-
"(?:^|[^A-Za-z])disregard (?:the
|
|
2290
|
-
"(?:^|[^A-Za-z])forget (?:all|previous|prior|
|
|
2288
|
+
"(?:^|[^A-Za-z])ignore (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
|
|
2289
|
+
"(?:^|[^A-Za-z])disregard (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*(?:instructions|prompts?|context)",
|
|
2290
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
|
|
2291
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )+(?:everything|context)",
|
|
2292
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:everything|context) (?:above|previously|prior|before|you (?:were|have been))",
|
|
2291
2293
|
"(?:^|[^A-Za-z])you are now (?:a |an )",
|
|
2292
2294
|
"(?:^|[^A-Za-z])system:\\s",
|
|
2293
2295
|
"<\\|im_start\\|>",
|
|
2294
2296
|
"<\\|im_end\\|>",
|
|
2295
2297
|
"</?im_(?:start|end)>",
|
|
2296
2298
|
"<\\|assistant\\|>",
|
|
2297
|
-
"(?:^|[^A-Za-z])\\[INST\\]"
|
|
2299
|
+
"(?:^|[^A-Za-z])\\[INST\\]",
|
|
2300
|
+
"(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
|
|
2301
|
+
"(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)"
|
|
2298
2302
|
],
|
|
2299
2303
|
PII_PATTERNS: {
|
|
2300
2304
|
email: "[\\w.+-]+@[\\w-]+\\.[\\w.-]+",
|
|
2301
2305
|
phone_us: "(?<!\\d|-)(?:\\+?1[-\\s.])?\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]?\\d{4}(?!\\d|-)",
|
|
2302
2306
|
phone_intl: "\\+\\d{1,3}[-\\s.]?\\d{1,4}[-\\s.]?\\d{1,4}[-\\s.]?\\d{4,}",
|
|
2303
2307
|
jwt: "eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+",
|
|
2304
|
-
api_key: "(?:sk|pk|snk|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
|
|
2308
|
+
api_key: "(?:sk|pk|snk|sno|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
|
|
2305
2309
|
credit_card: "(?<!\\d|-)(?:\\d{4}[ -]?){3}\\d{4}(?!\\d|-)",
|
|
2306
2310
|
ssn: "\\d{3}-\\d{2}-\\d{4}",
|
|
2307
|
-
ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])"
|
|
2311
|
+
ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])",
|
|
2312
|
+
aws_access_key: "AKIA[0-9A-Z]{16}",
|
|
2313
|
+
google_api_key: "AIza[0-9A-Za-z_\\-]{35}",
|
|
2314
|
+
slack_token: "xox[abprs]-[A-Za-z0-9-]{10,}|xapp-[0-9]-[A-Za-z0-9-]{10,}",
|
|
2315
|
+
bearer_token: "Bearer\\s+[A-Za-z0-9._~+/=-]{20,}",
|
|
2316
|
+
private_key_block: "-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----"
|
|
2308
2317
|
},
|
|
2309
2318
|
MAX_LENGTH: 1e4,
|
|
2310
2319
|
MAX_OUTPUT_LENGTH: 1e6
|
|
@@ -2331,13 +2340,90 @@ function normalize(text) {
|
|
|
2331
2340
|
out = out.replace(EXCESSIVE_WHITESPACE, " ");
|
|
2332
2341
|
return out;
|
|
2333
2342
|
}
|
|
2334
|
-
|
|
2343
|
+
function skeleton(text) {
|
|
2344
|
+
if (typeof text !== "string") return "";
|
|
2345
|
+
return text.replace(CONFUSABLE_RE, (ch) => CONFUSABLES[ch] ?? ch);
|
|
2346
|
+
}
|
|
2347
|
+
var OVERRIDE_CHARS, HTML_COMMENT, EXCESSIVE_WHITESPACE, CONFUSABLES, CONFUSABLE_RE;
|
|
2335
2348
|
var init_normalize = __esm({
|
|
2336
2349
|
"src/lib/agent-harness/normalize.ts"() {
|
|
2337
2350
|
"use strict";
|
|
2338
2351
|
OVERRIDE_CHARS = /[--]/g;
|
|
2339
2352
|
HTML_COMMENT = /<!--[\s\S]*?-->/g;
|
|
2340
2353
|
EXCESSIVE_WHITESPACE = /\s{2,}/g;
|
|
2354
|
+
CONFUSABLES = {
|
|
2355
|
+
// Cyrillic, lowercase
|
|
2356
|
+
\u0430: "a",
|
|
2357
|
+
\u0432: "b",
|
|
2358
|
+
\u0435: "e",
|
|
2359
|
+
\u0455: "s",
|
|
2360
|
+
\u0456: "i",
|
|
2361
|
+
\u0458: "j",
|
|
2362
|
+
\u043A: "k",
|
|
2363
|
+
\u043C: "m",
|
|
2364
|
+
\u043D: "h",
|
|
2365
|
+
\u043E: "o",
|
|
2366
|
+
\u0440: "p",
|
|
2367
|
+
\u0441: "c",
|
|
2368
|
+
\u0442: "t",
|
|
2369
|
+
\u0443: "y",
|
|
2370
|
+
\u0445: "x",
|
|
2371
|
+
"\u0501": "d",
|
|
2372
|
+
\u0451: "e",
|
|
2373
|
+
\u04BB: "h",
|
|
2374
|
+
\u0475: "v",
|
|
2375
|
+
"\u051B": "q",
|
|
2376
|
+
\u0461: "w",
|
|
2377
|
+
"\u04CF": "l",
|
|
2378
|
+
"\u051D": "w",
|
|
2379
|
+
// Cyrillic, uppercase
|
|
2380
|
+
\u0410: "A",
|
|
2381
|
+
\u0412: "B",
|
|
2382
|
+
\u0415: "E",
|
|
2383
|
+
\u0405: "S",
|
|
2384
|
+
\u0406: "I",
|
|
2385
|
+
\u0408: "J",
|
|
2386
|
+
\u041A: "K",
|
|
2387
|
+
\u041C: "M",
|
|
2388
|
+
\u041D: "H",
|
|
2389
|
+
\u041E: "O",
|
|
2390
|
+
\u0420: "P",
|
|
2391
|
+
\u0421: "C",
|
|
2392
|
+
\u0422: "T",
|
|
2393
|
+
\u0423: "Y",
|
|
2394
|
+
\u0425: "X",
|
|
2395
|
+
// Greek, lowercase
|
|
2396
|
+
\u03B1: "a",
|
|
2397
|
+
\u03B2: "b",
|
|
2398
|
+
\u03B5: "e",
|
|
2399
|
+
\u03B9: "i",
|
|
2400
|
+
\u03BA: "k",
|
|
2401
|
+
\u03BD: "v",
|
|
2402
|
+
\u03BF: "o",
|
|
2403
|
+
\u03C1: "p",
|
|
2404
|
+
\u03C4: "t",
|
|
2405
|
+
\u03C5: "u",
|
|
2406
|
+
\u03C7: "x",
|
|
2407
|
+
\u03F2: "c",
|
|
2408
|
+
\u03B3: "y",
|
|
2409
|
+
\u03B7: "n",
|
|
2410
|
+
// Greek, uppercase
|
|
2411
|
+
\u0391: "A",
|
|
2412
|
+
\u0392: "B",
|
|
2413
|
+
\u0395: "E",
|
|
2414
|
+
\u0396: "Z",
|
|
2415
|
+
\u0397: "H",
|
|
2416
|
+
\u0399: "I",
|
|
2417
|
+
\u039A: "K",
|
|
2418
|
+
\u039C: "M",
|
|
2419
|
+
\u039D: "N",
|
|
2420
|
+
\u039F: "O",
|
|
2421
|
+
\u03A1: "P",
|
|
2422
|
+
\u03A4: "T",
|
|
2423
|
+
\u03A5: "Y",
|
|
2424
|
+
\u03A7: "X"
|
|
2425
|
+
};
|
|
2426
|
+
CONFUSABLE_RE = new RegExp(`[${Object.keys(CONFUSABLES).join("")}]`, "g");
|
|
2341
2427
|
}
|
|
2342
2428
|
});
|
|
2343
2429
|
|
|
@@ -2429,6 +2515,14 @@ function scan(text, options) {
|
|
|
2429
2515
|
flagged.add(ipNorm.pattern);
|
|
2430
2516
|
risk = Math.max(risk, 0.9);
|
|
2431
2517
|
}
|
|
2518
|
+
const skeletonText = skeleton(normalized);
|
|
2519
|
+
if (skeletonText !== normalized) {
|
|
2520
|
+
const ipSkel = detectInstructionPhrase(skeletonText);
|
|
2521
|
+
if (ipSkel.found) {
|
|
2522
|
+
flagged.add(ipSkel.pattern);
|
|
2523
|
+
risk = Math.max(risk, 0.9);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2432
2526
|
const flaggedArr = Array.from(flagged);
|
|
2433
2527
|
if ((options.mode === "block" || options.mode === "sanitize") && flaggedArr.length > 0) {
|
|
2434
2528
|
return { passed: false, risk_score: risk, flagged_patterns: flaggedArr, pii_redacted: false };
|
|
@@ -2944,6 +3038,92 @@ var init_ideation = __esm({
|
|
|
2944
3038
|
}
|
|
2945
3039
|
});
|
|
2946
3040
|
|
|
3041
|
+
// src/lib/sanitize-error.ts
|
|
3042
|
+
function redactSensitiveIdentifiers(message) {
|
|
3043
|
+
return message.replace(EMAIL_PATTERN, "[redacted-email]").replace(UUID_PATTERN, "[redacted-id]");
|
|
3044
|
+
}
|
|
3045
|
+
function sanitizeError(error) {
|
|
3046
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
3047
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
3048
|
+
if (pattern.test(msg)) {
|
|
3049
|
+
return userMessage;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
return "An unexpected error occurred. Please try again.";
|
|
3053
|
+
}
|
|
3054
|
+
var ERROR_PATTERNS, UUID_PATTERN, EMAIL_PATTERN;
|
|
3055
|
+
var init_sanitize_error = __esm({
|
|
3056
|
+
"src/lib/sanitize-error.ts"() {
|
|
3057
|
+
"use strict";
|
|
3058
|
+
ERROR_PATTERNS = [
|
|
3059
|
+
// Postgres / PostgREST
|
|
3060
|
+
[
|
|
3061
|
+
/PGRST301|permission denied/i,
|
|
3062
|
+
"Access denied. Check your account permissions."
|
|
3063
|
+
],
|
|
3064
|
+
[
|
|
3065
|
+
/42P01|does not exist/i,
|
|
3066
|
+
"Service temporarily unavailable. Please try again."
|
|
3067
|
+
],
|
|
3068
|
+
[
|
|
3069
|
+
/23505|unique.*constraint|duplicate key/i,
|
|
3070
|
+
"A duplicate record already exists."
|
|
3071
|
+
],
|
|
3072
|
+
[/23503|foreign key/i, "Referenced record not found."],
|
|
3073
|
+
// Gemini / Google AI
|
|
3074
|
+
[
|
|
3075
|
+
/google.*api.*key|googleapis\.com.*40[13]/i,
|
|
3076
|
+
"Content generation failed. Please try again."
|
|
3077
|
+
],
|
|
3078
|
+
[
|
|
3079
|
+
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
3080
|
+
"AI service rate limit reached. Please wait and retry."
|
|
3081
|
+
],
|
|
3082
|
+
[
|
|
3083
|
+
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
3084
|
+
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
3085
|
+
],
|
|
3086
|
+
[
|
|
3087
|
+
/gemini.*error|generativelanguage/i,
|
|
3088
|
+
"Content generation failed. Please try again."
|
|
3089
|
+
],
|
|
3090
|
+
// Kie.ai
|
|
3091
|
+
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
3092
|
+
// Stripe
|
|
3093
|
+
[
|
|
3094
|
+
/stripe.*api|sk_live_|sk_test_/i,
|
|
3095
|
+
"Payment processing error. Please try again."
|
|
3096
|
+
],
|
|
3097
|
+
// Network / fetch
|
|
3098
|
+
[
|
|
3099
|
+
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
3100
|
+
"External service unavailable. Please try again."
|
|
3101
|
+
],
|
|
3102
|
+
[
|
|
3103
|
+
/fetch failed|network error|abort.*timeout/i,
|
|
3104
|
+
"Network request failed. Please try again."
|
|
3105
|
+
],
|
|
3106
|
+
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
3107
|
+
// Supabase Edge Function internals
|
|
3108
|
+
[
|
|
3109
|
+
/FunctionsHttpError|non-2xx status/i,
|
|
3110
|
+
"Backend service error. Please try again."
|
|
3111
|
+
],
|
|
3112
|
+
[
|
|
3113
|
+
/JWT|token.*expired|token.*invalid/i,
|
|
3114
|
+
"Authentication expired. Please re-authenticate."
|
|
3115
|
+
],
|
|
3116
|
+
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
3117
|
+
[
|
|
3118
|
+
/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i,
|
|
3119
|
+
"An internal error occurred. Please try again."
|
|
3120
|
+
]
|
|
3121
|
+
];
|
|
3122
|
+
UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
3123
|
+
EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
3124
|
+
}
|
|
3125
|
+
});
|
|
3126
|
+
|
|
2947
3127
|
// src/lib/checkStatusShape.ts
|
|
2948
3128
|
function buildCheckStatusPayload(job, liveStatus) {
|
|
2949
3129
|
const status = liveStatus?.status ?? job.status;
|
|
@@ -3039,9 +3219,22 @@ function checkCreditBudget(estimatedCost) {
|
|
|
3039
3219
|
}
|
|
3040
3220
|
const used = getCreditsUsed();
|
|
3041
3221
|
if (used + estimatedCost > MAX_CREDITS_PER_RUN) {
|
|
3222
|
+
const message = `Per-run credit cap reached \u2014 this is a local spend guard, not a server rate limit, so retrying will not help. The SOCIALNEURON_MAX_CREDITS_PER_RUN environment variable is set to ${MAX_CREDITS_PER_RUN} credits. This run has already spent ${used} credits and this call is estimated at ~${estimatedCost} more (${used} + ${estimatedCost} = ${used + estimatedCost} > ${MAX_CREDITS_PER_RUN}). To proceed, raise or unset SOCIALNEURON_MAX_CREDITS_PER_RUN.`;
|
|
3042
3223
|
return {
|
|
3043
3224
|
ok: false,
|
|
3044
|
-
message
|
|
3225
|
+
message,
|
|
3226
|
+
error: toolError("billing_error", message, {
|
|
3227
|
+
recover_with: [
|
|
3228
|
+
"Raise SOCIALNEURON_MAX_CREDITS_PER_RUN to a higher credit ceiling.",
|
|
3229
|
+
"Unset SOCIALNEURON_MAX_CREDITS_PER_RUN to remove the per-run cap."
|
|
3230
|
+
],
|
|
3231
|
+
details: {
|
|
3232
|
+
env_var: "SOCIALNEURON_MAX_CREDITS_PER_RUN",
|
|
3233
|
+
credits_used_this_run: used,
|
|
3234
|
+
estimated_call_cost: estimatedCost,
|
|
3235
|
+
max_credits_per_run: MAX_CREDITS_PER_RUN
|
|
3236
|
+
}
|
|
3237
|
+
})
|
|
3045
3238
|
};
|
|
3046
3239
|
}
|
|
3047
3240
|
return { ok: true };
|
|
@@ -3064,8 +3257,15 @@ var init_budget = __esm({
|
|
|
3064
3257
|
"src/lib/budget.ts"() {
|
|
3065
3258
|
"use strict";
|
|
3066
3259
|
init_request_context();
|
|
3067
|
-
|
|
3068
|
-
|
|
3260
|
+
init_tool_error();
|
|
3261
|
+
MAX_CREDITS_PER_RUN = Math.max(
|
|
3262
|
+
0,
|
|
3263
|
+
Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0)
|
|
3264
|
+
);
|
|
3265
|
+
MAX_ASSETS_PER_RUN = Math.max(
|
|
3266
|
+
0,
|
|
3267
|
+
Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0)
|
|
3268
|
+
);
|
|
3069
3269
|
_globalCreditsUsed = 0;
|
|
3070
3270
|
_globalAssetsGenerated = 0;
|
|
3071
3271
|
}
|
|
@@ -3092,7 +3292,7 @@ function asEnvelope2(data) {
|
|
|
3092
3292
|
function registerContentTools(server2) {
|
|
3093
3293
|
server2.tool(
|
|
3094
3294
|
"generate_video",
|
|
3095
|
-
"Start an async AI video generation job \u2014 returns a job_id immediately. Poll with check_status every 10-30s until complete. Base credit costs (reference config; dynamic models scale with duration/audio/resolution): seedance-2-fast 264 (8s, best quality/credit) \xB7 kling-3 100 (5s no-audio) \xB7 grok-imagine 30 (6s, cheapest) \xB7 veo3-fast 65 (8s) \xB7 kling-3-pro 135 (5s no-audio) \xB7 seedance-2 328 (8s 720p) \xB7 veo3-quality 1000 (8s) \xB7 wan-2.6 105 (5s) \xB7 gemini-omni-video 126 (multi-reference composites) \xB7 hailuo-02-standard 180 / seedance-1.5-pro 150 / kling 170 (legacy/budget fallbacks). Costs are estimated pre-check and reconciled post-response from the actual charge. audio adds ~1.5x on kling-3/kling-3-pro and ~2.65x on kling
|
|
3295
|
+
"Start an async AI video generation job \u2014 returns a job_id immediately. Poll with check_status every 10-30s until complete. Base credit costs (reference config; dynamic models scale with duration/audio/resolution): seedance-2-fast 264 (8s, best quality/credit) \xB7 kling-3 100 (5s no-audio) \xB7 grok-imagine 30 (6s, cheapest) \xB7 veo3-fast 65 (8s) \xB7 kling-3-pro 135 (5s no-audio) \xB7 seedance-2 328 (8s 720p) \xB7 veo3-quality 1000 (8s) \xB7 wan-2.6 105 (5s) \xB7 gemini-omni-video 126 (multi-reference composites) \xB7 hailuo-02-standard 180 / seedance-1.5-pro 150 / kling 170 (legacy/budget fallbacks). Costs are estimated pre-check and reconciled post-response from the actual charge. audio adds ~1.5x on kling-3/kling-3-pro and ~2.65x on kling; enable_audio defaults to FALSE EXCEPT the seedance-2 family (seedance-2-fast, seedance-2) where audio is ON by default and its cost is already included. Check get_credit_balance first for expensive generations.",
|
|
3096
3296
|
{
|
|
3097
3297
|
prompt: z2.string().max(2500).describe(
|
|
3098
3298
|
'Video prompt \u2014 be specific about visual style, camera movement, lighting, and mood. Example: "Aerial drone shot of coastal cliffs at golden hour, slow dolly forward, cinematic 24fps, warm color grading." Vague prompts produce generic results.'
|
|
@@ -3107,7 +3307,7 @@ function registerContentTools(server2) {
|
|
|
3107
3307
|
"Video aspect ratio. 16:9 for YouTube/landscape, 9:16 for TikTok/Reels/Shorts, 1:1 for Instagram feed/square. Defaults to 16:9."
|
|
3108
3308
|
),
|
|
3109
3309
|
enable_audio: z2.boolean().optional().describe(
|
|
3110
|
-
"Enable native audio generation. DEFAULTS TO FALSE (cost control). Cost multiplier when true: kling 2.6 ~2.65x (17->45 cr/sec), kling-3 1.5x (20->30 cr/sec), kling-3-pro ~1.5x (27->40 cr/sec).
|
|
3310
|
+
"Enable native audio generation. For most models this DEFAULTS TO FALSE (cost control). EXCEPTION: the seedance-2 family (seedance-2-fast, seedance-2) DEFAULTS TO TRUE \u2014 these generate audio natively and their credit cost already includes it, so audio is on unless you explicitly pass false. Cost multiplier when true on other models: kling 2.6 ~2.65x (17->45 cr/sec), kling-3 1.5x (20->30 cr/sec), kling-3-pro ~1.5x (27->40 cr/sec). 5+ languages."
|
|
3111
3311
|
),
|
|
3112
3312
|
image_url: z2.string().optional().describe(
|
|
3113
3313
|
"Start frame image URL for image-to-video (Kling 3.0 frame control)."
|
|
@@ -3143,10 +3343,7 @@ function registerContentTools(server2) {
|
|
|
3143
3343
|
const estimatedCost = VIDEO_CREDIT_ESTIMATES[model] ?? 120;
|
|
3144
3344
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3145
3345
|
if (!budgetCheck.ok) {
|
|
3146
|
-
return
|
|
3147
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3148
|
-
isError: true
|
|
3149
|
-
};
|
|
3346
|
+
return budgetCheck.error;
|
|
3150
3347
|
}
|
|
3151
3348
|
const rateLimit = checkRateLimit(
|
|
3152
3349
|
"generation",
|
|
@@ -3170,9 +3367,12 @@ function registerContentTools(server2) {
|
|
|
3170
3367
|
model,
|
|
3171
3368
|
duration: duration ?? 5,
|
|
3172
3369
|
aspectRatio: aspect_ratio ?? "16:9",
|
|
3173
|
-
// Default FALSE (2026-07-13): the old `?? true` default silently
|
|
3174
|
-
// kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
3175
|
-
|
|
3370
|
+
// Default FALSE for most models (2026-07-13): the old `?? true` default silently
|
|
3371
|
+
// multiplied kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
3372
|
+
// for audio. Exception (2026-07-17): the seedance-2 family bills audio into its base
|
|
3373
|
+
// cost and generates it natively, so a false default there shipped silent no-audio
|
|
3374
|
+
// mp4s — those models default TRUE when the caller omits enable_audio.
|
|
3375
|
+
enableAudio: enable_audio ?? AUDIO_NATIVE_DEFAULT_MODELS.has(model),
|
|
3176
3376
|
...image_url && { imageUrl: image_url },
|
|
3177
3377
|
...end_frame_url && { endFrameUrl: end_frame_url },
|
|
3178
3378
|
// The server reads projectId and enforces
|
|
@@ -3294,10 +3494,7 @@ function registerContentTools(server2) {
|
|
|
3294
3494
|
const estimatedCost = IMAGE_CREDIT_ESTIMATES[model] ?? 30;
|
|
3295
3495
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3296
3496
|
if (!budgetCheck.ok) {
|
|
3297
|
-
return
|
|
3298
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3299
|
-
isError: true
|
|
3300
|
-
};
|
|
3497
|
+
return budgetCheck.error;
|
|
3301
3498
|
}
|
|
3302
3499
|
const rateLimit = checkRateLimit(
|
|
3303
3500
|
"generation",
|
|
@@ -3542,7 +3739,7 @@ function registerContentTools(server2) {
|
|
|
3542
3739
|
);
|
|
3543
3740
|
}
|
|
3544
3741
|
if (job.error_message) {
|
|
3545
|
-
lines.push(`Error: ${job.error_message}`);
|
|
3742
|
+
lines.push(`Error: ${redactSensitiveIdentifiers(job.error_message)}`);
|
|
3546
3743
|
}
|
|
3547
3744
|
const fallbackDisclosure = buildFallbackDisclosureLine(
|
|
3548
3745
|
job.result_metadata
|
|
@@ -3696,10 +3893,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3696
3893
|
const estimatedCost = 10;
|
|
3697
3894
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3698
3895
|
if (!budgetCheck.ok) {
|
|
3699
|
-
return
|
|
3700
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3701
|
-
isError: true
|
|
3702
|
-
};
|
|
3896
|
+
return budgetCheck.error;
|
|
3703
3897
|
}
|
|
3704
3898
|
const { data, error } = await callEdgeFunction(
|
|
3705
3899
|
"social-neuron-ai",
|
|
@@ -3786,10 +3980,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3786
3980
|
const estimatedCost = 15;
|
|
3787
3981
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3788
3982
|
if (!budgetCheck.ok) {
|
|
3789
|
-
return
|
|
3790
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3791
|
-
isError: true
|
|
3792
|
-
};
|
|
3983
|
+
return budgetCheck.error;
|
|
3793
3984
|
}
|
|
3794
3985
|
const rateLimit = checkRateLimit(
|
|
3795
3986
|
"generation",
|
|
@@ -3952,10 +4143,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3952
4143
|
const estimatedCost = 10 + slideCount * 2;
|
|
3953
4144
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3954
4145
|
if (!budgetCheck.ok) {
|
|
3955
|
-
return
|
|
3956
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3957
|
-
isError: true
|
|
3958
|
-
};
|
|
4146
|
+
return budgetCheck.error;
|
|
3959
4147
|
}
|
|
3960
4148
|
const userId = await getDefaultUserId();
|
|
3961
4149
|
const rateLimit = checkRateLimit(
|
|
@@ -4058,13 +4246,14 @@ Return ONLY valid JSON in this exact format:
|
|
|
4058
4246
|
}
|
|
4059
4247
|
);
|
|
4060
4248
|
}
|
|
4061
|
-
var VIDEO_CREDIT_ESTIMATES, VIDEO_MODEL_ENUM, IMAGE_CREDIT_ESTIMATES;
|
|
4249
|
+
var VIDEO_CREDIT_ESTIMATES, AUDIO_NATIVE_DEFAULT_MODELS, VIDEO_MODEL_ENUM, IMAGE_CREDIT_ESTIMATES;
|
|
4062
4250
|
var init_content = __esm({
|
|
4063
4251
|
"src/tools/content.ts"() {
|
|
4064
4252
|
"use strict";
|
|
4065
4253
|
init_edge_function();
|
|
4066
4254
|
init_rate_limit();
|
|
4067
4255
|
init_supabase();
|
|
4256
|
+
init_sanitize_error();
|
|
4068
4257
|
init_version();
|
|
4069
4258
|
init_checkStatusShape();
|
|
4070
4259
|
init_budget();
|
|
@@ -4082,6 +4271,10 @@ var init_content = __esm({
|
|
|
4082
4271
|
"seedance-1.5-pro": 150,
|
|
4083
4272
|
kling: 170
|
|
4084
4273
|
};
|
|
4274
|
+
AUDIO_NATIVE_DEFAULT_MODELS = /* @__PURE__ */ new Set([
|
|
4275
|
+
"seedance-2-fast",
|
|
4276
|
+
"seedance-2"
|
|
4277
|
+
]);
|
|
4085
4278
|
VIDEO_MODEL_ENUM = [
|
|
4086
4279
|
"seedance-2-fast",
|
|
4087
4280
|
"kling-3",
|
|
@@ -4110,62 +4303,24 @@ var init_content = __esm({
|
|
|
4110
4303
|
}
|
|
4111
4304
|
});
|
|
4112
4305
|
|
|
4113
|
-
// src/lib/sanitize-error.ts
|
|
4114
|
-
function sanitizeError(error) {
|
|
4115
|
-
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
4116
|
-
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
4117
|
-
if (pattern.test(msg)) {
|
|
4118
|
-
return userMessage;
|
|
4119
|
-
}
|
|
4120
|
-
}
|
|
4121
|
-
return "An unexpected error occurred. Please try again.";
|
|
4122
|
-
}
|
|
4123
|
-
var ERROR_PATTERNS;
|
|
4124
|
-
var init_sanitize_error = __esm({
|
|
4125
|
-
"src/lib/sanitize-error.ts"() {
|
|
4126
|
-
"use strict";
|
|
4127
|
-
ERROR_PATTERNS = [
|
|
4128
|
-
// Postgres / PostgREST
|
|
4129
|
-
[/PGRST301|permission denied/i, "Access denied. Check your account permissions."],
|
|
4130
|
-
[/42P01|does not exist/i, "Service temporarily unavailable. Please try again."],
|
|
4131
|
-
[/23505|unique.*constraint|duplicate key/i, "A duplicate record already exists."],
|
|
4132
|
-
[/23503|foreign key/i, "Referenced record not found."],
|
|
4133
|
-
// Gemini / Google AI
|
|
4134
|
-
[/google.*api.*key|googleapis\.com.*40[13]/i, "Content generation failed. Please try again."],
|
|
4135
|
-
[
|
|
4136
|
-
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
4137
|
-
"AI service rate limit reached. Please wait and retry."
|
|
4138
|
-
],
|
|
4139
|
-
[
|
|
4140
|
-
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
4141
|
-
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
4142
|
-
],
|
|
4143
|
-
[/gemini.*error|generativelanguage/i, "Content generation failed. Please try again."],
|
|
4144
|
-
// Kie.ai
|
|
4145
|
-
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
4146
|
-
// Stripe
|
|
4147
|
-
[/stripe.*api|sk_live_|sk_test_/i, "Payment processing error. Please try again."],
|
|
4148
|
-
// Network / fetch
|
|
4149
|
-
[
|
|
4150
|
-
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
4151
|
-
"External service unavailable. Please try again."
|
|
4152
|
-
],
|
|
4153
|
-
[/fetch failed|network error|abort.*timeout/i, "Network request failed. Please try again."],
|
|
4154
|
-
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
4155
|
-
// Supabase Edge Function internals
|
|
4156
|
-
[/FunctionsHttpError|non-2xx status/i, "Backend service error. Please try again."],
|
|
4157
|
-
[/JWT|token.*expired|token.*invalid/i, "Authentication expired. Please re-authenticate."],
|
|
4158
|
-
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
4159
|
-
[/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i, "An internal error occurred. Please try again."]
|
|
4160
|
-
];
|
|
4161
|
-
}
|
|
4162
|
-
});
|
|
4163
|
-
|
|
4164
4306
|
// src/lib/ssrf.ts
|
|
4165
4307
|
import { promises as dnsPromises } from "node:dns";
|
|
4308
|
+
function expandIPv4Mapped(ip) {
|
|
4309
|
+
const dotted = /^::ffff:((?:\d{1,3}\.){3}\d{1,3})$/i.exec(ip);
|
|
4310
|
+
if (dotted) return dotted[1];
|
|
4311
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(ip);
|
|
4312
|
+
if (hex) {
|
|
4313
|
+
const high = parseInt(hex[1], 16);
|
|
4314
|
+
const low = parseInt(hex[2], 16);
|
|
4315
|
+
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
4316
|
+
}
|
|
4317
|
+
return null;
|
|
4318
|
+
}
|
|
4166
4319
|
function isBlockedIP(ip) {
|
|
4167
4320
|
const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
|
|
4168
4321
|
if (normalized.includes(":")) {
|
|
4322
|
+
const mapped = expandIPv4Mapped(normalized);
|
|
4323
|
+
if (mapped) return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(mapped));
|
|
4169
4324
|
return BLOCKED_IPV6_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
4170
4325
|
}
|
|
4171
4326
|
return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
@@ -12127,6 +12282,14 @@ var init_plan_approvals = __esm({
|
|
|
12127
12282
|
|
|
12128
12283
|
// src/tools/discovery.ts
|
|
12129
12284
|
import { z as z23 } from "zod";
|
|
12285
|
+
function isHostedTransport() {
|
|
12286
|
+
return process.env.MCP_TRANSPORT === "http";
|
|
12287
|
+
}
|
|
12288
|
+
function isPubliclyDiscoverable(tool) {
|
|
12289
|
+
if (tool.internal || tool.hiddenFromPublicCount) return false;
|
|
12290
|
+
if (tool.localOnly && isHostedTransport()) return false;
|
|
12291
|
+
return true;
|
|
12292
|
+
}
|
|
12130
12293
|
function toolKnowledgeDocument(tool) {
|
|
12131
12294
|
const lines = [
|
|
12132
12295
|
`Tool: ${tool.name}`,
|
|
@@ -12137,7 +12300,8 @@ function toolKnowledgeDocument(tool) {
|
|
|
12137
12300
|
if (tool.task_intent) lines.push(`Task intent: ${tool.task_intent}`);
|
|
12138
12301
|
if (tool.use_when) lines.push(`Use when: ${tool.use_when}`);
|
|
12139
12302
|
if (tool.avoid_when) lines.push(`Avoid when: ${tool.avoid_when}`);
|
|
12140
|
-
if (tool.next_tools?.length)
|
|
12303
|
+
if (tool.next_tools?.length)
|
|
12304
|
+
lines.push(`Common next tools: ${tool.next_tools.join(", ")}`);
|
|
12141
12305
|
return {
|
|
12142
12306
|
id: `tool:${tool.name}`,
|
|
12143
12307
|
title: `MCP tool: ${tool.name}`,
|
|
@@ -12154,9 +12318,7 @@ function toolKnowledgeDocument(tool) {
|
|
|
12154
12318
|
function getKnowledgeDocuments() {
|
|
12155
12319
|
return [
|
|
12156
12320
|
...STATIC_KNOWLEDGE_DOCUMENTS,
|
|
12157
|
-
...TOOL_CATALOG.filter(
|
|
12158
|
-
toolKnowledgeDocument
|
|
12159
|
-
)
|
|
12321
|
+
...TOOL_CATALOG.filter(isPubliclyDiscoverable).map(toolKnowledgeDocument)
|
|
12160
12322
|
];
|
|
12161
12323
|
}
|
|
12162
12324
|
function tokenize(input) {
|
|
@@ -12210,7 +12372,9 @@ function registerDiscoveryTools(server2) {
|
|
|
12210
12372
|
};
|
|
12211
12373
|
return {
|
|
12212
12374
|
structuredContent,
|
|
12213
|
-
content: [
|
|
12375
|
+
content: [
|
|
12376
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
12377
|
+
]
|
|
12214
12378
|
};
|
|
12215
12379
|
}
|
|
12216
12380
|
);
|
|
@@ -12231,10 +12395,14 @@ function registerDiscoveryTools(server2) {
|
|
|
12231
12395
|
}
|
|
12232
12396
|
},
|
|
12233
12397
|
async ({ id }) => {
|
|
12234
|
-
const doc = getKnowledgeDocuments().find(
|
|
12398
|
+
const doc = getKnowledgeDocuments().find(
|
|
12399
|
+
(candidate) => candidate.id === id
|
|
12400
|
+
);
|
|
12235
12401
|
if (!doc) {
|
|
12236
12402
|
return {
|
|
12237
|
-
content: [
|
|
12403
|
+
content: [
|
|
12404
|
+
{ type: "text", text: `Document not found: ${id}` }
|
|
12405
|
+
],
|
|
12238
12406
|
isError: true
|
|
12239
12407
|
};
|
|
12240
12408
|
}
|
|
@@ -12247,7 +12415,9 @@ function registerDiscoveryTools(server2) {
|
|
|
12247
12415
|
};
|
|
12248
12416
|
return {
|
|
12249
12417
|
structuredContent,
|
|
12250
|
-
content: [
|
|
12418
|
+
content: [
|
|
12419
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
12420
|
+
]
|
|
12251
12421
|
};
|
|
12252
12422
|
}
|
|
12253
12423
|
);
|
|
@@ -12256,7 +12426,9 @@ function registerDiscoveryTools(server2) {
|
|
|
12256
12426
|
'Search available tools by name, description, module, or scope. Use "name" detail (~50 tokens) for quick lookup, "summary" (~500 tokens) for descriptions, "full" for complete input schemas. Start here if unsure which tool to call \u2014 filter by module (e.g. "planning", "content", "analytics") to narrow results.',
|
|
12257
12427
|
{
|
|
12258
12428
|
query: z23.string().optional().describe("Search query to filter tools by name or description"),
|
|
12259
|
-
module: z23.string().optional().describe(
|
|
12429
|
+
module: z23.string().optional().describe(
|
|
12430
|
+
'Filter by module name (e.g. "planning", "content", "analytics")'
|
|
12431
|
+
),
|
|
12260
12432
|
scope: z23.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
12261
12433
|
detail: z23.enum(["name", "summary", "full"]).default("summary").describe(
|
|
12262
12434
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
@@ -12273,14 +12445,18 @@ function registerDiscoveryTools(server2) {
|
|
|
12273
12445
|
if (query) {
|
|
12274
12446
|
results = searchTools(query);
|
|
12275
12447
|
}
|
|
12276
|
-
results = results.filter(
|
|
12448
|
+
results = results.filter(isPubliclyDiscoverable);
|
|
12277
12449
|
if (module) {
|
|
12278
12450
|
const moduleTools = getToolsByModule(module);
|
|
12279
|
-
results = results.filter(
|
|
12451
|
+
results = results.filter(
|
|
12452
|
+
(t) => moduleTools.some((mt) => mt.name === t.name)
|
|
12453
|
+
);
|
|
12280
12454
|
}
|
|
12281
12455
|
if (scope) {
|
|
12282
12456
|
const scopeTools = getToolsByScope(scope);
|
|
12283
|
-
results = results.filter(
|
|
12457
|
+
results = results.filter(
|
|
12458
|
+
(t) => scopeTools.some((st) => st.name === t.name)
|
|
12459
|
+
);
|
|
12284
12460
|
}
|
|
12285
12461
|
if (available_only) {
|
|
12286
12462
|
results = results.filter(isAvailable);
|
|
@@ -12318,7 +12494,12 @@ function registerDiscoveryTools(server2) {
|
|
|
12318
12494
|
text: JSON.stringify(
|
|
12319
12495
|
{
|
|
12320
12496
|
toolCount: results.length,
|
|
12321
|
-
...hasKnownScopes ? {
|
|
12497
|
+
...hasKnownScopes ? {
|
|
12498
|
+
scopes: {
|
|
12499
|
+
available: currentScopes,
|
|
12500
|
+
unavailable_matches: unavailableCount
|
|
12501
|
+
}
|
|
12502
|
+
} : {},
|
|
12322
12503
|
tools: output
|
|
12323
12504
|
},
|
|
12324
12505
|
null,
|
|
@@ -14909,25 +15090,39 @@ function registerCarouselTools(server2) {
|
|
|
14909
15090
|
]).optional().describe("Carousel template. Default: hormozi-authority."),
|
|
14910
15091
|
slide_count: z28.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
14911
15092
|
aspect_ratio: z28.enum(["1:1", "4:5", "9:16"]).optional().describe("Aspect ratio for both carousel and images. Default: 1:1."),
|
|
14912
|
-
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
15093
|
+
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
15094
|
+
"Visual style. Default: hormozi for hormozi-authority template."
|
|
15095
|
+
),
|
|
14913
15096
|
image_style_suffix: z28.string().max(500).optional().describe(
|
|
14914
15097
|
'Style suffix appended to every image prompt for visual consistency across slides. Example: "dark moody lighting, cinematic, 35mm film grain".'
|
|
14915
15098
|
),
|
|
14916
15099
|
hook: z28.string().max(300).optional().describe(
|
|
14917
15100
|
"Explicit hook/opener for slide 1. Overrides any hook derived from topic. Keep under 15 words for strongest scroll-stop."
|
|
14918
15101
|
),
|
|
14919
|
-
hook_family: z28.enum([
|
|
15102
|
+
hook_family: z28.enum([
|
|
15103
|
+
"curiosity",
|
|
15104
|
+
"authority",
|
|
15105
|
+
"pain_point",
|
|
15106
|
+
"contrarian",
|
|
15107
|
+
"data_driven"
|
|
15108
|
+
]).optional().describe(
|
|
14920
15109
|
"Hook family tag. Recorded on the carousel so downstream analytics can attribute engagement to hook pattern."
|
|
14921
15110
|
),
|
|
14922
|
-
cta_text: z28.string().max(200).optional().describe(
|
|
14923
|
-
|
|
15111
|
+
cta_text: z28.string().max(200).optional().describe(
|
|
15112
|
+
"Explicit CTA copy for the final slide. If omitted, derived from topic."
|
|
15113
|
+
),
|
|
15114
|
+
cta_url: z28.string().url().optional().describe(
|
|
15115
|
+
"URL promoted on the CTA slide. Defaults to the project landing page."
|
|
15116
|
+
),
|
|
14924
15117
|
tone: z28.string().max(200).optional().describe(
|
|
14925
15118
|
'Voice/tone override. Composes with the brand profile voice when present. Example: "educational, confident, not arrogant".'
|
|
14926
15119
|
),
|
|
14927
15120
|
constraints: z28.string().max(500).optional().describe(
|
|
14928
15121
|
'Content constraints applied at generation time. Example: "No fabricated statistics. Sentence case only. No ALL CAPS."'
|
|
14929
15122
|
),
|
|
14930
|
-
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
15123
|
+
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
15124
|
+
"Target platform. Affects tone conventions and slide-count guardrails."
|
|
15125
|
+
),
|
|
14931
15126
|
brand_id: z28.string().optional().describe(
|
|
14932
15127
|
"Brand/project ID to pull visual context from (colors, logo, mood). Falls back to project_id, then default project."
|
|
14933
15128
|
),
|
|
@@ -14959,7 +15154,9 @@ function registerCarouselTools(server2) {
|
|
|
14959
15154
|
const slideCount = slide_count ?? 7;
|
|
14960
15155
|
const ratio = aspect_ratio ?? "1:1";
|
|
14961
15156
|
let brandContext = null;
|
|
14962
|
-
const
|
|
15157
|
+
const defaultProjectId = await getDefaultProjectId();
|
|
15158
|
+
const brandProjectId = brand_id || project_id || defaultProjectId;
|
|
15159
|
+
const resolvedProjectId = project_id || defaultProjectId;
|
|
14963
15160
|
if (brandProjectId) {
|
|
14964
15161
|
brandContext = await fetchBrandVisualContext(brandProjectId);
|
|
14965
15162
|
}
|
|
@@ -14968,10 +15165,7 @@ function registerCarouselTools(server2) {
|
|
|
14968
15165
|
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
14969
15166
|
const budgetCheck = checkCreditBudget(totalEstimatedCost);
|
|
14970
15167
|
if (!budgetCheck.ok) {
|
|
14971
|
-
return
|
|
14972
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
14973
|
-
isError: true
|
|
14974
|
-
};
|
|
15168
|
+
return budgetCheck.error;
|
|
14975
15169
|
}
|
|
14976
15170
|
const assetBudget = checkAssetBudget(slideCount);
|
|
14977
15171
|
if (!assetBudget.ok) {
|
|
@@ -14981,7 +15175,10 @@ function registerCarouselTools(server2) {
|
|
|
14981
15175
|
};
|
|
14982
15176
|
}
|
|
14983
15177
|
const userId = await getDefaultUserId();
|
|
14984
|
-
const rateLimit = checkRateLimit(
|
|
15178
|
+
const rateLimit = checkRateLimit(
|
|
15179
|
+
"generation",
|
|
15180
|
+
`create_carousel:${userId}`
|
|
15181
|
+
);
|
|
14985
15182
|
if (!rateLimit.allowed) {
|
|
14986
15183
|
return {
|
|
14987
15184
|
content: [
|
|
@@ -15001,7 +15198,7 @@ function registerCarouselTools(server2) {
|
|
|
15001
15198
|
slideCount,
|
|
15002
15199
|
aspectRatio: ratio,
|
|
15003
15200
|
style: resolvedStyle,
|
|
15004
|
-
projectId:
|
|
15201
|
+
projectId: resolvedProjectId,
|
|
15005
15202
|
hook,
|
|
15006
15203
|
hookFamily: hook_family,
|
|
15007
15204
|
ctaText: cta_text,
|
|
@@ -15015,7 +15212,12 @@ function registerCarouselTools(server2) {
|
|
|
15015
15212
|
if (carouselError || !carouselData?.carousel) {
|
|
15016
15213
|
const errMsg = carouselError ?? "No carousel data returned";
|
|
15017
15214
|
return {
|
|
15018
|
-
content: [
|
|
15215
|
+
content: [
|
|
15216
|
+
{
|
|
15217
|
+
type: "text",
|
|
15218
|
+
text: `Carousel text generation failed: ${errMsg}`
|
|
15219
|
+
}
|
|
15220
|
+
],
|
|
15019
15221
|
isError: true
|
|
15020
15222
|
};
|
|
15021
15223
|
}
|
|
@@ -15044,7 +15246,8 @@ function registerCarouselTools(server2) {
|
|
|
15044
15246
|
{
|
|
15045
15247
|
prompt: imagePrompt,
|
|
15046
15248
|
model: image_model,
|
|
15047
|
-
aspectRatio: ratio
|
|
15249
|
+
aspectRatio: ratio,
|
|
15250
|
+
...resolvedProjectId && { projectId: resolvedProjectId }
|
|
15048
15251
|
},
|
|
15049
15252
|
{ timeoutMs: 3e4 }
|
|
15050
15253
|
);
|
|
@@ -15101,14 +15304,19 @@ function registerCarouselTools(server2) {
|
|
|
15101
15304
|
type: "text",
|
|
15102
15305
|
text: JSON.stringify(
|
|
15103
15306
|
{
|
|
15104
|
-
_meta: {
|
|
15307
|
+
_meta: {
|
|
15308
|
+
version: MCP_VERSION,
|
|
15309
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
15310
|
+
},
|
|
15105
15311
|
data: {
|
|
15106
15312
|
carouselId: carousel.id,
|
|
15107
15313
|
templateId,
|
|
15108
15314
|
style: resolvedStyle,
|
|
15109
15315
|
slideCount: carousel.slides.length,
|
|
15110
15316
|
slides: carousel.slides.map((s) => {
|
|
15111
|
-
const job = imageJobs.find(
|
|
15317
|
+
const job = imageJobs.find(
|
|
15318
|
+
(j) => j.slideNumber === s.slideNumber
|
|
15319
|
+
);
|
|
15112
15320
|
return {
|
|
15113
15321
|
...s,
|
|
15114
15322
|
imageJobId: job?.jobId ?? null,
|
|
@@ -15135,9 +15343,17 @@ function registerCarouselTools(server2) {
|
|
|
15135
15343
|
credits: {
|
|
15136
15344
|
textGeneration: textCredits,
|
|
15137
15345
|
imagesEstimated: successfulJobs.length * perImageCost,
|
|
15138
|
-
imagesCharged: imageJobs.reduce(
|
|
15139
|
-
|
|
15140
|
-
|
|
15346
|
+
imagesCharged: imageJobs.reduce(
|
|
15347
|
+
(sum, job) => sum + (job.creditsCharged ?? 0),
|
|
15348
|
+
0
|
|
15349
|
+
),
|
|
15350
|
+
imagesRefunded: imageJobs.reduce(
|
|
15351
|
+
(sum, job) => sum + (job.creditsRefunded ?? 0),
|
|
15352
|
+
0
|
|
15353
|
+
),
|
|
15354
|
+
billingUnknownSlides: imageJobs.filter(
|
|
15355
|
+
(job) => job.billingStatus === "unknown"
|
|
15356
|
+
).length,
|
|
15141
15357
|
totalEstimated: textCredits + successfulJobs.length * perImageCost
|
|
15142
15358
|
}
|
|
15143
15359
|
}
|
|
@@ -15165,7 +15381,9 @@ function registerCarouselTools(server2) {
|
|
|
15165
15381
|
for (const slide of carousel.slides) {
|
|
15166
15382
|
const job = imageJobs.find((j) => j.slideNumber === slide.slideNumber);
|
|
15167
15383
|
const status = job?.jobId ? `image: ${job.jobId}` : `image FAILED: ${job?.error}`;
|
|
15168
|
-
lines.push(
|
|
15384
|
+
lines.push(
|
|
15385
|
+
` ${slide.slideNumber}. ${slide.headline || "(no headline)"} [${status}]`
|
|
15386
|
+
);
|
|
15169
15387
|
}
|
|
15170
15388
|
if (failedJobs.length > 0) {
|
|
15171
15389
|
lines.push("");
|
|
@@ -15176,7 +15394,9 @@ function registerCarouselTools(server2) {
|
|
|
15176
15394
|
const jobIdList = successfulJobs.map((j) => j.jobId).join(", ");
|
|
15177
15395
|
lines.push("");
|
|
15178
15396
|
lines.push("Next steps:");
|
|
15179
|
-
lines.push(
|
|
15397
|
+
lines.push(
|
|
15398
|
+
` 1. Poll each job: check_status with job_id for each of: ${jobIdList}`
|
|
15399
|
+
);
|
|
15180
15400
|
lines.push(
|
|
15181
15401
|
" 2. When all complete: schedule_post with job_ids=[...] and media_type=CAROUSEL_ALBUM"
|
|
15182
15402
|
);
|