@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/http.js
CHANGED
|
@@ -2264,26 +2264,35 @@ function applyToolProfile(server, profile) {
|
|
|
2264
2264
|
var constants_default = {
|
|
2265
2265
|
ZERO_WIDTH_CHARS: "[\\u200B\\u200C\\u200D\\u2060\\uFEFF\\u{E0020}-\\u{E007F}]",
|
|
2266
2266
|
INSTRUCTION_PHRASES: [
|
|
2267
|
-
"(?:^|[^A-Za-z])ignore (?:all|previous|prior)
|
|
2268
|
-
"(?:^|[^A-Za-z])disregard (?:the
|
|
2269
|
-
"(?:^|[^A-Za-z])forget (?:all|previous|prior|
|
|
2267
|
+
"(?:^|[^A-Za-z])ignore (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
|
|
2268
|
+
"(?:^|[^A-Za-z])disregard (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*(?:instructions|prompts?|context)",
|
|
2269
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
|
|
2270
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )+(?:everything|context)",
|
|
2271
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:everything|context) (?:above|previously|prior|before|you (?:were|have been))",
|
|
2270
2272
|
"(?:^|[^A-Za-z])you are now (?:a |an )",
|
|
2271
2273
|
"(?:^|[^A-Za-z])system:\\s",
|
|
2272
2274
|
"<\\|im_start\\|>",
|
|
2273
2275
|
"<\\|im_end\\|>",
|
|
2274
2276
|
"</?im_(?:start|end)>",
|
|
2275
2277
|
"<\\|assistant\\|>",
|
|
2276
|
-
"(?:^|[^A-Za-z])\\[INST\\]"
|
|
2278
|
+
"(?:^|[^A-Za-z])\\[INST\\]",
|
|
2279
|
+
"(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
|
|
2280
|
+
"(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)"
|
|
2277
2281
|
],
|
|
2278
2282
|
PII_PATTERNS: {
|
|
2279
2283
|
email: "[\\w.+-]+@[\\w-]+\\.[\\w.-]+",
|
|
2280
2284
|
phone_us: "(?<!\\d|-)(?:\\+?1[-\\s.])?\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]?\\d{4}(?!\\d|-)",
|
|
2281
2285
|
phone_intl: "\\+\\d{1,3}[-\\s.]?\\d{1,4}[-\\s.]?\\d{1,4}[-\\s.]?\\d{4,}",
|
|
2282
2286
|
jwt: "eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+",
|
|
2283
|
-
api_key: "(?:sk|pk|snk|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
|
|
2287
|
+
api_key: "(?:sk|pk|snk|sno|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
|
|
2284
2288
|
credit_card: "(?<!\\d|-)(?:\\d{4}[ -]?){3}\\d{4}(?!\\d|-)",
|
|
2285
2289
|
ssn: "\\d{3}-\\d{2}-\\d{4}",
|
|
2286
|
-
ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])"
|
|
2290
|
+
ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])",
|
|
2291
|
+
aws_access_key: "AKIA[0-9A-Z]{16}",
|
|
2292
|
+
google_api_key: "AIza[0-9A-Za-z_\\-]{35}",
|
|
2293
|
+
slack_token: "xox[abprs]-[A-Za-z0-9-]{10,}|xapp-[0-9]-[A-Za-z0-9-]{10,}",
|
|
2294
|
+
bearer_token: "Bearer\\s+[A-Za-z0-9._~+/=-]{20,}",
|
|
2295
|
+
private_key_block: "-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----"
|
|
2287
2296
|
},
|
|
2288
2297
|
MAX_LENGTH: 1e4,
|
|
2289
2298
|
MAX_OUTPUT_LENGTH: 1e6
|
|
@@ -2304,6 +2313,83 @@ function normalize(text) {
|
|
|
2304
2313
|
out = out.replace(EXCESSIVE_WHITESPACE, " ");
|
|
2305
2314
|
return out;
|
|
2306
2315
|
}
|
|
2316
|
+
var CONFUSABLES = {
|
|
2317
|
+
// Cyrillic, lowercase
|
|
2318
|
+
\u0430: "a",
|
|
2319
|
+
\u0432: "b",
|
|
2320
|
+
\u0435: "e",
|
|
2321
|
+
\u0455: "s",
|
|
2322
|
+
\u0456: "i",
|
|
2323
|
+
\u0458: "j",
|
|
2324
|
+
\u043A: "k",
|
|
2325
|
+
\u043C: "m",
|
|
2326
|
+
\u043D: "h",
|
|
2327
|
+
\u043E: "o",
|
|
2328
|
+
\u0440: "p",
|
|
2329
|
+
\u0441: "c",
|
|
2330
|
+
\u0442: "t",
|
|
2331
|
+
\u0443: "y",
|
|
2332
|
+
\u0445: "x",
|
|
2333
|
+
"\u0501": "d",
|
|
2334
|
+
\u0451: "e",
|
|
2335
|
+
\u04BB: "h",
|
|
2336
|
+
\u0475: "v",
|
|
2337
|
+
"\u051B": "q",
|
|
2338
|
+
\u0461: "w",
|
|
2339
|
+
"\u04CF": "l",
|
|
2340
|
+
"\u051D": "w",
|
|
2341
|
+
// Cyrillic, uppercase
|
|
2342
|
+
\u0410: "A",
|
|
2343
|
+
\u0412: "B",
|
|
2344
|
+
\u0415: "E",
|
|
2345
|
+
\u0405: "S",
|
|
2346
|
+
\u0406: "I",
|
|
2347
|
+
\u0408: "J",
|
|
2348
|
+
\u041A: "K",
|
|
2349
|
+
\u041C: "M",
|
|
2350
|
+
\u041D: "H",
|
|
2351
|
+
\u041E: "O",
|
|
2352
|
+
\u0420: "P",
|
|
2353
|
+
\u0421: "C",
|
|
2354
|
+
\u0422: "T",
|
|
2355
|
+
\u0423: "Y",
|
|
2356
|
+
\u0425: "X",
|
|
2357
|
+
// Greek, lowercase
|
|
2358
|
+
\u03B1: "a",
|
|
2359
|
+
\u03B2: "b",
|
|
2360
|
+
\u03B5: "e",
|
|
2361
|
+
\u03B9: "i",
|
|
2362
|
+
\u03BA: "k",
|
|
2363
|
+
\u03BD: "v",
|
|
2364
|
+
\u03BF: "o",
|
|
2365
|
+
\u03C1: "p",
|
|
2366
|
+
\u03C4: "t",
|
|
2367
|
+
\u03C5: "u",
|
|
2368
|
+
\u03C7: "x",
|
|
2369
|
+
\u03F2: "c",
|
|
2370
|
+
\u03B3: "y",
|
|
2371
|
+
\u03B7: "n",
|
|
2372
|
+
// Greek, uppercase
|
|
2373
|
+
\u0391: "A",
|
|
2374
|
+
\u0392: "B",
|
|
2375
|
+
\u0395: "E",
|
|
2376
|
+
\u0396: "Z",
|
|
2377
|
+
\u0397: "H",
|
|
2378
|
+
\u0399: "I",
|
|
2379
|
+
\u039A: "K",
|
|
2380
|
+
\u039C: "M",
|
|
2381
|
+
\u039D: "N",
|
|
2382
|
+
\u039F: "O",
|
|
2383
|
+
\u03A1: "P",
|
|
2384
|
+
\u03A4: "T",
|
|
2385
|
+
\u03A5: "Y",
|
|
2386
|
+
\u03A7: "X"
|
|
2387
|
+
};
|
|
2388
|
+
var CONFUSABLE_RE = new RegExp(`[${Object.keys(CONFUSABLES).join("")}]`, "g");
|
|
2389
|
+
function skeleton(text) {
|
|
2390
|
+
if (typeof text !== "string") return "";
|
|
2391
|
+
return text.replace(CONFUSABLE_RE, (ch) => CONFUSABLES[ch] ?? ch);
|
|
2392
|
+
}
|
|
2307
2393
|
|
|
2308
2394
|
// src/lib/agent-harness/detectors/zeroWidth.ts
|
|
2309
2395
|
var ZERO_WIDTH_RE = new RegExp(CONSTANTS.ZERO_WIDTH_CHARS, "u");
|
|
@@ -2372,6 +2458,14 @@ function scan(text, options) {
|
|
|
2372
2458
|
flagged.add(ipNorm.pattern);
|
|
2373
2459
|
risk = Math.max(risk, 0.9);
|
|
2374
2460
|
}
|
|
2461
|
+
const skeletonText = skeleton(normalized);
|
|
2462
|
+
if (skeletonText !== normalized) {
|
|
2463
|
+
const ipSkel = detectInstructionPhrase(skeletonText);
|
|
2464
|
+
if (ipSkel.found) {
|
|
2465
|
+
flagged.add(ipSkel.pattern);
|
|
2466
|
+
risk = Math.max(risk, 0.9);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2375
2469
|
const flaggedArr = Array.from(flagged);
|
|
2376
2470
|
if ((options.mode === "block" || options.mode === "sanitize") && flaggedArr.length > 0) {
|
|
2377
2471
|
return { passed: false, risk_score: risk, flagged_patterns: flaggedArr, pii_redacted: false };
|
|
@@ -2509,7 +2603,7 @@ function checkRateLimit(category, key) {
|
|
|
2509
2603
|
init_supabase();
|
|
2510
2604
|
|
|
2511
2605
|
// src/lib/version.ts
|
|
2512
|
-
var MCP_VERSION = "1.9.
|
|
2606
|
+
var MCP_VERSION = "1.9.2";
|
|
2513
2607
|
|
|
2514
2608
|
// src/tools/ideation.ts
|
|
2515
2609
|
function asEnvelope(data) {
|
|
@@ -2914,6 +3008,86 @@ init_edge_function();
|
|
|
2914
3008
|
import { z as z2 } from "zod";
|
|
2915
3009
|
init_supabase();
|
|
2916
3010
|
|
|
3011
|
+
// src/lib/sanitize-error.ts
|
|
3012
|
+
var ERROR_PATTERNS = [
|
|
3013
|
+
// Postgres / PostgREST
|
|
3014
|
+
[
|
|
3015
|
+
/PGRST301|permission denied/i,
|
|
3016
|
+
"Access denied. Check your account permissions."
|
|
3017
|
+
],
|
|
3018
|
+
[
|
|
3019
|
+
/42P01|does not exist/i,
|
|
3020
|
+
"Service temporarily unavailable. Please try again."
|
|
3021
|
+
],
|
|
3022
|
+
[
|
|
3023
|
+
/23505|unique.*constraint|duplicate key/i,
|
|
3024
|
+
"A duplicate record already exists."
|
|
3025
|
+
],
|
|
3026
|
+
[/23503|foreign key/i, "Referenced record not found."],
|
|
3027
|
+
// Gemini / Google AI
|
|
3028
|
+
[
|
|
3029
|
+
/google.*api.*key|googleapis\.com.*40[13]/i,
|
|
3030
|
+
"Content generation failed. Please try again."
|
|
3031
|
+
],
|
|
3032
|
+
[
|
|
3033
|
+
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
3034
|
+
"AI service rate limit reached. Please wait and retry."
|
|
3035
|
+
],
|
|
3036
|
+
[
|
|
3037
|
+
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
3038
|
+
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
3039
|
+
],
|
|
3040
|
+
[
|
|
3041
|
+
/gemini.*error|generativelanguage/i,
|
|
3042
|
+
"Content generation failed. Please try again."
|
|
3043
|
+
],
|
|
3044
|
+
// Kie.ai
|
|
3045
|
+
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
3046
|
+
// Stripe
|
|
3047
|
+
[
|
|
3048
|
+
/stripe.*api|sk_live_|sk_test_/i,
|
|
3049
|
+
"Payment processing error. Please try again."
|
|
3050
|
+
],
|
|
3051
|
+
// Network / fetch
|
|
3052
|
+
[
|
|
3053
|
+
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
3054
|
+
"External service unavailable. Please try again."
|
|
3055
|
+
],
|
|
3056
|
+
[
|
|
3057
|
+
/fetch failed|network error|abort.*timeout/i,
|
|
3058
|
+
"Network request failed. Please try again."
|
|
3059
|
+
],
|
|
3060
|
+
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
3061
|
+
// Supabase Edge Function internals
|
|
3062
|
+
[
|
|
3063
|
+
/FunctionsHttpError|non-2xx status/i,
|
|
3064
|
+
"Backend service error. Please try again."
|
|
3065
|
+
],
|
|
3066
|
+
[
|
|
3067
|
+
/JWT|token.*expired|token.*invalid/i,
|
|
3068
|
+
"Authentication expired. Please re-authenticate."
|
|
3069
|
+
],
|
|
3070
|
+
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
3071
|
+
[
|
|
3072
|
+
/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i,
|
|
3073
|
+
"An internal error occurred. Please try again."
|
|
3074
|
+
]
|
|
3075
|
+
];
|
|
3076
|
+
var UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
3077
|
+
var EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
3078
|
+
function redactSensitiveIdentifiers(message) {
|
|
3079
|
+
return message.replace(EMAIL_PATTERN, "[redacted-email]").replace(UUID_PATTERN, "[redacted-id]");
|
|
3080
|
+
}
|
|
3081
|
+
function sanitizeError(error) {
|
|
3082
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
3083
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
3084
|
+
if (pattern.test(msg)) {
|
|
3085
|
+
return userMessage;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
return "An unexpected error occurred. Please try again.";
|
|
3089
|
+
}
|
|
3090
|
+
|
|
2917
3091
|
// src/lib/checkStatusShape.ts
|
|
2918
3092
|
function buildCheckStatusPayload(job, liveStatus) {
|
|
2919
3093
|
const status = liveStatus?.status ?? job.status;
|
|
@@ -2963,8 +3137,14 @@ function buildCheckStatusPayload(job, liveStatus) {
|
|
|
2963
3137
|
|
|
2964
3138
|
// src/lib/budget.ts
|
|
2965
3139
|
init_request_context();
|
|
2966
|
-
var MAX_CREDITS_PER_RUN = Math.max(
|
|
2967
|
-
|
|
3140
|
+
var MAX_CREDITS_PER_RUN = Math.max(
|
|
3141
|
+
0,
|
|
3142
|
+
Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0)
|
|
3143
|
+
);
|
|
3144
|
+
var MAX_ASSETS_PER_RUN = Math.max(
|
|
3145
|
+
0,
|
|
3146
|
+
Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0)
|
|
3147
|
+
);
|
|
2968
3148
|
var _globalCreditsUsed = 0;
|
|
2969
3149
|
var _globalAssetsGenerated = 0;
|
|
2970
3150
|
function getCreditsUsed() {
|
|
@@ -3009,9 +3189,22 @@ function checkCreditBudget(estimatedCost) {
|
|
|
3009
3189
|
}
|
|
3010
3190
|
const used = getCreditsUsed();
|
|
3011
3191
|
if (used + estimatedCost > MAX_CREDITS_PER_RUN) {
|
|
3192
|
+
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.`;
|
|
3012
3193
|
return {
|
|
3013
3194
|
ok: false,
|
|
3014
|
-
message
|
|
3195
|
+
message,
|
|
3196
|
+
error: toolError("billing_error", message, {
|
|
3197
|
+
recover_with: [
|
|
3198
|
+
"Raise SOCIALNEURON_MAX_CREDITS_PER_RUN to a higher credit ceiling.",
|
|
3199
|
+
"Unset SOCIALNEURON_MAX_CREDITS_PER_RUN to remove the per-run cap."
|
|
3200
|
+
],
|
|
3201
|
+
details: {
|
|
3202
|
+
env_var: "SOCIALNEURON_MAX_CREDITS_PER_RUN",
|
|
3203
|
+
credits_used_this_run: used,
|
|
3204
|
+
estimated_call_cost: estimatedCost,
|
|
3205
|
+
max_credits_per_run: MAX_CREDITS_PER_RUN
|
|
3206
|
+
}
|
|
3207
|
+
})
|
|
3015
3208
|
};
|
|
3016
3209
|
}
|
|
3017
3210
|
return { ok: true };
|
|
@@ -3061,6 +3254,10 @@ var VIDEO_CREDIT_ESTIMATES = {
|
|
|
3061
3254
|
"seedance-1.5-pro": 150,
|
|
3062
3255
|
kling: 170
|
|
3063
3256
|
};
|
|
3257
|
+
var AUDIO_NATIVE_DEFAULT_MODELS = /* @__PURE__ */ new Set([
|
|
3258
|
+
"seedance-2-fast",
|
|
3259
|
+
"seedance-2"
|
|
3260
|
+
]);
|
|
3064
3261
|
var VIDEO_MODEL_ENUM = [
|
|
3065
3262
|
"seedance-2-fast",
|
|
3066
3263
|
"kling-3",
|
|
@@ -3089,7 +3286,7 @@ var IMAGE_CREDIT_ESTIMATES = {
|
|
|
3089
3286
|
function registerContentTools(server) {
|
|
3090
3287
|
server.tool(
|
|
3091
3288
|
"generate_video",
|
|
3092
|
-
"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
|
|
3289
|
+
"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.",
|
|
3093
3290
|
{
|
|
3094
3291
|
prompt: z2.string().max(2500).describe(
|
|
3095
3292
|
'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.'
|
|
@@ -3104,7 +3301,7 @@ function registerContentTools(server) {
|
|
|
3104
3301
|
"Video aspect ratio. 16:9 for YouTube/landscape, 9:16 for TikTok/Reels/Shorts, 1:1 for Instagram feed/square. Defaults to 16:9."
|
|
3105
3302
|
),
|
|
3106
3303
|
enable_audio: z2.boolean().optional().describe(
|
|
3107
|
-
"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).
|
|
3304
|
+
"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."
|
|
3108
3305
|
),
|
|
3109
3306
|
image_url: z2.string().optional().describe(
|
|
3110
3307
|
"Start frame image URL for image-to-video (Kling 3.0 frame control)."
|
|
@@ -3140,10 +3337,7 @@ function registerContentTools(server) {
|
|
|
3140
3337
|
const estimatedCost = VIDEO_CREDIT_ESTIMATES[model] ?? 120;
|
|
3141
3338
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3142
3339
|
if (!budgetCheck.ok) {
|
|
3143
|
-
return
|
|
3144
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3145
|
-
isError: true
|
|
3146
|
-
};
|
|
3340
|
+
return budgetCheck.error;
|
|
3147
3341
|
}
|
|
3148
3342
|
const rateLimit = checkRateLimit(
|
|
3149
3343
|
"generation",
|
|
@@ -3167,9 +3361,12 @@ function registerContentTools(server) {
|
|
|
3167
3361
|
model,
|
|
3168
3362
|
duration: duration ?? 5,
|
|
3169
3363
|
aspectRatio: aspect_ratio ?? "16:9",
|
|
3170
|
-
// Default FALSE (2026-07-13): the old `?? true` default silently
|
|
3171
|
-
// kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
3172
|
-
|
|
3364
|
+
// Default FALSE for most models (2026-07-13): the old `?? true` default silently
|
|
3365
|
+
// multiplied kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
3366
|
+
// for audio. Exception (2026-07-17): the seedance-2 family bills audio into its base
|
|
3367
|
+
// cost and generates it natively, so a false default there shipped silent no-audio
|
|
3368
|
+
// mp4s — those models default TRUE when the caller omits enable_audio.
|
|
3369
|
+
enableAudio: enable_audio ?? AUDIO_NATIVE_DEFAULT_MODELS.has(model),
|
|
3173
3370
|
...image_url && { imageUrl: image_url },
|
|
3174
3371
|
...end_frame_url && { endFrameUrl: end_frame_url },
|
|
3175
3372
|
// The server reads projectId and enforces
|
|
@@ -3291,10 +3488,7 @@ function registerContentTools(server) {
|
|
|
3291
3488
|
const estimatedCost = IMAGE_CREDIT_ESTIMATES[model] ?? 30;
|
|
3292
3489
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3293
3490
|
if (!budgetCheck.ok) {
|
|
3294
|
-
return
|
|
3295
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3296
|
-
isError: true
|
|
3297
|
-
};
|
|
3491
|
+
return budgetCheck.error;
|
|
3298
3492
|
}
|
|
3299
3493
|
const rateLimit = checkRateLimit(
|
|
3300
3494
|
"generation",
|
|
@@ -3539,7 +3733,7 @@ function registerContentTools(server) {
|
|
|
3539
3733
|
);
|
|
3540
3734
|
}
|
|
3541
3735
|
if (job.error_message) {
|
|
3542
|
-
lines.push(`Error: ${job.error_message}`);
|
|
3736
|
+
lines.push(`Error: ${redactSensitiveIdentifiers(job.error_message)}`);
|
|
3543
3737
|
}
|
|
3544
3738
|
const fallbackDisclosure = buildFallbackDisclosureLine(
|
|
3545
3739
|
job.result_metadata
|
|
@@ -3693,10 +3887,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3693
3887
|
const estimatedCost = 10;
|
|
3694
3888
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3695
3889
|
if (!budgetCheck.ok) {
|
|
3696
|
-
return
|
|
3697
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3698
|
-
isError: true
|
|
3699
|
-
};
|
|
3890
|
+
return budgetCheck.error;
|
|
3700
3891
|
}
|
|
3701
3892
|
const { data, error } = await callEdgeFunction(
|
|
3702
3893
|
"social-neuron-ai",
|
|
@@ -3783,10 +3974,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3783
3974
|
const estimatedCost = 15;
|
|
3784
3975
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3785
3976
|
if (!budgetCheck.ok) {
|
|
3786
|
-
return
|
|
3787
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3788
|
-
isError: true
|
|
3789
|
-
};
|
|
3977
|
+
return budgetCheck.error;
|
|
3790
3978
|
}
|
|
3791
3979
|
const rateLimit = checkRateLimit(
|
|
3792
3980
|
"generation",
|
|
@@ -3949,10 +4137,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3949
4137
|
const estimatedCost = 10 + slideCount * 2;
|
|
3950
4138
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3951
4139
|
if (!budgetCheck.ok) {
|
|
3952
|
-
return
|
|
3953
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3954
|
-
isError: true
|
|
3955
|
-
};
|
|
4140
|
+
return budgetCheck.error;
|
|
3956
4141
|
}
|
|
3957
4142
|
const userId = await getDefaultUserId();
|
|
3958
4143
|
const rateLimit = checkRateLimit(
|
|
@@ -4061,51 +4246,6 @@ init_edge_function();
|
|
|
4061
4246
|
import { z as z3 } from "zod";
|
|
4062
4247
|
import { createHash } from "node:crypto";
|
|
4063
4248
|
|
|
4064
|
-
// src/lib/sanitize-error.ts
|
|
4065
|
-
var ERROR_PATTERNS = [
|
|
4066
|
-
// Postgres / PostgREST
|
|
4067
|
-
[/PGRST301|permission denied/i, "Access denied. Check your account permissions."],
|
|
4068
|
-
[/42P01|does not exist/i, "Service temporarily unavailable. Please try again."],
|
|
4069
|
-
[/23505|unique.*constraint|duplicate key/i, "A duplicate record already exists."],
|
|
4070
|
-
[/23503|foreign key/i, "Referenced record not found."],
|
|
4071
|
-
// Gemini / Google AI
|
|
4072
|
-
[/google.*api.*key|googleapis\.com.*40[13]/i, "Content generation failed. Please try again."],
|
|
4073
|
-
[
|
|
4074
|
-
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
4075
|
-
"AI service rate limit reached. Please wait and retry."
|
|
4076
|
-
],
|
|
4077
|
-
[
|
|
4078
|
-
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
4079
|
-
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
4080
|
-
],
|
|
4081
|
-
[/gemini.*error|generativelanguage/i, "Content generation failed. Please try again."],
|
|
4082
|
-
// Kie.ai
|
|
4083
|
-
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
4084
|
-
// Stripe
|
|
4085
|
-
[/stripe.*api|sk_live_|sk_test_/i, "Payment processing error. Please try again."],
|
|
4086
|
-
// Network / fetch
|
|
4087
|
-
[
|
|
4088
|
-
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
4089
|
-
"External service unavailable. Please try again."
|
|
4090
|
-
],
|
|
4091
|
-
[/fetch failed|network error|abort.*timeout/i, "Network request failed. Please try again."],
|
|
4092
|
-
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
4093
|
-
// Supabase Edge Function internals
|
|
4094
|
-
[/FunctionsHttpError|non-2xx status/i, "Backend service error. Please try again."],
|
|
4095
|
-
[/JWT|token.*expired|token.*invalid/i, "Authentication expired. Please re-authenticate."],
|
|
4096
|
-
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
4097
|
-
[/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i, "An internal error occurred. Please try again."]
|
|
4098
|
-
];
|
|
4099
|
-
function sanitizeError(error) {
|
|
4100
|
-
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
4101
|
-
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
4102
|
-
if (pattern.test(msg)) {
|
|
4103
|
-
return userMessage;
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
|
-
return "An unexpected error occurred. Please try again.";
|
|
4107
|
-
}
|
|
4108
|
-
|
|
4109
4249
|
// src/lib/ssrf.ts
|
|
4110
4250
|
import { promises as dnsPromises } from "node:dns";
|
|
4111
4251
|
var BLOCKED_IP_PATTERNS = [
|
|
@@ -4157,9 +4297,22 @@ var BLOCKED_HOSTNAMES = [
|
|
|
4157
4297
|
];
|
|
4158
4298
|
var ALLOWED_PROTOCOLS = ["http:", "https:"];
|
|
4159
4299
|
var BLOCKED_PORTS = [22, 23, 25, 110, 143, 445, 3306, 5432, 6379, 27017, 11211];
|
|
4300
|
+
function expandIPv4Mapped(ip) {
|
|
4301
|
+
const dotted = /^::ffff:((?:\d{1,3}\.){3}\d{1,3})$/i.exec(ip);
|
|
4302
|
+
if (dotted) return dotted[1];
|
|
4303
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(ip);
|
|
4304
|
+
if (hex) {
|
|
4305
|
+
const high = parseInt(hex[1], 16);
|
|
4306
|
+
const low = parseInt(hex[2], 16);
|
|
4307
|
+
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
4308
|
+
}
|
|
4309
|
+
return null;
|
|
4310
|
+
}
|
|
4160
4311
|
function isBlockedIP(ip) {
|
|
4161
4312
|
const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
|
|
4162
4313
|
if (normalized.includes(":")) {
|
|
4314
|
+
const mapped = expandIPv4Mapped(normalized);
|
|
4315
|
+
if (mapped) return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(mapped));
|
|
4163
4316
|
return BLOCKED_IPV6_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
4164
4317
|
}
|
|
4165
4318
|
return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
@@ -11887,6 +12040,14 @@ init_supabase();
|
|
|
11887
12040
|
init_request_context();
|
|
11888
12041
|
var KNOWLEDGE_BASE_URL = "https://socialneuron.com/for-developers";
|
|
11889
12042
|
var KNOWLEDGE_SEARCH_LIMIT = 10;
|
|
12043
|
+
function isHostedTransport() {
|
|
12044
|
+
return process.env.MCP_TRANSPORT === "http";
|
|
12045
|
+
}
|
|
12046
|
+
function isPubliclyDiscoverable(tool) {
|
|
12047
|
+
if (tool.internal || tool.hiddenFromPublicCount) return false;
|
|
12048
|
+
if (tool.localOnly && isHostedTransport()) return false;
|
|
12049
|
+
return true;
|
|
12050
|
+
}
|
|
11890
12051
|
var SearchOutputSchema = {
|
|
11891
12052
|
results: z23.array(
|
|
11892
12053
|
z23.object({
|
|
@@ -11959,7 +12120,8 @@ function toolKnowledgeDocument(tool) {
|
|
|
11959
12120
|
if (tool.task_intent) lines.push(`Task intent: ${tool.task_intent}`);
|
|
11960
12121
|
if (tool.use_when) lines.push(`Use when: ${tool.use_when}`);
|
|
11961
12122
|
if (tool.avoid_when) lines.push(`Avoid when: ${tool.avoid_when}`);
|
|
11962
|
-
if (tool.next_tools?.length)
|
|
12123
|
+
if (tool.next_tools?.length)
|
|
12124
|
+
lines.push(`Common next tools: ${tool.next_tools.join(", ")}`);
|
|
11963
12125
|
return {
|
|
11964
12126
|
id: `tool:${tool.name}`,
|
|
11965
12127
|
title: `MCP tool: ${tool.name}`,
|
|
@@ -11976,9 +12138,7 @@ function toolKnowledgeDocument(tool) {
|
|
|
11976
12138
|
function getKnowledgeDocuments() {
|
|
11977
12139
|
return [
|
|
11978
12140
|
...STATIC_KNOWLEDGE_DOCUMENTS,
|
|
11979
|
-
...TOOL_CATALOG.filter(
|
|
11980
|
-
toolKnowledgeDocument
|
|
11981
|
-
)
|
|
12141
|
+
...TOOL_CATALOG.filter(isPubliclyDiscoverable).map(toolKnowledgeDocument)
|
|
11982
12142
|
];
|
|
11983
12143
|
}
|
|
11984
12144
|
function tokenize(input) {
|
|
@@ -12032,7 +12192,9 @@ function registerDiscoveryTools(server) {
|
|
|
12032
12192
|
};
|
|
12033
12193
|
return {
|
|
12034
12194
|
structuredContent,
|
|
12035
|
-
content: [
|
|
12195
|
+
content: [
|
|
12196
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
12197
|
+
]
|
|
12036
12198
|
};
|
|
12037
12199
|
}
|
|
12038
12200
|
);
|
|
@@ -12053,10 +12215,14 @@ function registerDiscoveryTools(server) {
|
|
|
12053
12215
|
}
|
|
12054
12216
|
},
|
|
12055
12217
|
async ({ id }) => {
|
|
12056
|
-
const doc = getKnowledgeDocuments().find(
|
|
12218
|
+
const doc = getKnowledgeDocuments().find(
|
|
12219
|
+
(candidate) => candidate.id === id
|
|
12220
|
+
);
|
|
12057
12221
|
if (!doc) {
|
|
12058
12222
|
return {
|
|
12059
|
-
content: [
|
|
12223
|
+
content: [
|
|
12224
|
+
{ type: "text", text: `Document not found: ${id}` }
|
|
12225
|
+
],
|
|
12060
12226
|
isError: true
|
|
12061
12227
|
};
|
|
12062
12228
|
}
|
|
@@ -12069,7 +12235,9 @@ function registerDiscoveryTools(server) {
|
|
|
12069
12235
|
};
|
|
12070
12236
|
return {
|
|
12071
12237
|
structuredContent,
|
|
12072
|
-
content: [
|
|
12238
|
+
content: [
|
|
12239
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
12240
|
+
]
|
|
12073
12241
|
};
|
|
12074
12242
|
}
|
|
12075
12243
|
);
|
|
@@ -12078,7 +12246,9 @@ function registerDiscoveryTools(server) {
|
|
|
12078
12246
|
'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.',
|
|
12079
12247
|
{
|
|
12080
12248
|
query: z23.string().optional().describe("Search query to filter tools by name or description"),
|
|
12081
|
-
module: z23.string().optional().describe(
|
|
12249
|
+
module: z23.string().optional().describe(
|
|
12250
|
+
'Filter by module name (e.g. "planning", "content", "analytics")'
|
|
12251
|
+
),
|
|
12082
12252
|
scope: z23.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
12083
12253
|
detail: z23.enum(["name", "summary", "full"]).default("summary").describe(
|
|
12084
12254
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
@@ -12095,14 +12265,18 @@ function registerDiscoveryTools(server) {
|
|
|
12095
12265
|
if (query) {
|
|
12096
12266
|
results = searchTools(query);
|
|
12097
12267
|
}
|
|
12098
|
-
results = results.filter(
|
|
12268
|
+
results = results.filter(isPubliclyDiscoverable);
|
|
12099
12269
|
if (module) {
|
|
12100
12270
|
const moduleTools = getToolsByModule(module);
|
|
12101
|
-
results = results.filter(
|
|
12271
|
+
results = results.filter(
|
|
12272
|
+
(t) => moduleTools.some((mt) => mt.name === t.name)
|
|
12273
|
+
);
|
|
12102
12274
|
}
|
|
12103
12275
|
if (scope) {
|
|
12104
12276
|
const scopeTools = getToolsByScope(scope);
|
|
12105
|
-
results = results.filter(
|
|
12277
|
+
results = results.filter(
|
|
12278
|
+
(t) => scopeTools.some((st) => st.name === t.name)
|
|
12279
|
+
);
|
|
12106
12280
|
}
|
|
12107
12281
|
if (available_only) {
|
|
12108
12282
|
results = results.filter(isAvailable);
|
|
@@ -12140,7 +12314,12 @@ function registerDiscoveryTools(server) {
|
|
|
12140
12314
|
text: JSON.stringify(
|
|
12141
12315
|
{
|
|
12142
12316
|
toolCount: results.length,
|
|
12143
|
-
...hasKnownScopes ? {
|
|
12317
|
+
...hasKnownScopes ? {
|
|
12318
|
+
scopes: {
|
|
12319
|
+
available: currentScopes,
|
|
12320
|
+
unavailable_matches: unavailableCount
|
|
12321
|
+
}
|
|
12322
|
+
} : {},
|
|
12144
12323
|
tools: output
|
|
12145
12324
|
},
|
|
12146
12325
|
null,
|
|
@@ -14621,25 +14800,39 @@ function registerCarouselTools(server) {
|
|
|
14621
14800
|
]).optional().describe("Carousel template. Default: hormozi-authority."),
|
|
14622
14801
|
slide_count: z28.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
14623
14802
|
aspect_ratio: z28.enum(["1:1", "4:5", "9:16"]).optional().describe("Aspect ratio for both carousel and images. Default: 1:1."),
|
|
14624
|
-
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
14803
|
+
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
14804
|
+
"Visual style. Default: hormozi for hormozi-authority template."
|
|
14805
|
+
),
|
|
14625
14806
|
image_style_suffix: z28.string().max(500).optional().describe(
|
|
14626
14807
|
'Style suffix appended to every image prompt for visual consistency across slides. Example: "dark moody lighting, cinematic, 35mm film grain".'
|
|
14627
14808
|
),
|
|
14628
14809
|
hook: z28.string().max(300).optional().describe(
|
|
14629
14810
|
"Explicit hook/opener for slide 1. Overrides any hook derived from topic. Keep under 15 words for strongest scroll-stop."
|
|
14630
14811
|
),
|
|
14631
|
-
hook_family: z28.enum([
|
|
14812
|
+
hook_family: z28.enum([
|
|
14813
|
+
"curiosity",
|
|
14814
|
+
"authority",
|
|
14815
|
+
"pain_point",
|
|
14816
|
+
"contrarian",
|
|
14817
|
+
"data_driven"
|
|
14818
|
+
]).optional().describe(
|
|
14632
14819
|
"Hook family tag. Recorded on the carousel so downstream analytics can attribute engagement to hook pattern."
|
|
14633
14820
|
),
|
|
14634
|
-
cta_text: z28.string().max(200).optional().describe(
|
|
14635
|
-
|
|
14821
|
+
cta_text: z28.string().max(200).optional().describe(
|
|
14822
|
+
"Explicit CTA copy for the final slide. If omitted, derived from topic."
|
|
14823
|
+
),
|
|
14824
|
+
cta_url: z28.string().url().optional().describe(
|
|
14825
|
+
"URL promoted on the CTA slide. Defaults to the project landing page."
|
|
14826
|
+
),
|
|
14636
14827
|
tone: z28.string().max(200).optional().describe(
|
|
14637
14828
|
'Voice/tone override. Composes with the brand profile voice when present. Example: "educational, confident, not arrogant".'
|
|
14638
14829
|
),
|
|
14639
14830
|
constraints: z28.string().max(500).optional().describe(
|
|
14640
14831
|
'Content constraints applied at generation time. Example: "No fabricated statistics. Sentence case only. No ALL CAPS."'
|
|
14641
14832
|
),
|
|
14642
|
-
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
14833
|
+
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
14834
|
+
"Target platform. Affects tone conventions and slide-count guardrails."
|
|
14835
|
+
),
|
|
14643
14836
|
brand_id: z28.string().optional().describe(
|
|
14644
14837
|
"Brand/project ID to pull visual context from (colors, logo, mood). Falls back to project_id, then default project."
|
|
14645
14838
|
),
|
|
@@ -14671,7 +14864,9 @@ function registerCarouselTools(server) {
|
|
|
14671
14864
|
const slideCount = slide_count ?? 7;
|
|
14672
14865
|
const ratio = aspect_ratio ?? "1:1";
|
|
14673
14866
|
let brandContext = null;
|
|
14674
|
-
const
|
|
14867
|
+
const defaultProjectId = await getDefaultProjectId();
|
|
14868
|
+
const brandProjectId = brand_id || project_id || defaultProjectId;
|
|
14869
|
+
const resolvedProjectId = project_id || defaultProjectId;
|
|
14675
14870
|
if (brandProjectId) {
|
|
14676
14871
|
brandContext = await fetchBrandVisualContext(brandProjectId);
|
|
14677
14872
|
}
|
|
@@ -14680,10 +14875,7 @@ function registerCarouselTools(server) {
|
|
|
14680
14875
|
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
14681
14876
|
const budgetCheck = checkCreditBudget(totalEstimatedCost);
|
|
14682
14877
|
if (!budgetCheck.ok) {
|
|
14683
|
-
return
|
|
14684
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
14685
|
-
isError: true
|
|
14686
|
-
};
|
|
14878
|
+
return budgetCheck.error;
|
|
14687
14879
|
}
|
|
14688
14880
|
const assetBudget = checkAssetBudget(slideCount);
|
|
14689
14881
|
if (!assetBudget.ok) {
|
|
@@ -14693,7 +14885,10 @@ function registerCarouselTools(server) {
|
|
|
14693
14885
|
};
|
|
14694
14886
|
}
|
|
14695
14887
|
const userId = await getDefaultUserId();
|
|
14696
|
-
const rateLimit = checkRateLimit(
|
|
14888
|
+
const rateLimit = checkRateLimit(
|
|
14889
|
+
"generation",
|
|
14890
|
+
`create_carousel:${userId}`
|
|
14891
|
+
);
|
|
14697
14892
|
if (!rateLimit.allowed) {
|
|
14698
14893
|
return {
|
|
14699
14894
|
content: [
|
|
@@ -14713,7 +14908,7 @@ function registerCarouselTools(server) {
|
|
|
14713
14908
|
slideCount,
|
|
14714
14909
|
aspectRatio: ratio,
|
|
14715
14910
|
style: resolvedStyle,
|
|
14716
|
-
projectId:
|
|
14911
|
+
projectId: resolvedProjectId,
|
|
14717
14912
|
hook,
|
|
14718
14913
|
hookFamily: hook_family,
|
|
14719
14914
|
ctaText: cta_text,
|
|
@@ -14727,7 +14922,12 @@ function registerCarouselTools(server) {
|
|
|
14727
14922
|
if (carouselError || !carouselData?.carousel) {
|
|
14728
14923
|
const errMsg = carouselError ?? "No carousel data returned";
|
|
14729
14924
|
return {
|
|
14730
|
-
content: [
|
|
14925
|
+
content: [
|
|
14926
|
+
{
|
|
14927
|
+
type: "text",
|
|
14928
|
+
text: `Carousel text generation failed: ${errMsg}`
|
|
14929
|
+
}
|
|
14930
|
+
],
|
|
14731
14931
|
isError: true
|
|
14732
14932
|
};
|
|
14733
14933
|
}
|
|
@@ -14756,7 +14956,8 @@ function registerCarouselTools(server) {
|
|
|
14756
14956
|
{
|
|
14757
14957
|
prompt: imagePrompt,
|
|
14758
14958
|
model: image_model,
|
|
14759
|
-
aspectRatio: ratio
|
|
14959
|
+
aspectRatio: ratio,
|
|
14960
|
+
...resolvedProjectId && { projectId: resolvedProjectId }
|
|
14760
14961
|
},
|
|
14761
14962
|
{ timeoutMs: 3e4 }
|
|
14762
14963
|
);
|
|
@@ -14813,14 +15014,19 @@ function registerCarouselTools(server) {
|
|
|
14813
15014
|
type: "text",
|
|
14814
15015
|
text: JSON.stringify(
|
|
14815
15016
|
{
|
|
14816
|
-
_meta: {
|
|
15017
|
+
_meta: {
|
|
15018
|
+
version: MCP_VERSION,
|
|
15019
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
15020
|
+
},
|
|
14817
15021
|
data: {
|
|
14818
15022
|
carouselId: carousel.id,
|
|
14819
15023
|
templateId,
|
|
14820
15024
|
style: resolvedStyle,
|
|
14821
15025
|
slideCount: carousel.slides.length,
|
|
14822
15026
|
slides: carousel.slides.map((s) => {
|
|
14823
|
-
const job = imageJobs.find(
|
|
15027
|
+
const job = imageJobs.find(
|
|
15028
|
+
(j) => j.slideNumber === s.slideNumber
|
|
15029
|
+
);
|
|
14824
15030
|
return {
|
|
14825
15031
|
...s,
|
|
14826
15032
|
imageJobId: job?.jobId ?? null,
|
|
@@ -14847,9 +15053,17 @@ function registerCarouselTools(server) {
|
|
|
14847
15053
|
credits: {
|
|
14848
15054
|
textGeneration: textCredits,
|
|
14849
15055
|
imagesEstimated: successfulJobs.length * perImageCost,
|
|
14850
|
-
imagesCharged: imageJobs.reduce(
|
|
14851
|
-
|
|
14852
|
-
|
|
15056
|
+
imagesCharged: imageJobs.reduce(
|
|
15057
|
+
(sum, job) => sum + (job.creditsCharged ?? 0),
|
|
15058
|
+
0
|
|
15059
|
+
),
|
|
15060
|
+
imagesRefunded: imageJobs.reduce(
|
|
15061
|
+
(sum, job) => sum + (job.creditsRefunded ?? 0),
|
|
15062
|
+
0
|
|
15063
|
+
),
|
|
15064
|
+
billingUnknownSlides: imageJobs.filter(
|
|
15065
|
+
(job) => job.billingStatus === "unknown"
|
|
15066
|
+
).length,
|
|
14853
15067
|
totalEstimated: textCredits + successfulJobs.length * perImageCost
|
|
14854
15068
|
}
|
|
14855
15069
|
}
|
|
@@ -14877,7 +15091,9 @@ function registerCarouselTools(server) {
|
|
|
14877
15091
|
for (const slide of carousel.slides) {
|
|
14878
15092
|
const job = imageJobs.find((j) => j.slideNumber === slide.slideNumber);
|
|
14879
15093
|
const status = job?.jobId ? `image: ${job.jobId}` : `image FAILED: ${job?.error}`;
|
|
14880
|
-
lines.push(
|
|
15094
|
+
lines.push(
|
|
15095
|
+
` ${slide.slideNumber}. ${slide.headline || "(no headline)"} [${status}]`
|
|
15096
|
+
);
|
|
14881
15097
|
}
|
|
14882
15098
|
if (failedJobs.length > 0) {
|
|
14883
15099
|
lines.push("");
|
|
@@ -14888,7 +15104,9 @@ function registerCarouselTools(server) {
|
|
|
14888
15104
|
const jobIdList = successfulJobs.map((j) => j.jobId).join(", ");
|
|
14889
15105
|
lines.push("");
|
|
14890
15106
|
lines.push("Next steps:");
|
|
14891
|
-
lines.push(
|
|
15107
|
+
lines.push(
|
|
15108
|
+
` 1. Poll each job: check_status with job_id for each of: ${jobIdList}`
|
|
15109
|
+
);
|
|
14892
15110
|
lines.push(
|
|
14893
15111
|
" 2. When all complete: schedule_post with job_ids=[...] and media_type=CAROUSEL_ALBUM"
|
|
14894
15112
|
);
|
|
@@ -19225,8 +19443,14 @@ async function authenticateRequest(req, res, next) {
|
|
|
19225
19443
|
const scopeParam = req.query.scope;
|
|
19226
19444
|
if (scopeParam) {
|
|
19227
19445
|
const requestedScopes = scopeParam.split(",").map((s) => s.trim()).filter(Boolean);
|
|
19228
|
-
scopes = requestedScopes.filter((s) => authInfo.scopes
|
|
19229
|
-
if (scopes.length === 0)
|
|
19446
|
+
scopes = requestedScopes.filter((s) => hasScope(authInfo.scopes, s));
|
|
19447
|
+
if (scopes.length === 0) {
|
|
19448
|
+
res.status(403).json({
|
|
19449
|
+
error: "insufficient_scope",
|
|
19450
|
+
error_description: "None of the requested scopes are granted by this token."
|
|
19451
|
+
});
|
|
19452
|
+
return;
|
|
19453
|
+
}
|
|
19230
19454
|
}
|
|
19231
19455
|
req.auth = {
|
|
19232
19456
|
userId: authInfo.extra?.userId ?? authInfo.clientId,
|