@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/sn.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
|
|
|
@@ -3688,26 +3688,35 @@ var init_constants = __esm({
|
|
|
3688
3688
|
constants_default = {
|
|
3689
3689
|
ZERO_WIDTH_CHARS: "[\\u200B\\u200C\\u200D\\u2060\\uFEFF\\u{E0020}-\\u{E007F}]",
|
|
3690
3690
|
INSTRUCTION_PHRASES: [
|
|
3691
|
-
"(?:^|[^A-Za-z])ignore (?:all|previous|prior)
|
|
3692
|
-
"(?:^|[^A-Za-z])disregard (?:the
|
|
3693
|
-
"(?:^|[^A-Za-z])forget (?:all|previous|prior|
|
|
3691
|
+
"(?:^|[^A-Za-z])ignore (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
|
|
3692
|
+
"(?:^|[^A-Za-z])disregard (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*(?:instructions|prompts?|context)",
|
|
3693
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )*instructions",
|
|
3694
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:previous |prior |preceding |earlier |above )+(?:everything|context)",
|
|
3695
|
+
"(?:^|[^A-Za-z])forget (?:all |any |the )*(?:everything|context) (?:above|previously|prior|before|you (?:were|have been))",
|
|
3694
3696
|
"(?:^|[^A-Za-z])you are now (?:a |an )",
|
|
3695
3697
|
"(?:^|[^A-Za-z])system:\\s",
|
|
3696
3698
|
"<\\|im_start\\|>",
|
|
3697
3699
|
"<\\|im_end\\|>",
|
|
3698
3700
|
"</?im_(?:start|end)>",
|
|
3699
3701
|
"<\\|assistant\\|>",
|
|
3700
|
-
"(?:^|[^A-Za-z])\\[INST\\]"
|
|
3702
|
+
"(?:^|[^A-Za-z])\\[INST\\]",
|
|
3703
|
+
"(?:^|[^A-Za-z])disregard (?:the|all) (?:above|prior|previous)",
|
|
3704
|
+
"(?:^|[^A-Za-z])forget (?:all|previous|prior|the above)"
|
|
3701
3705
|
],
|
|
3702
3706
|
PII_PATTERNS: {
|
|
3703
3707
|
email: "[\\w.+-]+@[\\w-]+\\.[\\w.-]+",
|
|
3704
3708
|
phone_us: "(?<!\\d|-)(?:\\+?1[-\\s.])?\\(?\\d{3}\\)?[-\\s.]?\\d{3}[-\\s.]?\\d{4}(?!\\d|-)",
|
|
3705
3709
|
phone_intl: "\\+\\d{1,3}[-\\s.]?\\d{1,4}[-\\s.]?\\d{1,4}[-\\s.]?\\d{4,}",
|
|
3706
3710
|
jwt: "eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+",
|
|
3707
|
-
api_key: "(?:sk|pk|snk|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
|
|
3711
|
+
api_key: "(?:sk|pk|snk|sno|xoxb|ghp|github_pat)_[A-Za-z0-9_-]{16,}",
|
|
3708
3712
|
credit_card: "(?<!\\d|-)(?:\\d{4}[ -]?){3}\\d{4}(?!\\d|-)",
|
|
3709
3713
|
ssn: "\\d{3}-\\d{2}-\\d{4}",
|
|
3710
|
-
ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])"
|
|
3714
|
+
ip: "(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])",
|
|
3715
|
+
aws_access_key: "AKIA[0-9A-Z]{16}",
|
|
3716
|
+
google_api_key: "AIza[0-9A-Za-z_\\-]{35}",
|
|
3717
|
+
slack_token: "xox[abprs]-[A-Za-z0-9-]{10,}|xapp-[0-9]-[A-Za-z0-9-]{10,}",
|
|
3718
|
+
bearer_token: "Bearer\\s+[A-Za-z0-9._~+/=-]{20,}",
|
|
3719
|
+
private_key_block: "-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----"
|
|
3711
3720
|
},
|
|
3712
3721
|
MAX_LENGTH: 1e4,
|
|
3713
3722
|
MAX_OUTPUT_LENGTH: 1e6
|
|
@@ -3734,13 +3743,90 @@ function normalize(text) {
|
|
|
3734
3743
|
out = out.replace(EXCESSIVE_WHITESPACE, " ");
|
|
3735
3744
|
return out;
|
|
3736
3745
|
}
|
|
3737
|
-
|
|
3746
|
+
function skeleton(text) {
|
|
3747
|
+
if (typeof text !== "string") return "";
|
|
3748
|
+
return text.replace(CONFUSABLE_RE, (ch) => CONFUSABLES[ch] ?? ch);
|
|
3749
|
+
}
|
|
3750
|
+
var OVERRIDE_CHARS, HTML_COMMENT, EXCESSIVE_WHITESPACE, CONFUSABLES, CONFUSABLE_RE;
|
|
3738
3751
|
var init_normalize = __esm({
|
|
3739
3752
|
"src/lib/agent-harness/normalize.ts"() {
|
|
3740
3753
|
"use strict";
|
|
3741
3754
|
OVERRIDE_CHARS = /[--]/g;
|
|
3742
3755
|
HTML_COMMENT = /<!--[\s\S]*?-->/g;
|
|
3743
3756
|
EXCESSIVE_WHITESPACE = /\s{2,}/g;
|
|
3757
|
+
CONFUSABLES = {
|
|
3758
|
+
// Cyrillic, lowercase
|
|
3759
|
+
\u0430: "a",
|
|
3760
|
+
\u0432: "b",
|
|
3761
|
+
\u0435: "e",
|
|
3762
|
+
\u0455: "s",
|
|
3763
|
+
\u0456: "i",
|
|
3764
|
+
\u0458: "j",
|
|
3765
|
+
\u043A: "k",
|
|
3766
|
+
\u043C: "m",
|
|
3767
|
+
\u043D: "h",
|
|
3768
|
+
\u043E: "o",
|
|
3769
|
+
\u0440: "p",
|
|
3770
|
+
\u0441: "c",
|
|
3771
|
+
\u0442: "t",
|
|
3772
|
+
\u0443: "y",
|
|
3773
|
+
\u0445: "x",
|
|
3774
|
+
"\u0501": "d",
|
|
3775
|
+
\u0451: "e",
|
|
3776
|
+
\u04BB: "h",
|
|
3777
|
+
\u0475: "v",
|
|
3778
|
+
"\u051B": "q",
|
|
3779
|
+
\u0461: "w",
|
|
3780
|
+
"\u04CF": "l",
|
|
3781
|
+
"\u051D": "w",
|
|
3782
|
+
// Cyrillic, uppercase
|
|
3783
|
+
\u0410: "A",
|
|
3784
|
+
\u0412: "B",
|
|
3785
|
+
\u0415: "E",
|
|
3786
|
+
\u0405: "S",
|
|
3787
|
+
\u0406: "I",
|
|
3788
|
+
\u0408: "J",
|
|
3789
|
+
\u041A: "K",
|
|
3790
|
+
\u041C: "M",
|
|
3791
|
+
\u041D: "H",
|
|
3792
|
+
\u041E: "O",
|
|
3793
|
+
\u0420: "P",
|
|
3794
|
+
\u0421: "C",
|
|
3795
|
+
\u0422: "T",
|
|
3796
|
+
\u0423: "Y",
|
|
3797
|
+
\u0425: "X",
|
|
3798
|
+
// Greek, lowercase
|
|
3799
|
+
\u03B1: "a",
|
|
3800
|
+
\u03B2: "b",
|
|
3801
|
+
\u03B5: "e",
|
|
3802
|
+
\u03B9: "i",
|
|
3803
|
+
\u03BA: "k",
|
|
3804
|
+
\u03BD: "v",
|
|
3805
|
+
\u03BF: "o",
|
|
3806
|
+
\u03C1: "p",
|
|
3807
|
+
\u03C4: "t",
|
|
3808
|
+
\u03C5: "u",
|
|
3809
|
+
\u03C7: "x",
|
|
3810
|
+
\u03F2: "c",
|
|
3811
|
+
\u03B3: "y",
|
|
3812
|
+
\u03B7: "n",
|
|
3813
|
+
// Greek, uppercase
|
|
3814
|
+
\u0391: "A",
|
|
3815
|
+
\u0392: "B",
|
|
3816
|
+
\u0395: "E",
|
|
3817
|
+
\u0396: "Z",
|
|
3818
|
+
\u0397: "H",
|
|
3819
|
+
\u0399: "I",
|
|
3820
|
+
\u039A: "K",
|
|
3821
|
+
\u039C: "M",
|
|
3822
|
+
\u039D: "N",
|
|
3823
|
+
\u039F: "O",
|
|
3824
|
+
\u03A1: "P",
|
|
3825
|
+
\u03A4: "T",
|
|
3826
|
+
\u03A5: "Y",
|
|
3827
|
+
\u03A7: "X"
|
|
3828
|
+
};
|
|
3829
|
+
CONFUSABLE_RE = new RegExp(`[${Object.keys(CONFUSABLES).join("")}]`, "g");
|
|
3744
3830
|
}
|
|
3745
3831
|
});
|
|
3746
3832
|
|
|
@@ -3832,6 +3918,14 @@ function scan(text, options) {
|
|
|
3832
3918
|
flagged.add(ipNorm.pattern);
|
|
3833
3919
|
risk = Math.max(risk, 0.9);
|
|
3834
3920
|
}
|
|
3921
|
+
const skeletonText = skeleton(normalized);
|
|
3922
|
+
if (skeletonText !== normalized) {
|
|
3923
|
+
const ipSkel = detectInstructionPhrase(skeletonText);
|
|
3924
|
+
if (ipSkel.found) {
|
|
3925
|
+
flagged.add(ipSkel.pattern);
|
|
3926
|
+
risk = Math.max(risk, 0.9);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3835
3929
|
const flaggedArr = Array.from(flagged);
|
|
3836
3930
|
if ((options.mode === "block" || options.mode === "sanitize") && flaggedArr.length > 0) {
|
|
3837
3931
|
return { passed: false, risk_score: risk, flagged_patterns: flaggedArr, pii_redacted: false };
|
|
@@ -4347,6 +4441,92 @@ var init_ideation = __esm({
|
|
|
4347
4441
|
}
|
|
4348
4442
|
});
|
|
4349
4443
|
|
|
4444
|
+
// src/lib/sanitize-error.ts
|
|
4445
|
+
function redactSensitiveIdentifiers(message) {
|
|
4446
|
+
return message.replace(EMAIL_PATTERN, "[redacted-email]").replace(UUID_PATTERN, "[redacted-id]");
|
|
4447
|
+
}
|
|
4448
|
+
function sanitizeError(error) {
|
|
4449
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
4450
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
4451
|
+
if (pattern.test(msg)) {
|
|
4452
|
+
return userMessage;
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
return "An unexpected error occurred. Please try again.";
|
|
4456
|
+
}
|
|
4457
|
+
var ERROR_PATTERNS, UUID_PATTERN, EMAIL_PATTERN;
|
|
4458
|
+
var init_sanitize_error = __esm({
|
|
4459
|
+
"src/lib/sanitize-error.ts"() {
|
|
4460
|
+
"use strict";
|
|
4461
|
+
ERROR_PATTERNS = [
|
|
4462
|
+
// Postgres / PostgREST
|
|
4463
|
+
[
|
|
4464
|
+
/PGRST301|permission denied/i,
|
|
4465
|
+
"Access denied. Check your account permissions."
|
|
4466
|
+
],
|
|
4467
|
+
[
|
|
4468
|
+
/42P01|does not exist/i,
|
|
4469
|
+
"Service temporarily unavailable. Please try again."
|
|
4470
|
+
],
|
|
4471
|
+
[
|
|
4472
|
+
/23505|unique.*constraint|duplicate key/i,
|
|
4473
|
+
"A duplicate record already exists."
|
|
4474
|
+
],
|
|
4475
|
+
[/23503|foreign key/i, "Referenced record not found."],
|
|
4476
|
+
// Gemini / Google AI
|
|
4477
|
+
[
|
|
4478
|
+
/google.*api.*key|googleapis\.com.*40[13]/i,
|
|
4479
|
+
"Content generation failed. Please try again."
|
|
4480
|
+
],
|
|
4481
|
+
[
|
|
4482
|
+
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
4483
|
+
"AI service rate limit reached. Please wait and retry."
|
|
4484
|
+
],
|
|
4485
|
+
[
|
|
4486
|
+
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
4487
|
+
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
4488
|
+
],
|
|
4489
|
+
[
|
|
4490
|
+
/gemini.*error|generativelanguage/i,
|
|
4491
|
+
"Content generation failed. Please try again."
|
|
4492
|
+
],
|
|
4493
|
+
// Kie.ai
|
|
4494
|
+
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
4495
|
+
// Stripe
|
|
4496
|
+
[
|
|
4497
|
+
/stripe.*api|sk_live_|sk_test_/i,
|
|
4498
|
+
"Payment processing error. Please try again."
|
|
4499
|
+
],
|
|
4500
|
+
// Network / fetch
|
|
4501
|
+
[
|
|
4502
|
+
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
4503
|
+
"External service unavailable. Please try again."
|
|
4504
|
+
],
|
|
4505
|
+
[
|
|
4506
|
+
/fetch failed|network error|abort.*timeout/i,
|
|
4507
|
+
"Network request failed. Please try again."
|
|
4508
|
+
],
|
|
4509
|
+
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
4510
|
+
// Supabase Edge Function internals
|
|
4511
|
+
[
|
|
4512
|
+
/FunctionsHttpError|non-2xx status/i,
|
|
4513
|
+
"Backend service error. Please try again."
|
|
4514
|
+
],
|
|
4515
|
+
[
|
|
4516
|
+
/JWT|token.*expired|token.*invalid/i,
|
|
4517
|
+
"Authentication expired. Please re-authenticate."
|
|
4518
|
+
],
|
|
4519
|
+
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
4520
|
+
[
|
|
4521
|
+
/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i,
|
|
4522
|
+
"An internal error occurred. Please try again."
|
|
4523
|
+
]
|
|
4524
|
+
];
|
|
4525
|
+
UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
4526
|
+
EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
4527
|
+
}
|
|
4528
|
+
});
|
|
4529
|
+
|
|
4350
4530
|
// src/lib/checkStatusShape.ts
|
|
4351
4531
|
function buildCheckStatusPayload(job, liveStatus) {
|
|
4352
4532
|
const status = liveStatus?.status ?? job.status;
|
|
@@ -4442,9 +4622,22 @@ function checkCreditBudget(estimatedCost) {
|
|
|
4442
4622
|
}
|
|
4443
4623
|
const used = getCreditsUsed();
|
|
4444
4624
|
if (used + estimatedCost > MAX_CREDITS_PER_RUN) {
|
|
4625
|
+
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.`;
|
|
4445
4626
|
return {
|
|
4446
4627
|
ok: false,
|
|
4447
|
-
message
|
|
4628
|
+
message,
|
|
4629
|
+
error: toolError("billing_error", message, {
|
|
4630
|
+
recover_with: [
|
|
4631
|
+
"Raise SOCIALNEURON_MAX_CREDITS_PER_RUN to a higher credit ceiling.",
|
|
4632
|
+
"Unset SOCIALNEURON_MAX_CREDITS_PER_RUN to remove the per-run cap."
|
|
4633
|
+
],
|
|
4634
|
+
details: {
|
|
4635
|
+
env_var: "SOCIALNEURON_MAX_CREDITS_PER_RUN",
|
|
4636
|
+
credits_used_this_run: used,
|
|
4637
|
+
estimated_call_cost: estimatedCost,
|
|
4638
|
+
max_credits_per_run: MAX_CREDITS_PER_RUN
|
|
4639
|
+
}
|
|
4640
|
+
})
|
|
4448
4641
|
};
|
|
4449
4642
|
}
|
|
4450
4643
|
return { ok: true };
|
|
@@ -4467,8 +4660,15 @@ var init_budget = __esm({
|
|
|
4467
4660
|
"src/lib/budget.ts"() {
|
|
4468
4661
|
"use strict";
|
|
4469
4662
|
init_request_context();
|
|
4470
|
-
|
|
4471
|
-
|
|
4663
|
+
init_tool_error();
|
|
4664
|
+
MAX_CREDITS_PER_RUN = Math.max(
|
|
4665
|
+
0,
|
|
4666
|
+
Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0)
|
|
4667
|
+
);
|
|
4668
|
+
MAX_ASSETS_PER_RUN = Math.max(
|
|
4669
|
+
0,
|
|
4670
|
+
Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0)
|
|
4671
|
+
);
|
|
4472
4672
|
_globalCreditsUsed = 0;
|
|
4473
4673
|
_globalAssetsGenerated = 0;
|
|
4474
4674
|
}
|
|
@@ -4495,7 +4695,7 @@ function asEnvelope2(data) {
|
|
|
4495
4695
|
function registerContentTools(server) {
|
|
4496
4696
|
server.tool(
|
|
4497
4697
|
"generate_video",
|
|
4498
|
-
"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
|
|
4698
|
+
"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.",
|
|
4499
4699
|
{
|
|
4500
4700
|
prompt: z2.string().max(2500).describe(
|
|
4501
4701
|
'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.'
|
|
@@ -4510,7 +4710,7 @@ function registerContentTools(server) {
|
|
|
4510
4710
|
"Video aspect ratio. 16:9 for YouTube/landscape, 9:16 for TikTok/Reels/Shorts, 1:1 for Instagram feed/square. Defaults to 16:9."
|
|
4511
4711
|
),
|
|
4512
4712
|
enable_audio: z2.boolean().optional().describe(
|
|
4513
|
-
"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).
|
|
4713
|
+
"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."
|
|
4514
4714
|
),
|
|
4515
4715
|
image_url: z2.string().optional().describe(
|
|
4516
4716
|
"Start frame image URL for image-to-video (Kling 3.0 frame control)."
|
|
@@ -4546,10 +4746,7 @@ function registerContentTools(server) {
|
|
|
4546
4746
|
const estimatedCost = VIDEO_CREDIT_ESTIMATES[model] ?? 120;
|
|
4547
4747
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
4548
4748
|
if (!budgetCheck.ok) {
|
|
4549
|
-
return
|
|
4550
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
4551
|
-
isError: true
|
|
4552
|
-
};
|
|
4749
|
+
return budgetCheck.error;
|
|
4553
4750
|
}
|
|
4554
4751
|
const rateLimit = checkRateLimit(
|
|
4555
4752
|
"generation",
|
|
@@ -4573,9 +4770,12 @@ function registerContentTools(server) {
|
|
|
4573
4770
|
model,
|
|
4574
4771
|
duration: duration ?? 5,
|
|
4575
4772
|
aspectRatio: aspect_ratio ?? "16:9",
|
|
4576
|
-
// Default FALSE (2026-07-13): the old `?? true` default silently
|
|
4577
|
-
// kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
4578
|
-
|
|
4773
|
+
// Default FALSE for most models (2026-07-13): the old `?? true` default silently
|
|
4774
|
+
// multiplied kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
4775
|
+
// for audio. Exception (2026-07-17): the seedance-2 family bills audio into its base
|
|
4776
|
+
// cost and generates it natively, so a false default there shipped silent no-audio
|
|
4777
|
+
// mp4s — those models default TRUE when the caller omits enable_audio.
|
|
4778
|
+
enableAudio: enable_audio ?? AUDIO_NATIVE_DEFAULT_MODELS.has(model),
|
|
4579
4779
|
...image_url && { imageUrl: image_url },
|
|
4580
4780
|
...end_frame_url && { endFrameUrl: end_frame_url },
|
|
4581
4781
|
// The server reads projectId and enforces
|
|
@@ -4697,10 +4897,7 @@ function registerContentTools(server) {
|
|
|
4697
4897
|
const estimatedCost = IMAGE_CREDIT_ESTIMATES[model] ?? 30;
|
|
4698
4898
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
4699
4899
|
if (!budgetCheck.ok) {
|
|
4700
|
-
return
|
|
4701
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
4702
|
-
isError: true
|
|
4703
|
-
};
|
|
4900
|
+
return budgetCheck.error;
|
|
4704
4901
|
}
|
|
4705
4902
|
const rateLimit = checkRateLimit(
|
|
4706
4903
|
"generation",
|
|
@@ -4945,7 +5142,7 @@ function registerContentTools(server) {
|
|
|
4945
5142
|
);
|
|
4946
5143
|
}
|
|
4947
5144
|
if (job.error_message) {
|
|
4948
|
-
lines.push(`Error: ${job.error_message}`);
|
|
5145
|
+
lines.push(`Error: ${redactSensitiveIdentifiers(job.error_message)}`);
|
|
4949
5146
|
}
|
|
4950
5147
|
const fallbackDisclosure = buildFallbackDisclosureLine(
|
|
4951
5148
|
job.result_metadata
|
|
@@ -5099,10 +5296,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5099
5296
|
const estimatedCost = 10;
|
|
5100
5297
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
5101
5298
|
if (!budgetCheck.ok) {
|
|
5102
|
-
return
|
|
5103
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
5104
|
-
isError: true
|
|
5105
|
-
};
|
|
5299
|
+
return budgetCheck.error;
|
|
5106
5300
|
}
|
|
5107
5301
|
const { data, error } = await callEdgeFunction(
|
|
5108
5302
|
"social-neuron-ai",
|
|
@@ -5189,10 +5383,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5189
5383
|
const estimatedCost = 15;
|
|
5190
5384
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
5191
5385
|
if (!budgetCheck.ok) {
|
|
5192
|
-
return
|
|
5193
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
5194
|
-
isError: true
|
|
5195
|
-
};
|
|
5386
|
+
return budgetCheck.error;
|
|
5196
5387
|
}
|
|
5197
5388
|
const rateLimit = checkRateLimit(
|
|
5198
5389
|
"generation",
|
|
@@ -5355,10 +5546,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5355
5546
|
const estimatedCost = 10 + slideCount * 2;
|
|
5356
5547
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
5357
5548
|
if (!budgetCheck.ok) {
|
|
5358
|
-
return
|
|
5359
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
5360
|
-
isError: true
|
|
5361
|
-
};
|
|
5549
|
+
return budgetCheck.error;
|
|
5362
5550
|
}
|
|
5363
5551
|
const userId = await getDefaultUserId();
|
|
5364
5552
|
const rateLimit = checkRateLimit(
|
|
@@ -5461,13 +5649,14 @@ Return ONLY valid JSON in this exact format:
|
|
|
5461
5649
|
}
|
|
5462
5650
|
);
|
|
5463
5651
|
}
|
|
5464
|
-
var VIDEO_CREDIT_ESTIMATES, VIDEO_MODEL_ENUM, IMAGE_CREDIT_ESTIMATES;
|
|
5652
|
+
var VIDEO_CREDIT_ESTIMATES, AUDIO_NATIVE_DEFAULT_MODELS, VIDEO_MODEL_ENUM, IMAGE_CREDIT_ESTIMATES;
|
|
5465
5653
|
var init_content2 = __esm({
|
|
5466
5654
|
"src/tools/content.ts"() {
|
|
5467
5655
|
"use strict";
|
|
5468
5656
|
init_edge_function();
|
|
5469
5657
|
init_rate_limit();
|
|
5470
5658
|
init_supabase();
|
|
5659
|
+
init_sanitize_error();
|
|
5471
5660
|
init_version();
|
|
5472
5661
|
init_checkStatusShape();
|
|
5473
5662
|
init_budget();
|
|
@@ -5485,6 +5674,10 @@ var init_content2 = __esm({
|
|
|
5485
5674
|
"seedance-1.5-pro": 150,
|
|
5486
5675
|
kling: 170
|
|
5487
5676
|
};
|
|
5677
|
+
AUDIO_NATIVE_DEFAULT_MODELS = /* @__PURE__ */ new Set([
|
|
5678
|
+
"seedance-2-fast",
|
|
5679
|
+
"seedance-2"
|
|
5680
|
+
]);
|
|
5488
5681
|
VIDEO_MODEL_ENUM = [
|
|
5489
5682
|
"seedance-2-fast",
|
|
5490
5683
|
"kling-3",
|
|
@@ -5513,62 +5706,24 @@ var init_content2 = __esm({
|
|
|
5513
5706
|
}
|
|
5514
5707
|
});
|
|
5515
5708
|
|
|
5516
|
-
// src/lib/sanitize-error.ts
|
|
5517
|
-
function sanitizeError(error) {
|
|
5518
|
-
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
5519
|
-
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
5520
|
-
if (pattern.test(msg)) {
|
|
5521
|
-
return userMessage;
|
|
5522
|
-
}
|
|
5523
|
-
}
|
|
5524
|
-
return "An unexpected error occurred. Please try again.";
|
|
5525
|
-
}
|
|
5526
|
-
var ERROR_PATTERNS;
|
|
5527
|
-
var init_sanitize_error = __esm({
|
|
5528
|
-
"src/lib/sanitize-error.ts"() {
|
|
5529
|
-
"use strict";
|
|
5530
|
-
ERROR_PATTERNS = [
|
|
5531
|
-
// Postgres / PostgREST
|
|
5532
|
-
[/PGRST301|permission denied/i, "Access denied. Check your account permissions."],
|
|
5533
|
-
[/42P01|does not exist/i, "Service temporarily unavailable. Please try again."],
|
|
5534
|
-
[/23505|unique.*constraint|duplicate key/i, "A duplicate record already exists."],
|
|
5535
|
-
[/23503|foreign key/i, "Referenced record not found."],
|
|
5536
|
-
// Gemini / Google AI
|
|
5537
|
-
[/google.*api.*key|googleapis\.com.*40[13]/i, "Content generation failed. Please try again."],
|
|
5538
|
-
[
|
|
5539
|
-
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
5540
|
-
"AI service rate limit reached. Please wait and retry."
|
|
5541
|
-
],
|
|
5542
|
-
[
|
|
5543
|
-
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
5544
|
-
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
5545
|
-
],
|
|
5546
|
-
[/gemini.*error|generativelanguage/i, "Content generation failed. Please try again."],
|
|
5547
|
-
// Kie.ai
|
|
5548
|
-
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
5549
|
-
// Stripe
|
|
5550
|
-
[/stripe.*api|sk_live_|sk_test_/i, "Payment processing error. Please try again."],
|
|
5551
|
-
// Network / fetch
|
|
5552
|
-
[
|
|
5553
|
-
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
5554
|
-
"External service unavailable. Please try again."
|
|
5555
|
-
],
|
|
5556
|
-
[/fetch failed|network error|abort.*timeout/i, "Network request failed. Please try again."],
|
|
5557
|
-
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
5558
|
-
// Supabase Edge Function internals
|
|
5559
|
-
[/FunctionsHttpError|non-2xx status/i, "Backend service error. Please try again."],
|
|
5560
|
-
[/JWT|token.*expired|token.*invalid/i, "Authentication expired. Please re-authenticate."],
|
|
5561
|
-
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
5562
|
-
[/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i, "An internal error occurred. Please try again."]
|
|
5563
|
-
];
|
|
5564
|
-
}
|
|
5565
|
-
});
|
|
5566
|
-
|
|
5567
5709
|
// src/lib/ssrf.ts
|
|
5568
5710
|
import { promises as dnsPromises } from "node:dns";
|
|
5711
|
+
function expandIPv4Mapped(ip) {
|
|
5712
|
+
const dotted = /^::ffff:((?:\d{1,3}\.){3}\d{1,3})$/i.exec(ip);
|
|
5713
|
+
if (dotted) return dotted[1];
|
|
5714
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(ip);
|
|
5715
|
+
if (hex) {
|
|
5716
|
+
const high = parseInt(hex[1], 16);
|
|
5717
|
+
const low = parseInt(hex[2], 16);
|
|
5718
|
+
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
5719
|
+
}
|
|
5720
|
+
return null;
|
|
5721
|
+
}
|
|
5569
5722
|
function isBlockedIP(ip) {
|
|
5570
5723
|
const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
|
|
5571
5724
|
if (normalized.includes(":")) {
|
|
5725
|
+
const mapped = expandIPv4Mapped(normalized);
|
|
5726
|
+
if (mapped) return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(mapped));
|
|
5572
5727
|
return BLOCKED_IPV6_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
5573
5728
|
}
|
|
5574
5729
|
return BLOCKED_IP_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
@@ -13397,6 +13552,14 @@ var init_plan_approvals = __esm({
|
|
|
13397
13552
|
|
|
13398
13553
|
// src/tools/discovery.ts
|
|
13399
13554
|
import { z as z23 } from "zod";
|
|
13555
|
+
function isHostedTransport() {
|
|
13556
|
+
return process.env.MCP_TRANSPORT === "http";
|
|
13557
|
+
}
|
|
13558
|
+
function isPubliclyDiscoverable(tool) {
|
|
13559
|
+
if (tool.internal || tool.hiddenFromPublicCount) return false;
|
|
13560
|
+
if (tool.localOnly && isHostedTransport()) return false;
|
|
13561
|
+
return true;
|
|
13562
|
+
}
|
|
13400
13563
|
function toolKnowledgeDocument(tool) {
|
|
13401
13564
|
const lines = [
|
|
13402
13565
|
`Tool: ${tool.name}`,
|
|
@@ -13407,7 +13570,8 @@ function toolKnowledgeDocument(tool) {
|
|
|
13407
13570
|
if (tool.task_intent) lines.push(`Task intent: ${tool.task_intent}`);
|
|
13408
13571
|
if (tool.use_when) lines.push(`Use when: ${tool.use_when}`);
|
|
13409
13572
|
if (tool.avoid_when) lines.push(`Avoid when: ${tool.avoid_when}`);
|
|
13410
|
-
if (tool.next_tools?.length)
|
|
13573
|
+
if (tool.next_tools?.length)
|
|
13574
|
+
lines.push(`Common next tools: ${tool.next_tools.join(", ")}`);
|
|
13411
13575
|
return {
|
|
13412
13576
|
id: `tool:${tool.name}`,
|
|
13413
13577
|
title: `MCP tool: ${tool.name}`,
|
|
@@ -13424,9 +13588,7 @@ function toolKnowledgeDocument(tool) {
|
|
|
13424
13588
|
function getKnowledgeDocuments() {
|
|
13425
13589
|
return [
|
|
13426
13590
|
...STATIC_KNOWLEDGE_DOCUMENTS,
|
|
13427
|
-
...TOOL_CATALOG.filter(
|
|
13428
|
-
toolKnowledgeDocument
|
|
13429
|
-
)
|
|
13591
|
+
...TOOL_CATALOG.filter(isPubliclyDiscoverable).map(toolKnowledgeDocument)
|
|
13430
13592
|
];
|
|
13431
13593
|
}
|
|
13432
13594
|
function tokenize(input) {
|
|
@@ -13480,7 +13642,9 @@ function registerDiscoveryTools(server) {
|
|
|
13480
13642
|
};
|
|
13481
13643
|
return {
|
|
13482
13644
|
structuredContent,
|
|
13483
|
-
content: [
|
|
13645
|
+
content: [
|
|
13646
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
13647
|
+
]
|
|
13484
13648
|
};
|
|
13485
13649
|
}
|
|
13486
13650
|
);
|
|
@@ -13501,10 +13665,14 @@ function registerDiscoveryTools(server) {
|
|
|
13501
13665
|
}
|
|
13502
13666
|
},
|
|
13503
13667
|
async ({ id }) => {
|
|
13504
|
-
const doc = getKnowledgeDocuments().find(
|
|
13668
|
+
const doc = getKnowledgeDocuments().find(
|
|
13669
|
+
(candidate) => candidate.id === id
|
|
13670
|
+
);
|
|
13505
13671
|
if (!doc) {
|
|
13506
13672
|
return {
|
|
13507
|
-
content: [
|
|
13673
|
+
content: [
|
|
13674
|
+
{ type: "text", text: `Document not found: ${id}` }
|
|
13675
|
+
],
|
|
13508
13676
|
isError: true
|
|
13509
13677
|
};
|
|
13510
13678
|
}
|
|
@@ -13517,7 +13685,9 @@ function registerDiscoveryTools(server) {
|
|
|
13517
13685
|
};
|
|
13518
13686
|
return {
|
|
13519
13687
|
structuredContent,
|
|
13520
|
-
content: [
|
|
13688
|
+
content: [
|
|
13689
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
13690
|
+
]
|
|
13521
13691
|
};
|
|
13522
13692
|
}
|
|
13523
13693
|
);
|
|
@@ -13526,7 +13696,9 @@ function registerDiscoveryTools(server) {
|
|
|
13526
13696
|
'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.',
|
|
13527
13697
|
{
|
|
13528
13698
|
query: z23.string().optional().describe("Search query to filter tools by name or description"),
|
|
13529
|
-
module: z23.string().optional().describe(
|
|
13699
|
+
module: z23.string().optional().describe(
|
|
13700
|
+
'Filter by module name (e.g. "planning", "content", "analytics")'
|
|
13701
|
+
),
|
|
13530
13702
|
scope: z23.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
13531
13703
|
detail: z23.enum(["name", "summary", "full"]).default("summary").describe(
|
|
13532
13704
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
@@ -13543,14 +13715,18 @@ function registerDiscoveryTools(server) {
|
|
|
13543
13715
|
if (query) {
|
|
13544
13716
|
results = searchTools(query);
|
|
13545
13717
|
}
|
|
13546
|
-
results = results.filter(
|
|
13718
|
+
results = results.filter(isPubliclyDiscoverable);
|
|
13547
13719
|
if (module) {
|
|
13548
13720
|
const moduleTools = getToolsByModule(module);
|
|
13549
|
-
results = results.filter(
|
|
13721
|
+
results = results.filter(
|
|
13722
|
+
(t) => moduleTools.some((mt) => mt.name === t.name)
|
|
13723
|
+
);
|
|
13550
13724
|
}
|
|
13551
13725
|
if (scope) {
|
|
13552
13726
|
const scopeTools = getToolsByScope(scope);
|
|
13553
|
-
results = results.filter(
|
|
13727
|
+
results = results.filter(
|
|
13728
|
+
(t) => scopeTools.some((st) => st.name === t.name)
|
|
13729
|
+
);
|
|
13554
13730
|
}
|
|
13555
13731
|
if (available_only) {
|
|
13556
13732
|
results = results.filter(isAvailable);
|
|
@@ -13588,7 +13764,12 @@ function registerDiscoveryTools(server) {
|
|
|
13588
13764
|
text: JSON.stringify(
|
|
13589
13765
|
{
|
|
13590
13766
|
toolCount: results.length,
|
|
13591
|
-
...hasKnownScopes ? {
|
|
13767
|
+
...hasKnownScopes ? {
|
|
13768
|
+
scopes: {
|
|
13769
|
+
available: currentScopes,
|
|
13770
|
+
unavailable_matches: unavailableCount
|
|
13771
|
+
}
|
|
13772
|
+
} : {},
|
|
13592
13773
|
tools: output
|
|
13593
13774
|
},
|
|
13594
13775
|
null,
|
|
@@ -16179,25 +16360,39 @@ function registerCarouselTools(server) {
|
|
|
16179
16360
|
]).optional().describe("Carousel template. Default: hormozi-authority."),
|
|
16180
16361
|
slide_count: z28.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
16181
16362
|
aspect_ratio: z28.enum(["1:1", "4:5", "9:16"]).optional().describe("Aspect ratio for both carousel and images. Default: 1:1."),
|
|
16182
|
-
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
16363
|
+
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
16364
|
+
"Visual style. Default: hormozi for hormozi-authority template."
|
|
16365
|
+
),
|
|
16183
16366
|
image_style_suffix: z28.string().max(500).optional().describe(
|
|
16184
16367
|
'Style suffix appended to every image prompt for visual consistency across slides. Example: "dark moody lighting, cinematic, 35mm film grain".'
|
|
16185
16368
|
),
|
|
16186
16369
|
hook: z28.string().max(300).optional().describe(
|
|
16187
16370
|
"Explicit hook/opener for slide 1. Overrides any hook derived from topic. Keep under 15 words for strongest scroll-stop."
|
|
16188
16371
|
),
|
|
16189
|
-
hook_family: z28.enum([
|
|
16372
|
+
hook_family: z28.enum([
|
|
16373
|
+
"curiosity",
|
|
16374
|
+
"authority",
|
|
16375
|
+
"pain_point",
|
|
16376
|
+
"contrarian",
|
|
16377
|
+
"data_driven"
|
|
16378
|
+
]).optional().describe(
|
|
16190
16379
|
"Hook family tag. Recorded on the carousel so downstream analytics can attribute engagement to hook pattern."
|
|
16191
16380
|
),
|
|
16192
|
-
cta_text: z28.string().max(200).optional().describe(
|
|
16193
|
-
|
|
16381
|
+
cta_text: z28.string().max(200).optional().describe(
|
|
16382
|
+
"Explicit CTA copy for the final slide. If omitted, derived from topic."
|
|
16383
|
+
),
|
|
16384
|
+
cta_url: z28.string().url().optional().describe(
|
|
16385
|
+
"URL promoted on the CTA slide. Defaults to the project landing page."
|
|
16386
|
+
),
|
|
16194
16387
|
tone: z28.string().max(200).optional().describe(
|
|
16195
16388
|
'Voice/tone override. Composes with the brand profile voice when present. Example: "educational, confident, not arrogant".'
|
|
16196
16389
|
),
|
|
16197
16390
|
constraints: z28.string().max(500).optional().describe(
|
|
16198
16391
|
'Content constraints applied at generation time. Example: "No fabricated statistics. Sentence case only. No ALL CAPS."'
|
|
16199
16392
|
),
|
|
16200
|
-
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
16393
|
+
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
16394
|
+
"Target platform. Affects tone conventions and slide-count guardrails."
|
|
16395
|
+
),
|
|
16201
16396
|
brand_id: z28.string().optional().describe(
|
|
16202
16397
|
"Brand/project ID to pull visual context from (colors, logo, mood). Falls back to project_id, then default project."
|
|
16203
16398
|
),
|
|
@@ -16229,7 +16424,9 @@ function registerCarouselTools(server) {
|
|
|
16229
16424
|
const slideCount = slide_count ?? 7;
|
|
16230
16425
|
const ratio = aspect_ratio ?? "1:1";
|
|
16231
16426
|
let brandContext = null;
|
|
16232
|
-
const
|
|
16427
|
+
const defaultProjectId = await getDefaultProjectId();
|
|
16428
|
+
const brandProjectId = brand_id || project_id || defaultProjectId;
|
|
16429
|
+
const resolvedProjectId = project_id || defaultProjectId;
|
|
16233
16430
|
if (brandProjectId) {
|
|
16234
16431
|
brandContext = await fetchBrandVisualContext(brandProjectId);
|
|
16235
16432
|
}
|
|
@@ -16238,10 +16435,7 @@ function registerCarouselTools(server) {
|
|
|
16238
16435
|
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
16239
16436
|
const budgetCheck = checkCreditBudget(totalEstimatedCost);
|
|
16240
16437
|
if (!budgetCheck.ok) {
|
|
16241
|
-
return
|
|
16242
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
16243
|
-
isError: true
|
|
16244
|
-
};
|
|
16438
|
+
return budgetCheck.error;
|
|
16245
16439
|
}
|
|
16246
16440
|
const assetBudget = checkAssetBudget(slideCount);
|
|
16247
16441
|
if (!assetBudget.ok) {
|
|
@@ -16251,7 +16445,10 @@ function registerCarouselTools(server) {
|
|
|
16251
16445
|
};
|
|
16252
16446
|
}
|
|
16253
16447
|
const userId = await getDefaultUserId();
|
|
16254
|
-
const rateLimit = checkRateLimit(
|
|
16448
|
+
const rateLimit = checkRateLimit(
|
|
16449
|
+
"generation",
|
|
16450
|
+
`create_carousel:${userId}`
|
|
16451
|
+
);
|
|
16255
16452
|
if (!rateLimit.allowed) {
|
|
16256
16453
|
return {
|
|
16257
16454
|
content: [
|
|
@@ -16271,7 +16468,7 @@ function registerCarouselTools(server) {
|
|
|
16271
16468
|
slideCount,
|
|
16272
16469
|
aspectRatio: ratio,
|
|
16273
16470
|
style: resolvedStyle,
|
|
16274
|
-
projectId:
|
|
16471
|
+
projectId: resolvedProjectId,
|
|
16275
16472
|
hook,
|
|
16276
16473
|
hookFamily: hook_family,
|
|
16277
16474
|
ctaText: cta_text,
|
|
@@ -16285,7 +16482,12 @@ function registerCarouselTools(server) {
|
|
|
16285
16482
|
if (carouselError || !carouselData?.carousel) {
|
|
16286
16483
|
const errMsg = carouselError ?? "No carousel data returned";
|
|
16287
16484
|
return {
|
|
16288
|
-
content: [
|
|
16485
|
+
content: [
|
|
16486
|
+
{
|
|
16487
|
+
type: "text",
|
|
16488
|
+
text: `Carousel text generation failed: ${errMsg}`
|
|
16489
|
+
}
|
|
16490
|
+
],
|
|
16289
16491
|
isError: true
|
|
16290
16492
|
};
|
|
16291
16493
|
}
|
|
@@ -16314,7 +16516,8 @@ function registerCarouselTools(server) {
|
|
|
16314
16516
|
{
|
|
16315
16517
|
prompt: imagePrompt,
|
|
16316
16518
|
model: image_model,
|
|
16317
|
-
aspectRatio: ratio
|
|
16519
|
+
aspectRatio: ratio,
|
|
16520
|
+
...resolvedProjectId && { projectId: resolvedProjectId }
|
|
16318
16521
|
},
|
|
16319
16522
|
{ timeoutMs: 3e4 }
|
|
16320
16523
|
);
|
|
@@ -16371,14 +16574,19 @@ function registerCarouselTools(server) {
|
|
|
16371
16574
|
type: "text",
|
|
16372
16575
|
text: JSON.stringify(
|
|
16373
16576
|
{
|
|
16374
|
-
_meta: {
|
|
16577
|
+
_meta: {
|
|
16578
|
+
version: MCP_VERSION,
|
|
16579
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16580
|
+
},
|
|
16375
16581
|
data: {
|
|
16376
16582
|
carouselId: carousel.id,
|
|
16377
16583
|
templateId,
|
|
16378
16584
|
style: resolvedStyle,
|
|
16379
16585
|
slideCount: carousel.slides.length,
|
|
16380
16586
|
slides: carousel.slides.map((s) => {
|
|
16381
|
-
const job = imageJobs.find(
|
|
16587
|
+
const job = imageJobs.find(
|
|
16588
|
+
(j) => j.slideNumber === s.slideNumber
|
|
16589
|
+
);
|
|
16382
16590
|
return {
|
|
16383
16591
|
...s,
|
|
16384
16592
|
imageJobId: job?.jobId ?? null,
|
|
@@ -16405,9 +16613,17 @@ function registerCarouselTools(server) {
|
|
|
16405
16613
|
credits: {
|
|
16406
16614
|
textGeneration: textCredits,
|
|
16407
16615
|
imagesEstimated: successfulJobs.length * perImageCost,
|
|
16408
|
-
imagesCharged: imageJobs.reduce(
|
|
16409
|
-
|
|
16410
|
-
|
|
16616
|
+
imagesCharged: imageJobs.reduce(
|
|
16617
|
+
(sum, job) => sum + (job.creditsCharged ?? 0),
|
|
16618
|
+
0
|
|
16619
|
+
),
|
|
16620
|
+
imagesRefunded: imageJobs.reduce(
|
|
16621
|
+
(sum, job) => sum + (job.creditsRefunded ?? 0),
|
|
16622
|
+
0
|
|
16623
|
+
),
|
|
16624
|
+
billingUnknownSlides: imageJobs.filter(
|
|
16625
|
+
(job) => job.billingStatus === "unknown"
|
|
16626
|
+
).length,
|
|
16411
16627
|
totalEstimated: textCredits + successfulJobs.length * perImageCost
|
|
16412
16628
|
}
|
|
16413
16629
|
}
|
|
@@ -16435,7 +16651,9 @@ function registerCarouselTools(server) {
|
|
|
16435
16651
|
for (const slide of carousel.slides) {
|
|
16436
16652
|
const job = imageJobs.find((j) => j.slideNumber === slide.slideNumber);
|
|
16437
16653
|
const status = job?.jobId ? `image: ${job.jobId}` : `image FAILED: ${job?.error}`;
|
|
16438
|
-
lines.push(
|
|
16654
|
+
lines.push(
|
|
16655
|
+
` ${slide.slideNumber}. ${slide.headline || "(no headline)"} [${status}]`
|
|
16656
|
+
);
|
|
16439
16657
|
}
|
|
16440
16658
|
if (failedJobs.length > 0) {
|
|
16441
16659
|
lines.push("");
|
|
@@ -16446,7 +16664,9 @@ function registerCarouselTools(server) {
|
|
|
16446
16664
|
const jobIdList = successfulJobs.map((j) => j.jobId).join(", ");
|
|
16447
16665
|
lines.push("");
|
|
16448
16666
|
lines.push("Next steps:");
|
|
16449
|
-
lines.push(
|
|
16667
|
+
lines.push(
|
|
16668
|
+
` 1. Poll each job: check_status with job_id for each of: ${jobIdList}`
|
|
16669
|
+
);
|
|
16450
16670
|
lines.push(
|
|
16451
16671
|
" 2. When all complete: schedule_post with job_ids=[...] and media_type=CAROUSEL_ALBUM"
|
|
16452
16672
|
);
|