@socialneuron/mcp-server 1.9.0 → 1.9.1
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 +10 -0
- package/dist/http.js +220 -109
- package/dist/index.js +229 -116
- package/dist/sn.js +229 -116
- package/package.json +1 -1
- package/tools.lock.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to `@socialneuron/mcp-server` will be documented in this fil
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.9.1] - 2026-07-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`generate_video` seedance audio default.** Seedance requests no longer silently drop audio: the audio flag now defaults on for audio-capable seedance models instead of rendering silent clips.
|
|
12
|
+
- **Budget-limit errors are self-explanatory.** Budget rejections now state the limit, current usage, and what to do next, instead of a generic failure.
|
|
13
|
+
- **`localOnly` tools no longer leak into remote listings.** Local-only tools are filtered from the tool catalogue when the server runs in remote/HTTP mode.
|
|
14
|
+
- **`check_status` error scrubbing.** Internal error details are scrubbed from `check_status` responses; callers get actionable, non-internal messages.
|
|
15
|
+
- **Carousel per-slide images inherit the resolved project.** `create_carousel` now threads the resolved `project_id` to the carousel job and every per-slide image job, so slide uploads carry project/organization context and are no longer rejected at upload ("A verified project or organization is required for uploads").
|
|
16
|
+
|
|
7
17
|
## [1.9.0] - 2026-07-15
|
|
8
18
|
|
|
9
19
|
### Added
|
package/dist/http.js
CHANGED
|
@@ -2509,7 +2509,7 @@ function checkRateLimit(category, key) {
|
|
|
2509
2509
|
init_supabase();
|
|
2510
2510
|
|
|
2511
2511
|
// src/lib/version.ts
|
|
2512
|
-
var MCP_VERSION = "1.9.
|
|
2512
|
+
var MCP_VERSION = "1.9.1";
|
|
2513
2513
|
|
|
2514
2514
|
// src/tools/ideation.ts
|
|
2515
2515
|
function asEnvelope(data) {
|
|
@@ -2914,6 +2914,86 @@ init_edge_function();
|
|
|
2914
2914
|
import { z as z2 } from "zod";
|
|
2915
2915
|
init_supabase();
|
|
2916
2916
|
|
|
2917
|
+
// src/lib/sanitize-error.ts
|
|
2918
|
+
var ERROR_PATTERNS = [
|
|
2919
|
+
// Postgres / PostgREST
|
|
2920
|
+
[
|
|
2921
|
+
/PGRST301|permission denied/i,
|
|
2922
|
+
"Access denied. Check your account permissions."
|
|
2923
|
+
],
|
|
2924
|
+
[
|
|
2925
|
+
/42P01|does not exist/i,
|
|
2926
|
+
"Service temporarily unavailable. Please try again."
|
|
2927
|
+
],
|
|
2928
|
+
[
|
|
2929
|
+
/23505|unique.*constraint|duplicate key/i,
|
|
2930
|
+
"A duplicate record already exists."
|
|
2931
|
+
],
|
|
2932
|
+
[/23503|foreign key/i, "Referenced record not found."],
|
|
2933
|
+
// Gemini / Google AI
|
|
2934
|
+
[
|
|
2935
|
+
/google.*api.*key|googleapis\.com.*40[13]/i,
|
|
2936
|
+
"Content generation failed. Please try again."
|
|
2937
|
+
],
|
|
2938
|
+
[
|
|
2939
|
+
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
2940
|
+
"AI service rate limit reached. Please wait and retry."
|
|
2941
|
+
],
|
|
2942
|
+
[
|
|
2943
|
+
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
2944
|
+
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
2945
|
+
],
|
|
2946
|
+
[
|
|
2947
|
+
/gemini.*error|generativelanguage/i,
|
|
2948
|
+
"Content generation failed. Please try again."
|
|
2949
|
+
],
|
|
2950
|
+
// Kie.ai
|
|
2951
|
+
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
2952
|
+
// Stripe
|
|
2953
|
+
[
|
|
2954
|
+
/stripe.*api|sk_live_|sk_test_/i,
|
|
2955
|
+
"Payment processing error. Please try again."
|
|
2956
|
+
],
|
|
2957
|
+
// Network / fetch
|
|
2958
|
+
[
|
|
2959
|
+
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
2960
|
+
"External service unavailable. Please try again."
|
|
2961
|
+
],
|
|
2962
|
+
[
|
|
2963
|
+
/fetch failed|network error|abort.*timeout/i,
|
|
2964
|
+
"Network request failed. Please try again."
|
|
2965
|
+
],
|
|
2966
|
+
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
2967
|
+
// Supabase Edge Function internals
|
|
2968
|
+
[
|
|
2969
|
+
/FunctionsHttpError|non-2xx status/i,
|
|
2970
|
+
"Backend service error. Please try again."
|
|
2971
|
+
],
|
|
2972
|
+
[
|
|
2973
|
+
/JWT|token.*expired|token.*invalid/i,
|
|
2974
|
+
"Authentication expired. Please re-authenticate."
|
|
2975
|
+
],
|
|
2976
|
+
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
2977
|
+
[
|
|
2978
|
+
/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i,
|
|
2979
|
+
"An internal error occurred. Please try again."
|
|
2980
|
+
]
|
|
2981
|
+
];
|
|
2982
|
+
var UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
2983
|
+
var EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
2984
|
+
function redactSensitiveIdentifiers(message) {
|
|
2985
|
+
return message.replace(EMAIL_PATTERN, "[redacted-email]").replace(UUID_PATTERN, "[redacted-id]");
|
|
2986
|
+
}
|
|
2987
|
+
function sanitizeError(error) {
|
|
2988
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
2989
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
2990
|
+
if (pattern.test(msg)) {
|
|
2991
|
+
return userMessage;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
return "An unexpected error occurred. Please try again.";
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2917
2997
|
// src/lib/checkStatusShape.ts
|
|
2918
2998
|
function buildCheckStatusPayload(job, liveStatus) {
|
|
2919
2999
|
const status = liveStatus?.status ?? job.status;
|
|
@@ -2963,8 +3043,14 @@ function buildCheckStatusPayload(job, liveStatus) {
|
|
|
2963
3043
|
|
|
2964
3044
|
// src/lib/budget.ts
|
|
2965
3045
|
init_request_context();
|
|
2966
|
-
var MAX_CREDITS_PER_RUN = Math.max(
|
|
2967
|
-
|
|
3046
|
+
var MAX_CREDITS_PER_RUN = Math.max(
|
|
3047
|
+
0,
|
|
3048
|
+
Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0)
|
|
3049
|
+
);
|
|
3050
|
+
var MAX_ASSETS_PER_RUN = Math.max(
|
|
3051
|
+
0,
|
|
3052
|
+
Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0)
|
|
3053
|
+
);
|
|
2968
3054
|
var _globalCreditsUsed = 0;
|
|
2969
3055
|
var _globalAssetsGenerated = 0;
|
|
2970
3056
|
function getCreditsUsed() {
|
|
@@ -3009,9 +3095,22 @@ function checkCreditBudget(estimatedCost) {
|
|
|
3009
3095
|
}
|
|
3010
3096
|
const used = getCreditsUsed();
|
|
3011
3097
|
if (used + estimatedCost > MAX_CREDITS_PER_RUN) {
|
|
3098
|
+
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
3099
|
return {
|
|
3013
3100
|
ok: false,
|
|
3014
|
-
message
|
|
3101
|
+
message,
|
|
3102
|
+
error: toolError("billing_error", message, {
|
|
3103
|
+
recover_with: [
|
|
3104
|
+
"Raise SOCIALNEURON_MAX_CREDITS_PER_RUN to a higher credit ceiling.",
|
|
3105
|
+
"Unset SOCIALNEURON_MAX_CREDITS_PER_RUN to remove the per-run cap."
|
|
3106
|
+
],
|
|
3107
|
+
details: {
|
|
3108
|
+
env_var: "SOCIALNEURON_MAX_CREDITS_PER_RUN",
|
|
3109
|
+
credits_used_this_run: used,
|
|
3110
|
+
estimated_call_cost: estimatedCost,
|
|
3111
|
+
max_credits_per_run: MAX_CREDITS_PER_RUN
|
|
3112
|
+
}
|
|
3113
|
+
})
|
|
3015
3114
|
};
|
|
3016
3115
|
}
|
|
3017
3116
|
return { ok: true };
|
|
@@ -3061,6 +3160,10 @@ var VIDEO_CREDIT_ESTIMATES = {
|
|
|
3061
3160
|
"seedance-1.5-pro": 150,
|
|
3062
3161
|
kling: 170
|
|
3063
3162
|
};
|
|
3163
|
+
var AUDIO_NATIVE_DEFAULT_MODELS = /* @__PURE__ */ new Set([
|
|
3164
|
+
"seedance-2-fast",
|
|
3165
|
+
"seedance-2"
|
|
3166
|
+
]);
|
|
3064
3167
|
var VIDEO_MODEL_ENUM = [
|
|
3065
3168
|
"seedance-2-fast",
|
|
3066
3169
|
"kling-3",
|
|
@@ -3089,7 +3192,7 @@ var IMAGE_CREDIT_ESTIMATES = {
|
|
|
3089
3192
|
function registerContentTools(server) {
|
|
3090
3193
|
server.tool(
|
|
3091
3194
|
"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
|
|
3195
|
+
"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
3196
|
{
|
|
3094
3197
|
prompt: z2.string().max(2500).describe(
|
|
3095
3198
|
'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 +3207,7 @@ function registerContentTools(server) {
|
|
|
3104
3207
|
"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
3208
|
),
|
|
3106
3209
|
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).
|
|
3210
|
+
"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
3211
|
),
|
|
3109
3212
|
image_url: z2.string().optional().describe(
|
|
3110
3213
|
"Start frame image URL for image-to-video (Kling 3.0 frame control)."
|
|
@@ -3140,10 +3243,7 @@ function registerContentTools(server) {
|
|
|
3140
3243
|
const estimatedCost = VIDEO_CREDIT_ESTIMATES[model] ?? 120;
|
|
3141
3244
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3142
3245
|
if (!budgetCheck.ok) {
|
|
3143
|
-
return
|
|
3144
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3145
|
-
isError: true
|
|
3146
|
-
};
|
|
3246
|
+
return budgetCheck.error;
|
|
3147
3247
|
}
|
|
3148
3248
|
const rateLimit = checkRateLimit(
|
|
3149
3249
|
"generation",
|
|
@@ -3167,9 +3267,12 @@ function registerContentTools(server) {
|
|
|
3167
3267
|
model,
|
|
3168
3268
|
duration: duration ?? 5,
|
|
3169
3269
|
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
|
-
|
|
3270
|
+
// Default FALSE for most models (2026-07-13): the old `?? true` default silently
|
|
3271
|
+
// multiplied kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
3272
|
+
// for audio. Exception (2026-07-17): the seedance-2 family bills audio into its base
|
|
3273
|
+
// cost and generates it natively, so a false default there shipped silent no-audio
|
|
3274
|
+
// mp4s — those models default TRUE when the caller omits enable_audio.
|
|
3275
|
+
enableAudio: enable_audio ?? AUDIO_NATIVE_DEFAULT_MODELS.has(model),
|
|
3173
3276
|
...image_url && { imageUrl: image_url },
|
|
3174
3277
|
...end_frame_url && { endFrameUrl: end_frame_url },
|
|
3175
3278
|
// The server reads projectId and enforces
|
|
@@ -3291,10 +3394,7 @@ function registerContentTools(server) {
|
|
|
3291
3394
|
const estimatedCost = IMAGE_CREDIT_ESTIMATES[model] ?? 30;
|
|
3292
3395
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3293
3396
|
if (!budgetCheck.ok) {
|
|
3294
|
-
return
|
|
3295
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3296
|
-
isError: true
|
|
3297
|
-
};
|
|
3397
|
+
return budgetCheck.error;
|
|
3298
3398
|
}
|
|
3299
3399
|
const rateLimit = checkRateLimit(
|
|
3300
3400
|
"generation",
|
|
@@ -3539,7 +3639,7 @@ function registerContentTools(server) {
|
|
|
3539
3639
|
);
|
|
3540
3640
|
}
|
|
3541
3641
|
if (job.error_message) {
|
|
3542
|
-
lines.push(`Error: ${job.error_message}`);
|
|
3642
|
+
lines.push(`Error: ${redactSensitiveIdentifiers(job.error_message)}`);
|
|
3543
3643
|
}
|
|
3544
3644
|
const fallbackDisclosure = buildFallbackDisclosureLine(
|
|
3545
3645
|
job.result_metadata
|
|
@@ -3693,10 +3793,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3693
3793
|
const estimatedCost = 10;
|
|
3694
3794
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3695
3795
|
if (!budgetCheck.ok) {
|
|
3696
|
-
return
|
|
3697
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3698
|
-
isError: true
|
|
3699
|
-
};
|
|
3796
|
+
return budgetCheck.error;
|
|
3700
3797
|
}
|
|
3701
3798
|
const { data, error } = await callEdgeFunction(
|
|
3702
3799
|
"social-neuron-ai",
|
|
@@ -3783,10 +3880,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3783
3880
|
const estimatedCost = 15;
|
|
3784
3881
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3785
3882
|
if (!budgetCheck.ok) {
|
|
3786
|
-
return
|
|
3787
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3788
|
-
isError: true
|
|
3789
|
-
};
|
|
3883
|
+
return budgetCheck.error;
|
|
3790
3884
|
}
|
|
3791
3885
|
const rateLimit = checkRateLimit(
|
|
3792
3886
|
"generation",
|
|
@@ -3949,10 +4043,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3949
4043
|
const estimatedCost = 10 + slideCount * 2;
|
|
3950
4044
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3951
4045
|
if (!budgetCheck.ok) {
|
|
3952
|
-
return
|
|
3953
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3954
|
-
isError: true
|
|
3955
|
-
};
|
|
4046
|
+
return budgetCheck.error;
|
|
3956
4047
|
}
|
|
3957
4048
|
const userId = await getDefaultUserId();
|
|
3958
4049
|
const rateLimit = checkRateLimit(
|
|
@@ -4061,51 +4152,6 @@ init_edge_function();
|
|
|
4061
4152
|
import { z as z3 } from "zod";
|
|
4062
4153
|
import { createHash } from "node:crypto";
|
|
4063
4154
|
|
|
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
4155
|
// src/lib/ssrf.ts
|
|
4110
4156
|
import { promises as dnsPromises } from "node:dns";
|
|
4111
4157
|
var BLOCKED_IP_PATTERNS = [
|
|
@@ -11887,6 +11933,14 @@ init_supabase();
|
|
|
11887
11933
|
init_request_context();
|
|
11888
11934
|
var KNOWLEDGE_BASE_URL = "https://socialneuron.com/for-developers";
|
|
11889
11935
|
var KNOWLEDGE_SEARCH_LIMIT = 10;
|
|
11936
|
+
function isHostedTransport() {
|
|
11937
|
+
return process.env.MCP_TRANSPORT === "http";
|
|
11938
|
+
}
|
|
11939
|
+
function isPubliclyDiscoverable(tool) {
|
|
11940
|
+
if (tool.internal || tool.hiddenFromPublicCount) return false;
|
|
11941
|
+
if (tool.localOnly && isHostedTransport()) return false;
|
|
11942
|
+
return true;
|
|
11943
|
+
}
|
|
11890
11944
|
var SearchOutputSchema = {
|
|
11891
11945
|
results: z23.array(
|
|
11892
11946
|
z23.object({
|
|
@@ -11959,7 +12013,8 @@ function toolKnowledgeDocument(tool) {
|
|
|
11959
12013
|
if (tool.task_intent) lines.push(`Task intent: ${tool.task_intent}`);
|
|
11960
12014
|
if (tool.use_when) lines.push(`Use when: ${tool.use_when}`);
|
|
11961
12015
|
if (tool.avoid_when) lines.push(`Avoid when: ${tool.avoid_when}`);
|
|
11962
|
-
if (tool.next_tools?.length)
|
|
12016
|
+
if (tool.next_tools?.length)
|
|
12017
|
+
lines.push(`Common next tools: ${tool.next_tools.join(", ")}`);
|
|
11963
12018
|
return {
|
|
11964
12019
|
id: `tool:${tool.name}`,
|
|
11965
12020
|
title: `MCP tool: ${tool.name}`,
|
|
@@ -11976,9 +12031,7 @@ function toolKnowledgeDocument(tool) {
|
|
|
11976
12031
|
function getKnowledgeDocuments() {
|
|
11977
12032
|
return [
|
|
11978
12033
|
...STATIC_KNOWLEDGE_DOCUMENTS,
|
|
11979
|
-
...TOOL_CATALOG.filter(
|
|
11980
|
-
toolKnowledgeDocument
|
|
11981
|
-
)
|
|
12034
|
+
...TOOL_CATALOG.filter(isPubliclyDiscoverable).map(toolKnowledgeDocument)
|
|
11982
12035
|
];
|
|
11983
12036
|
}
|
|
11984
12037
|
function tokenize(input) {
|
|
@@ -12032,7 +12085,9 @@ function registerDiscoveryTools(server) {
|
|
|
12032
12085
|
};
|
|
12033
12086
|
return {
|
|
12034
12087
|
structuredContent,
|
|
12035
|
-
content: [
|
|
12088
|
+
content: [
|
|
12089
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
12090
|
+
]
|
|
12036
12091
|
};
|
|
12037
12092
|
}
|
|
12038
12093
|
);
|
|
@@ -12053,10 +12108,14 @@ function registerDiscoveryTools(server) {
|
|
|
12053
12108
|
}
|
|
12054
12109
|
},
|
|
12055
12110
|
async ({ id }) => {
|
|
12056
|
-
const doc = getKnowledgeDocuments().find(
|
|
12111
|
+
const doc = getKnowledgeDocuments().find(
|
|
12112
|
+
(candidate) => candidate.id === id
|
|
12113
|
+
);
|
|
12057
12114
|
if (!doc) {
|
|
12058
12115
|
return {
|
|
12059
|
-
content: [
|
|
12116
|
+
content: [
|
|
12117
|
+
{ type: "text", text: `Document not found: ${id}` }
|
|
12118
|
+
],
|
|
12060
12119
|
isError: true
|
|
12061
12120
|
};
|
|
12062
12121
|
}
|
|
@@ -12069,7 +12128,9 @@ function registerDiscoveryTools(server) {
|
|
|
12069
12128
|
};
|
|
12070
12129
|
return {
|
|
12071
12130
|
structuredContent,
|
|
12072
|
-
content: [
|
|
12131
|
+
content: [
|
|
12132
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
12133
|
+
]
|
|
12073
12134
|
};
|
|
12074
12135
|
}
|
|
12075
12136
|
);
|
|
@@ -12078,7 +12139,9 @@ function registerDiscoveryTools(server) {
|
|
|
12078
12139
|
'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
12140
|
{
|
|
12080
12141
|
query: z23.string().optional().describe("Search query to filter tools by name or description"),
|
|
12081
|
-
module: z23.string().optional().describe(
|
|
12142
|
+
module: z23.string().optional().describe(
|
|
12143
|
+
'Filter by module name (e.g. "planning", "content", "analytics")'
|
|
12144
|
+
),
|
|
12082
12145
|
scope: z23.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
12083
12146
|
detail: z23.enum(["name", "summary", "full"]).default("summary").describe(
|
|
12084
12147
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
@@ -12095,14 +12158,18 @@ function registerDiscoveryTools(server) {
|
|
|
12095
12158
|
if (query) {
|
|
12096
12159
|
results = searchTools(query);
|
|
12097
12160
|
}
|
|
12098
|
-
results = results.filter(
|
|
12161
|
+
results = results.filter(isPubliclyDiscoverable);
|
|
12099
12162
|
if (module) {
|
|
12100
12163
|
const moduleTools = getToolsByModule(module);
|
|
12101
|
-
results = results.filter(
|
|
12164
|
+
results = results.filter(
|
|
12165
|
+
(t) => moduleTools.some((mt) => mt.name === t.name)
|
|
12166
|
+
);
|
|
12102
12167
|
}
|
|
12103
12168
|
if (scope) {
|
|
12104
12169
|
const scopeTools = getToolsByScope(scope);
|
|
12105
|
-
results = results.filter(
|
|
12170
|
+
results = results.filter(
|
|
12171
|
+
(t) => scopeTools.some((st) => st.name === t.name)
|
|
12172
|
+
);
|
|
12106
12173
|
}
|
|
12107
12174
|
if (available_only) {
|
|
12108
12175
|
results = results.filter(isAvailable);
|
|
@@ -12140,7 +12207,12 @@ function registerDiscoveryTools(server) {
|
|
|
12140
12207
|
text: JSON.stringify(
|
|
12141
12208
|
{
|
|
12142
12209
|
toolCount: results.length,
|
|
12143
|
-
...hasKnownScopes ? {
|
|
12210
|
+
...hasKnownScopes ? {
|
|
12211
|
+
scopes: {
|
|
12212
|
+
available: currentScopes,
|
|
12213
|
+
unavailable_matches: unavailableCount
|
|
12214
|
+
}
|
|
12215
|
+
} : {},
|
|
12144
12216
|
tools: output
|
|
12145
12217
|
},
|
|
12146
12218
|
null,
|
|
@@ -14621,25 +14693,39 @@ function registerCarouselTools(server) {
|
|
|
14621
14693
|
]).optional().describe("Carousel template. Default: hormozi-authority."),
|
|
14622
14694
|
slide_count: z28.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
14623
14695
|
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(
|
|
14696
|
+
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
14697
|
+
"Visual style. Default: hormozi for hormozi-authority template."
|
|
14698
|
+
),
|
|
14625
14699
|
image_style_suffix: z28.string().max(500).optional().describe(
|
|
14626
14700
|
'Style suffix appended to every image prompt for visual consistency across slides. Example: "dark moody lighting, cinematic, 35mm film grain".'
|
|
14627
14701
|
),
|
|
14628
14702
|
hook: z28.string().max(300).optional().describe(
|
|
14629
14703
|
"Explicit hook/opener for slide 1. Overrides any hook derived from topic. Keep under 15 words for strongest scroll-stop."
|
|
14630
14704
|
),
|
|
14631
|
-
hook_family: z28.enum([
|
|
14705
|
+
hook_family: z28.enum([
|
|
14706
|
+
"curiosity",
|
|
14707
|
+
"authority",
|
|
14708
|
+
"pain_point",
|
|
14709
|
+
"contrarian",
|
|
14710
|
+
"data_driven"
|
|
14711
|
+
]).optional().describe(
|
|
14632
14712
|
"Hook family tag. Recorded on the carousel so downstream analytics can attribute engagement to hook pattern."
|
|
14633
14713
|
),
|
|
14634
|
-
cta_text: z28.string().max(200).optional().describe(
|
|
14635
|
-
|
|
14714
|
+
cta_text: z28.string().max(200).optional().describe(
|
|
14715
|
+
"Explicit CTA copy for the final slide. If omitted, derived from topic."
|
|
14716
|
+
),
|
|
14717
|
+
cta_url: z28.string().url().optional().describe(
|
|
14718
|
+
"URL promoted on the CTA slide. Defaults to the project landing page."
|
|
14719
|
+
),
|
|
14636
14720
|
tone: z28.string().max(200).optional().describe(
|
|
14637
14721
|
'Voice/tone override. Composes with the brand profile voice when present. Example: "educational, confident, not arrogant".'
|
|
14638
14722
|
),
|
|
14639
14723
|
constraints: z28.string().max(500).optional().describe(
|
|
14640
14724
|
'Content constraints applied at generation time. Example: "No fabricated statistics. Sentence case only. No ALL CAPS."'
|
|
14641
14725
|
),
|
|
14642
|
-
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
14726
|
+
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
14727
|
+
"Target platform. Affects tone conventions and slide-count guardrails."
|
|
14728
|
+
),
|
|
14643
14729
|
brand_id: z28.string().optional().describe(
|
|
14644
14730
|
"Brand/project ID to pull visual context from (colors, logo, mood). Falls back to project_id, then default project."
|
|
14645
14731
|
),
|
|
@@ -14671,7 +14757,9 @@ function registerCarouselTools(server) {
|
|
|
14671
14757
|
const slideCount = slide_count ?? 7;
|
|
14672
14758
|
const ratio = aspect_ratio ?? "1:1";
|
|
14673
14759
|
let brandContext = null;
|
|
14674
|
-
const
|
|
14760
|
+
const defaultProjectId = await getDefaultProjectId();
|
|
14761
|
+
const brandProjectId = brand_id || project_id || defaultProjectId;
|
|
14762
|
+
const resolvedProjectId = project_id || defaultProjectId;
|
|
14675
14763
|
if (brandProjectId) {
|
|
14676
14764
|
brandContext = await fetchBrandVisualContext(brandProjectId);
|
|
14677
14765
|
}
|
|
@@ -14680,10 +14768,7 @@ function registerCarouselTools(server) {
|
|
|
14680
14768
|
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
14681
14769
|
const budgetCheck = checkCreditBudget(totalEstimatedCost);
|
|
14682
14770
|
if (!budgetCheck.ok) {
|
|
14683
|
-
return
|
|
14684
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
14685
|
-
isError: true
|
|
14686
|
-
};
|
|
14771
|
+
return budgetCheck.error;
|
|
14687
14772
|
}
|
|
14688
14773
|
const assetBudget = checkAssetBudget(slideCount);
|
|
14689
14774
|
if (!assetBudget.ok) {
|
|
@@ -14693,7 +14778,10 @@ function registerCarouselTools(server) {
|
|
|
14693
14778
|
};
|
|
14694
14779
|
}
|
|
14695
14780
|
const userId = await getDefaultUserId();
|
|
14696
|
-
const rateLimit = checkRateLimit(
|
|
14781
|
+
const rateLimit = checkRateLimit(
|
|
14782
|
+
"generation",
|
|
14783
|
+
`create_carousel:${userId}`
|
|
14784
|
+
);
|
|
14697
14785
|
if (!rateLimit.allowed) {
|
|
14698
14786
|
return {
|
|
14699
14787
|
content: [
|
|
@@ -14713,7 +14801,7 @@ function registerCarouselTools(server) {
|
|
|
14713
14801
|
slideCount,
|
|
14714
14802
|
aspectRatio: ratio,
|
|
14715
14803
|
style: resolvedStyle,
|
|
14716
|
-
projectId:
|
|
14804
|
+
projectId: resolvedProjectId,
|
|
14717
14805
|
hook,
|
|
14718
14806
|
hookFamily: hook_family,
|
|
14719
14807
|
ctaText: cta_text,
|
|
@@ -14727,7 +14815,12 @@ function registerCarouselTools(server) {
|
|
|
14727
14815
|
if (carouselError || !carouselData?.carousel) {
|
|
14728
14816
|
const errMsg = carouselError ?? "No carousel data returned";
|
|
14729
14817
|
return {
|
|
14730
|
-
content: [
|
|
14818
|
+
content: [
|
|
14819
|
+
{
|
|
14820
|
+
type: "text",
|
|
14821
|
+
text: `Carousel text generation failed: ${errMsg}`
|
|
14822
|
+
}
|
|
14823
|
+
],
|
|
14731
14824
|
isError: true
|
|
14732
14825
|
};
|
|
14733
14826
|
}
|
|
@@ -14756,7 +14849,8 @@ function registerCarouselTools(server) {
|
|
|
14756
14849
|
{
|
|
14757
14850
|
prompt: imagePrompt,
|
|
14758
14851
|
model: image_model,
|
|
14759
|
-
aspectRatio: ratio
|
|
14852
|
+
aspectRatio: ratio,
|
|
14853
|
+
...resolvedProjectId && { projectId: resolvedProjectId }
|
|
14760
14854
|
},
|
|
14761
14855
|
{ timeoutMs: 3e4 }
|
|
14762
14856
|
);
|
|
@@ -14813,14 +14907,19 @@ function registerCarouselTools(server) {
|
|
|
14813
14907
|
type: "text",
|
|
14814
14908
|
text: JSON.stringify(
|
|
14815
14909
|
{
|
|
14816
|
-
_meta: {
|
|
14910
|
+
_meta: {
|
|
14911
|
+
version: MCP_VERSION,
|
|
14912
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14913
|
+
},
|
|
14817
14914
|
data: {
|
|
14818
14915
|
carouselId: carousel.id,
|
|
14819
14916
|
templateId,
|
|
14820
14917
|
style: resolvedStyle,
|
|
14821
14918
|
slideCount: carousel.slides.length,
|
|
14822
14919
|
slides: carousel.slides.map((s) => {
|
|
14823
|
-
const job = imageJobs.find(
|
|
14920
|
+
const job = imageJobs.find(
|
|
14921
|
+
(j) => j.slideNumber === s.slideNumber
|
|
14922
|
+
);
|
|
14824
14923
|
return {
|
|
14825
14924
|
...s,
|
|
14826
14925
|
imageJobId: job?.jobId ?? null,
|
|
@@ -14847,9 +14946,17 @@ function registerCarouselTools(server) {
|
|
|
14847
14946
|
credits: {
|
|
14848
14947
|
textGeneration: textCredits,
|
|
14849
14948
|
imagesEstimated: successfulJobs.length * perImageCost,
|
|
14850
|
-
imagesCharged: imageJobs.reduce(
|
|
14851
|
-
|
|
14852
|
-
|
|
14949
|
+
imagesCharged: imageJobs.reduce(
|
|
14950
|
+
(sum, job) => sum + (job.creditsCharged ?? 0),
|
|
14951
|
+
0
|
|
14952
|
+
),
|
|
14953
|
+
imagesRefunded: imageJobs.reduce(
|
|
14954
|
+
(sum, job) => sum + (job.creditsRefunded ?? 0),
|
|
14955
|
+
0
|
|
14956
|
+
),
|
|
14957
|
+
billingUnknownSlides: imageJobs.filter(
|
|
14958
|
+
(job) => job.billingStatus === "unknown"
|
|
14959
|
+
).length,
|
|
14853
14960
|
totalEstimated: textCredits + successfulJobs.length * perImageCost
|
|
14854
14961
|
}
|
|
14855
14962
|
}
|
|
@@ -14877,7 +14984,9 @@ function registerCarouselTools(server) {
|
|
|
14877
14984
|
for (const slide of carousel.slides) {
|
|
14878
14985
|
const job = imageJobs.find((j) => j.slideNumber === slide.slideNumber);
|
|
14879
14986
|
const status = job?.jobId ? `image: ${job.jobId}` : `image FAILED: ${job?.error}`;
|
|
14880
|
-
lines.push(
|
|
14987
|
+
lines.push(
|
|
14988
|
+
` ${slide.slideNumber}. ${slide.headline || "(no headline)"} [${status}]`
|
|
14989
|
+
);
|
|
14881
14990
|
}
|
|
14882
14991
|
if (failedJobs.length > 0) {
|
|
14883
14992
|
lines.push("");
|
|
@@ -14888,7 +14997,9 @@ function registerCarouselTools(server) {
|
|
|
14888
14997
|
const jobIdList = successfulJobs.map((j) => j.jobId).join(", ");
|
|
14889
14998
|
lines.push("");
|
|
14890
14999
|
lines.push("Next steps:");
|
|
14891
|
-
lines.push(
|
|
15000
|
+
lines.push(
|
|
15001
|
+
` 1. Poll each job: check_status with job_id for each of: ${jobIdList}`
|
|
15002
|
+
);
|
|
14892
15003
|
lines.push(
|
|
14893
15004
|
" 2. When all complete: schedule_post with job_ids=[...] and media_type=CAROUSEL_ALBUM"
|
|
14894
15005
|
);
|