@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/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var MCP_VERSION;
|
|
|
19
19
|
var init_version = __esm({
|
|
20
20
|
"src/lib/version.ts"() {
|
|
21
21
|
"use strict";
|
|
22
|
-
MCP_VERSION = "1.9.
|
|
22
|
+
MCP_VERSION = "1.9.1";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -2944,6 +2944,92 @@ var init_ideation = __esm({
|
|
|
2944
2944
|
}
|
|
2945
2945
|
});
|
|
2946
2946
|
|
|
2947
|
+
// src/lib/sanitize-error.ts
|
|
2948
|
+
function redactSensitiveIdentifiers(message) {
|
|
2949
|
+
return message.replace(EMAIL_PATTERN, "[redacted-email]").replace(UUID_PATTERN, "[redacted-id]");
|
|
2950
|
+
}
|
|
2951
|
+
function sanitizeError(error) {
|
|
2952
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
2953
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
2954
|
+
if (pattern.test(msg)) {
|
|
2955
|
+
return userMessage;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
return "An unexpected error occurred. Please try again.";
|
|
2959
|
+
}
|
|
2960
|
+
var ERROR_PATTERNS, UUID_PATTERN, EMAIL_PATTERN;
|
|
2961
|
+
var init_sanitize_error = __esm({
|
|
2962
|
+
"src/lib/sanitize-error.ts"() {
|
|
2963
|
+
"use strict";
|
|
2964
|
+
ERROR_PATTERNS = [
|
|
2965
|
+
// Postgres / PostgREST
|
|
2966
|
+
[
|
|
2967
|
+
/PGRST301|permission denied/i,
|
|
2968
|
+
"Access denied. Check your account permissions."
|
|
2969
|
+
],
|
|
2970
|
+
[
|
|
2971
|
+
/42P01|does not exist/i,
|
|
2972
|
+
"Service temporarily unavailable. Please try again."
|
|
2973
|
+
],
|
|
2974
|
+
[
|
|
2975
|
+
/23505|unique.*constraint|duplicate key/i,
|
|
2976
|
+
"A duplicate record already exists."
|
|
2977
|
+
],
|
|
2978
|
+
[/23503|foreign key/i, "Referenced record not found."],
|
|
2979
|
+
// Gemini / Google AI
|
|
2980
|
+
[
|
|
2981
|
+
/google.*api.*key|googleapis\.com.*40[13]/i,
|
|
2982
|
+
"Content generation failed. Please try again."
|
|
2983
|
+
],
|
|
2984
|
+
[
|
|
2985
|
+
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
2986
|
+
"AI service rate limit reached. Please wait and retry."
|
|
2987
|
+
],
|
|
2988
|
+
[
|
|
2989
|
+
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
2990
|
+
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
2991
|
+
],
|
|
2992
|
+
[
|
|
2993
|
+
/gemini.*error|generativelanguage/i,
|
|
2994
|
+
"Content generation failed. Please try again."
|
|
2995
|
+
],
|
|
2996
|
+
// Kie.ai
|
|
2997
|
+
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
2998
|
+
// Stripe
|
|
2999
|
+
[
|
|
3000
|
+
/stripe.*api|sk_live_|sk_test_/i,
|
|
3001
|
+
"Payment processing error. Please try again."
|
|
3002
|
+
],
|
|
3003
|
+
// Network / fetch
|
|
3004
|
+
[
|
|
3005
|
+
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
3006
|
+
"External service unavailable. Please try again."
|
|
3007
|
+
],
|
|
3008
|
+
[
|
|
3009
|
+
/fetch failed|network error|abort.*timeout/i,
|
|
3010
|
+
"Network request failed. Please try again."
|
|
3011
|
+
],
|
|
3012
|
+
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
3013
|
+
// Supabase Edge Function internals
|
|
3014
|
+
[
|
|
3015
|
+
/FunctionsHttpError|non-2xx status/i,
|
|
3016
|
+
"Backend service error. Please try again."
|
|
3017
|
+
],
|
|
3018
|
+
[
|
|
3019
|
+
/JWT|token.*expired|token.*invalid/i,
|
|
3020
|
+
"Authentication expired. Please re-authenticate."
|
|
3021
|
+
],
|
|
3022
|
+
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
3023
|
+
[
|
|
3024
|
+
/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i,
|
|
3025
|
+
"An internal error occurred. Please try again."
|
|
3026
|
+
]
|
|
3027
|
+
];
|
|
3028
|
+
UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
3029
|
+
EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
3030
|
+
}
|
|
3031
|
+
});
|
|
3032
|
+
|
|
2947
3033
|
// src/lib/checkStatusShape.ts
|
|
2948
3034
|
function buildCheckStatusPayload(job, liveStatus) {
|
|
2949
3035
|
const status = liveStatus?.status ?? job.status;
|
|
@@ -3039,9 +3125,22 @@ function checkCreditBudget(estimatedCost) {
|
|
|
3039
3125
|
}
|
|
3040
3126
|
const used = getCreditsUsed();
|
|
3041
3127
|
if (used + estimatedCost > MAX_CREDITS_PER_RUN) {
|
|
3128
|
+
const message = `Per-run credit cap reached \u2014 this is a local spend guard, not a server rate limit, so retrying will not help. The SOCIALNEURON_MAX_CREDITS_PER_RUN environment variable is set to ${MAX_CREDITS_PER_RUN} credits. This run has already spent ${used} credits and this call is estimated at ~${estimatedCost} more (${used} + ${estimatedCost} = ${used + estimatedCost} > ${MAX_CREDITS_PER_RUN}). To proceed, raise or unset SOCIALNEURON_MAX_CREDITS_PER_RUN.`;
|
|
3042
3129
|
return {
|
|
3043
3130
|
ok: false,
|
|
3044
|
-
message
|
|
3131
|
+
message,
|
|
3132
|
+
error: toolError("billing_error", message, {
|
|
3133
|
+
recover_with: [
|
|
3134
|
+
"Raise SOCIALNEURON_MAX_CREDITS_PER_RUN to a higher credit ceiling.",
|
|
3135
|
+
"Unset SOCIALNEURON_MAX_CREDITS_PER_RUN to remove the per-run cap."
|
|
3136
|
+
],
|
|
3137
|
+
details: {
|
|
3138
|
+
env_var: "SOCIALNEURON_MAX_CREDITS_PER_RUN",
|
|
3139
|
+
credits_used_this_run: used,
|
|
3140
|
+
estimated_call_cost: estimatedCost,
|
|
3141
|
+
max_credits_per_run: MAX_CREDITS_PER_RUN
|
|
3142
|
+
}
|
|
3143
|
+
})
|
|
3045
3144
|
};
|
|
3046
3145
|
}
|
|
3047
3146
|
return { ok: true };
|
|
@@ -3064,8 +3163,15 @@ var init_budget = __esm({
|
|
|
3064
3163
|
"src/lib/budget.ts"() {
|
|
3065
3164
|
"use strict";
|
|
3066
3165
|
init_request_context();
|
|
3067
|
-
|
|
3068
|
-
|
|
3166
|
+
init_tool_error();
|
|
3167
|
+
MAX_CREDITS_PER_RUN = Math.max(
|
|
3168
|
+
0,
|
|
3169
|
+
Number(process.env.SOCIALNEURON_MAX_CREDITS_PER_RUN || 0)
|
|
3170
|
+
);
|
|
3171
|
+
MAX_ASSETS_PER_RUN = Math.max(
|
|
3172
|
+
0,
|
|
3173
|
+
Number(process.env.SOCIALNEURON_MAX_ASSETS_PER_RUN || 0)
|
|
3174
|
+
);
|
|
3069
3175
|
_globalCreditsUsed = 0;
|
|
3070
3176
|
_globalAssetsGenerated = 0;
|
|
3071
3177
|
}
|
|
@@ -3092,7 +3198,7 @@ function asEnvelope2(data) {
|
|
|
3092
3198
|
function registerContentTools(server2) {
|
|
3093
3199
|
server2.tool(
|
|
3094
3200
|
"generate_video",
|
|
3095
|
-
"Start an async AI video generation job \u2014 returns a job_id immediately. Poll with check_status every 10-30s until complete. Base credit costs (reference config; dynamic models scale with duration/audio/resolution): seedance-2-fast 264 (8s, best quality/credit) \xB7 kling-3 100 (5s no-audio) \xB7 grok-imagine 30 (6s, cheapest) \xB7 veo3-fast 65 (8s) \xB7 kling-3-pro 135 (5s no-audio) \xB7 seedance-2 328 (8s 720p) \xB7 veo3-quality 1000 (8s) \xB7 wan-2.6 105 (5s) \xB7 gemini-omni-video 126 (multi-reference composites) \xB7 hailuo-02-standard 180 / seedance-1.5-pro 150 / kling 170 (legacy/budget fallbacks). Costs are estimated pre-check and reconciled post-response from the actual charge. audio adds ~1.5x on kling-3/kling-3-pro and ~2.65x on kling
|
|
3201
|
+
"Start an async AI video generation job \u2014 returns a job_id immediately. Poll with check_status every 10-30s until complete. Base credit costs (reference config; dynamic models scale with duration/audio/resolution): seedance-2-fast 264 (8s, best quality/credit) \xB7 kling-3 100 (5s no-audio) \xB7 grok-imagine 30 (6s, cheapest) \xB7 veo3-fast 65 (8s) \xB7 kling-3-pro 135 (5s no-audio) \xB7 seedance-2 328 (8s 720p) \xB7 veo3-quality 1000 (8s) \xB7 wan-2.6 105 (5s) \xB7 gemini-omni-video 126 (multi-reference composites) \xB7 hailuo-02-standard 180 / seedance-1.5-pro 150 / kling 170 (legacy/budget fallbacks). Costs are estimated pre-check and reconciled post-response from the actual charge. audio adds ~1.5x on kling-3/kling-3-pro and ~2.65x on kling; enable_audio defaults to FALSE EXCEPT the seedance-2 family (seedance-2-fast, seedance-2) where audio is ON by default and its cost is already included. Check get_credit_balance first for expensive generations.",
|
|
3096
3202
|
{
|
|
3097
3203
|
prompt: z2.string().max(2500).describe(
|
|
3098
3204
|
'Video prompt \u2014 be specific about visual style, camera movement, lighting, and mood. Example: "Aerial drone shot of coastal cliffs at golden hour, slow dolly forward, cinematic 24fps, warm color grading." Vague prompts produce generic results.'
|
|
@@ -3107,7 +3213,7 @@ function registerContentTools(server2) {
|
|
|
3107
3213
|
"Video aspect ratio. 16:9 for YouTube/landscape, 9:16 for TikTok/Reels/Shorts, 1:1 for Instagram feed/square. Defaults to 16:9."
|
|
3108
3214
|
),
|
|
3109
3215
|
enable_audio: z2.boolean().optional().describe(
|
|
3110
|
-
"Enable native audio generation. DEFAULTS TO FALSE (cost control). Cost multiplier when true: kling 2.6 ~2.65x (17->45 cr/sec), kling-3 1.5x (20->30 cr/sec), kling-3-pro ~1.5x (27->40 cr/sec).
|
|
3216
|
+
"Enable native audio generation. For most models this DEFAULTS TO FALSE (cost control). EXCEPTION: the seedance-2 family (seedance-2-fast, seedance-2) DEFAULTS TO TRUE \u2014 these generate audio natively and their credit cost already includes it, so audio is on unless you explicitly pass false. Cost multiplier when true on other models: kling 2.6 ~2.65x (17->45 cr/sec), kling-3 1.5x (20->30 cr/sec), kling-3-pro ~1.5x (27->40 cr/sec). 5+ languages."
|
|
3111
3217
|
),
|
|
3112
3218
|
image_url: z2.string().optional().describe(
|
|
3113
3219
|
"Start frame image URL for image-to-video (Kling 3.0 frame control)."
|
|
@@ -3143,10 +3249,7 @@ function registerContentTools(server2) {
|
|
|
3143
3249
|
const estimatedCost = VIDEO_CREDIT_ESTIMATES[model] ?? 120;
|
|
3144
3250
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3145
3251
|
if (!budgetCheck.ok) {
|
|
3146
|
-
return
|
|
3147
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3148
|
-
isError: true
|
|
3149
|
-
};
|
|
3252
|
+
return budgetCheck.error;
|
|
3150
3253
|
}
|
|
3151
3254
|
const rateLimit = checkRateLimit(
|
|
3152
3255
|
"generation",
|
|
@@ -3170,9 +3273,12 @@ function registerContentTools(server2) {
|
|
|
3170
3273
|
model,
|
|
3171
3274
|
duration: duration ?? 5,
|
|
3172
3275
|
aspectRatio: aspect_ratio ?? "16:9",
|
|
3173
|
-
// Default FALSE (2026-07-13): the old `?? true` default silently
|
|
3174
|
-
// kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
3175
|
-
|
|
3276
|
+
// Default FALSE for most models (2026-07-13): the old `?? true` default silently
|
|
3277
|
+
// multiplied kling-family costs (2.65x on kling 2.6) for callers that never asked
|
|
3278
|
+
// for audio. Exception (2026-07-17): the seedance-2 family bills audio into its base
|
|
3279
|
+
// cost and generates it natively, so a false default there shipped silent no-audio
|
|
3280
|
+
// mp4s — those models default TRUE when the caller omits enable_audio.
|
|
3281
|
+
enableAudio: enable_audio ?? AUDIO_NATIVE_DEFAULT_MODELS.has(model),
|
|
3176
3282
|
...image_url && { imageUrl: image_url },
|
|
3177
3283
|
...end_frame_url && { endFrameUrl: end_frame_url },
|
|
3178
3284
|
// The server reads projectId and enforces
|
|
@@ -3294,10 +3400,7 @@ function registerContentTools(server2) {
|
|
|
3294
3400
|
const estimatedCost = IMAGE_CREDIT_ESTIMATES[model] ?? 30;
|
|
3295
3401
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3296
3402
|
if (!budgetCheck.ok) {
|
|
3297
|
-
return
|
|
3298
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3299
|
-
isError: true
|
|
3300
|
-
};
|
|
3403
|
+
return budgetCheck.error;
|
|
3301
3404
|
}
|
|
3302
3405
|
const rateLimit = checkRateLimit(
|
|
3303
3406
|
"generation",
|
|
@@ -3542,7 +3645,7 @@ function registerContentTools(server2) {
|
|
|
3542
3645
|
);
|
|
3543
3646
|
}
|
|
3544
3647
|
if (job.error_message) {
|
|
3545
|
-
lines.push(`Error: ${job.error_message}`);
|
|
3648
|
+
lines.push(`Error: ${redactSensitiveIdentifiers(job.error_message)}`);
|
|
3546
3649
|
}
|
|
3547
3650
|
const fallbackDisclosure = buildFallbackDisclosureLine(
|
|
3548
3651
|
job.result_metadata
|
|
@@ -3696,10 +3799,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3696
3799
|
const estimatedCost = 10;
|
|
3697
3800
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3698
3801
|
if (!budgetCheck.ok) {
|
|
3699
|
-
return
|
|
3700
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3701
|
-
isError: true
|
|
3702
|
-
};
|
|
3802
|
+
return budgetCheck.error;
|
|
3703
3803
|
}
|
|
3704
3804
|
const { data, error } = await callEdgeFunction(
|
|
3705
3805
|
"social-neuron-ai",
|
|
@@ -3786,10 +3886,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3786
3886
|
const estimatedCost = 15;
|
|
3787
3887
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3788
3888
|
if (!budgetCheck.ok) {
|
|
3789
|
-
return
|
|
3790
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3791
|
-
isError: true
|
|
3792
|
-
};
|
|
3889
|
+
return budgetCheck.error;
|
|
3793
3890
|
}
|
|
3794
3891
|
const rateLimit = checkRateLimit(
|
|
3795
3892
|
"generation",
|
|
@@ -3952,10 +4049,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
3952
4049
|
const estimatedCost = 10 + slideCount * 2;
|
|
3953
4050
|
const budgetCheck = checkCreditBudget(estimatedCost);
|
|
3954
4051
|
if (!budgetCheck.ok) {
|
|
3955
|
-
return
|
|
3956
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
3957
|
-
isError: true
|
|
3958
|
-
};
|
|
4052
|
+
return budgetCheck.error;
|
|
3959
4053
|
}
|
|
3960
4054
|
const userId = await getDefaultUserId();
|
|
3961
4055
|
const rateLimit = checkRateLimit(
|
|
@@ -4058,13 +4152,14 @@ Return ONLY valid JSON in this exact format:
|
|
|
4058
4152
|
}
|
|
4059
4153
|
);
|
|
4060
4154
|
}
|
|
4061
|
-
var VIDEO_CREDIT_ESTIMATES, VIDEO_MODEL_ENUM, IMAGE_CREDIT_ESTIMATES;
|
|
4155
|
+
var VIDEO_CREDIT_ESTIMATES, AUDIO_NATIVE_DEFAULT_MODELS, VIDEO_MODEL_ENUM, IMAGE_CREDIT_ESTIMATES;
|
|
4062
4156
|
var init_content = __esm({
|
|
4063
4157
|
"src/tools/content.ts"() {
|
|
4064
4158
|
"use strict";
|
|
4065
4159
|
init_edge_function();
|
|
4066
4160
|
init_rate_limit();
|
|
4067
4161
|
init_supabase();
|
|
4162
|
+
init_sanitize_error();
|
|
4068
4163
|
init_version();
|
|
4069
4164
|
init_checkStatusShape();
|
|
4070
4165
|
init_budget();
|
|
@@ -4082,6 +4177,10 @@ var init_content = __esm({
|
|
|
4082
4177
|
"seedance-1.5-pro": 150,
|
|
4083
4178
|
kling: 170
|
|
4084
4179
|
};
|
|
4180
|
+
AUDIO_NATIVE_DEFAULT_MODELS = /* @__PURE__ */ new Set([
|
|
4181
|
+
"seedance-2-fast",
|
|
4182
|
+
"seedance-2"
|
|
4183
|
+
]);
|
|
4085
4184
|
VIDEO_MODEL_ENUM = [
|
|
4086
4185
|
"seedance-2-fast",
|
|
4087
4186
|
"kling-3",
|
|
@@ -4110,57 +4209,6 @@ var init_content = __esm({
|
|
|
4110
4209
|
}
|
|
4111
4210
|
});
|
|
4112
4211
|
|
|
4113
|
-
// src/lib/sanitize-error.ts
|
|
4114
|
-
function sanitizeError(error) {
|
|
4115
|
-
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
4116
|
-
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
4117
|
-
if (pattern.test(msg)) {
|
|
4118
|
-
return userMessage;
|
|
4119
|
-
}
|
|
4120
|
-
}
|
|
4121
|
-
return "An unexpected error occurred. Please try again.";
|
|
4122
|
-
}
|
|
4123
|
-
var ERROR_PATTERNS;
|
|
4124
|
-
var init_sanitize_error = __esm({
|
|
4125
|
-
"src/lib/sanitize-error.ts"() {
|
|
4126
|
-
"use strict";
|
|
4127
|
-
ERROR_PATTERNS = [
|
|
4128
|
-
// Postgres / PostgREST
|
|
4129
|
-
[/PGRST301|permission denied/i, "Access denied. Check your account permissions."],
|
|
4130
|
-
[/42P01|does not exist/i, "Service temporarily unavailable. Please try again."],
|
|
4131
|
-
[/23505|unique.*constraint|duplicate key/i, "A duplicate record already exists."],
|
|
4132
|
-
[/23503|foreign key/i, "Referenced record not found."],
|
|
4133
|
-
// Gemini / Google AI
|
|
4134
|
-
[/google.*api.*key|googleapis\.com.*40[13]/i, "Content generation failed. Please try again."],
|
|
4135
|
-
[
|
|
4136
|
-
/RESOURCE_EXHAUSTED|quota.*exceeded|429.*google/i,
|
|
4137
|
-
"AI service rate limit reached. Please wait and retry."
|
|
4138
|
-
],
|
|
4139
|
-
[
|
|
4140
|
-
/SAFETY|prompt.*blocked|content.*filter/i,
|
|
4141
|
-
"Content was blocked by the AI safety filter. Try rephrasing."
|
|
4142
|
-
],
|
|
4143
|
-
[/gemini.*error|generativelanguage/i, "Content generation failed. Please try again."],
|
|
4144
|
-
// Kie.ai
|
|
4145
|
-
[/kie\.ai|kieai|kie_api/i, "Media generation failed. Please try again."],
|
|
4146
|
-
// Stripe
|
|
4147
|
-
[/stripe.*api|sk_live_|sk_test_/i, "Payment processing error. Please try again."],
|
|
4148
|
-
// Network / fetch
|
|
4149
|
-
[
|
|
4150
|
-
/ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET/i,
|
|
4151
|
-
"External service unavailable. Please try again."
|
|
4152
|
-
],
|
|
4153
|
-
[/fetch failed|network error|abort.*timeout/i, "Network request failed. Please try again."],
|
|
4154
|
-
[/CERT_|certificate|SSL|TLS/i, "Secure connection failed. Please try again."],
|
|
4155
|
-
// Supabase Edge Function internals
|
|
4156
|
-
[/FunctionsHttpError|non-2xx status/i, "Backend service error. Please try again."],
|
|
4157
|
-
[/JWT|token.*expired|token.*invalid/i, "Authentication expired. Please re-authenticate."],
|
|
4158
|
-
// Generic sensitive patterns (API keys, URLs with secrets)
|
|
4159
|
-
[/[a-z0-9]{32,}.*key|Bearer [a-zA-Z0-9._-]+/i, "An internal error occurred. Please try again."]
|
|
4160
|
-
];
|
|
4161
|
-
}
|
|
4162
|
-
});
|
|
4163
|
-
|
|
4164
4212
|
// src/lib/ssrf.ts
|
|
4165
4213
|
import { promises as dnsPromises } from "node:dns";
|
|
4166
4214
|
function isBlockedIP(ip) {
|
|
@@ -12127,6 +12175,14 @@ var init_plan_approvals = __esm({
|
|
|
12127
12175
|
|
|
12128
12176
|
// src/tools/discovery.ts
|
|
12129
12177
|
import { z as z23 } from "zod";
|
|
12178
|
+
function isHostedTransport() {
|
|
12179
|
+
return process.env.MCP_TRANSPORT === "http";
|
|
12180
|
+
}
|
|
12181
|
+
function isPubliclyDiscoverable(tool) {
|
|
12182
|
+
if (tool.internal || tool.hiddenFromPublicCount) return false;
|
|
12183
|
+
if (tool.localOnly && isHostedTransport()) return false;
|
|
12184
|
+
return true;
|
|
12185
|
+
}
|
|
12130
12186
|
function toolKnowledgeDocument(tool) {
|
|
12131
12187
|
const lines = [
|
|
12132
12188
|
`Tool: ${tool.name}`,
|
|
@@ -12137,7 +12193,8 @@ function toolKnowledgeDocument(tool) {
|
|
|
12137
12193
|
if (tool.task_intent) lines.push(`Task intent: ${tool.task_intent}`);
|
|
12138
12194
|
if (tool.use_when) lines.push(`Use when: ${tool.use_when}`);
|
|
12139
12195
|
if (tool.avoid_when) lines.push(`Avoid when: ${tool.avoid_when}`);
|
|
12140
|
-
if (tool.next_tools?.length)
|
|
12196
|
+
if (tool.next_tools?.length)
|
|
12197
|
+
lines.push(`Common next tools: ${tool.next_tools.join(", ")}`);
|
|
12141
12198
|
return {
|
|
12142
12199
|
id: `tool:${tool.name}`,
|
|
12143
12200
|
title: `MCP tool: ${tool.name}`,
|
|
@@ -12154,9 +12211,7 @@ function toolKnowledgeDocument(tool) {
|
|
|
12154
12211
|
function getKnowledgeDocuments() {
|
|
12155
12212
|
return [
|
|
12156
12213
|
...STATIC_KNOWLEDGE_DOCUMENTS,
|
|
12157
|
-
...TOOL_CATALOG.filter(
|
|
12158
|
-
toolKnowledgeDocument
|
|
12159
|
-
)
|
|
12214
|
+
...TOOL_CATALOG.filter(isPubliclyDiscoverable).map(toolKnowledgeDocument)
|
|
12160
12215
|
];
|
|
12161
12216
|
}
|
|
12162
12217
|
function tokenize(input) {
|
|
@@ -12210,7 +12265,9 @@ function registerDiscoveryTools(server2) {
|
|
|
12210
12265
|
};
|
|
12211
12266
|
return {
|
|
12212
12267
|
structuredContent,
|
|
12213
|
-
content: [
|
|
12268
|
+
content: [
|
|
12269
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
12270
|
+
]
|
|
12214
12271
|
};
|
|
12215
12272
|
}
|
|
12216
12273
|
);
|
|
@@ -12231,10 +12288,14 @@ function registerDiscoveryTools(server2) {
|
|
|
12231
12288
|
}
|
|
12232
12289
|
},
|
|
12233
12290
|
async ({ id }) => {
|
|
12234
|
-
const doc = getKnowledgeDocuments().find(
|
|
12291
|
+
const doc = getKnowledgeDocuments().find(
|
|
12292
|
+
(candidate) => candidate.id === id
|
|
12293
|
+
);
|
|
12235
12294
|
if (!doc) {
|
|
12236
12295
|
return {
|
|
12237
|
-
content: [
|
|
12296
|
+
content: [
|
|
12297
|
+
{ type: "text", text: `Document not found: ${id}` }
|
|
12298
|
+
],
|
|
12238
12299
|
isError: true
|
|
12239
12300
|
};
|
|
12240
12301
|
}
|
|
@@ -12247,7 +12308,9 @@ function registerDiscoveryTools(server2) {
|
|
|
12247
12308
|
};
|
|
12248
12309
|
return {
|
|
12249
12310
|
structuredContent,
|
|
12250
|
-
content: [
|
|
12311
|
+
content: [
|
|
12312
|
+
{ type: "text", text: JSON.stringify(structuredContent) }
|
|
12313
|
+
]
|
|
12251
12314
|
};
|
|
12252
12315
|
}
|
|
12253
12316
|
);
|
|
@@ -12256,7 +12319,9 @@ function registerDiscoveryTools(server2) {
|
|
|
12256
12319
|
'Search available tools by name, description, module, or scope. Use "name" detail (~50 tokens) for quick lookup, "summary" (~500 tokens) for descriptions, "full" for complete input schemas. Start here if unsure which tool to call \u2014 filter by module (e.g. "planning", "content", "analytics") to narrow results.',
|
|
12257
12320
|
{
|
|
12258
12321
|
query: z23.string().optional().describe("Search query to filter tools by name or description"),
|
|
12259
|
-
module: z23.string().optional().describe(
|
|
12322
|
+
module: z23.string().optional().describe(
|
|
12323
|
+
'Filter by module name (e.g. "planning", "content", "analytics")'
|
|
12324
|
+
),
|
|
12260
12325
|
scope: z23.string().optional().describe('Filter by required scope (e.g. "mcp:read", "mcp:write")'),
|
|
12261
12326
|
detail: z23.enum(["name", "summary", "full"]).default("summary").describe(
|
|
12262
12327
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
@@ -12273,14 +12338,18 @@ function registerDiscoveryTools(server2) {
|
|
|
12273
12338
|
if (query) {
|
|
12274
12339
|
results = searchTools(query);
|
|
12275
12340
|
}
|
|
12276
|
-
results = results.filter(
|
|
12341
|
+
results = results.filter(isPubliclyDiscoverable);
|
|
12277
12342
|
if (module) {
|
|
12278
12343
|
const moduleTools = getToolsByModule(module);
|
|
12279
|
-
results = results.filter(
|
|
12344
|
+
results = results.filter(
|
|
12345
|
+
(t) => moduleTools.some((mt) => mt.name === t.name)
|
|
12346
|
+
);
|
|
12280
12347
|
}
|
|
12281
12348
|
if (scope) {
|
|
12282
12349
|
const scopeTools = getToolsByScope(scope);
|
|
12283
|
-
results = results.filter(
|
|
12350
|
+
results = results.filter(
|
|
12351
|
+
(t) => scopeTools.some((st) => st.name === t.name)
|
|
12352
|
+
);
|
|
12284
12353
|
}
|
|
12285
12354
|
if (available_only) {
|
|
12286
12355
|
results = results.filter(isAvailable);
|
|
@@ -12318,7 +12387,12 @@ function registerDiscoveryTools(server2) {
|
|
|
12318
12387
|
text: JSON.stringify(
|
|
12319
12388
|
{
|
|
12320
12389
|
toolCount: results.length,
|
|
12321
|
-
...hasKnownScopes ? {
|
|
12390
|
+
...hasKnownScopes ? {
|
|
12391
|
+
scopes: {
|
|
12392
|
+
available: currentScopes,
|
|
12393
|
+
unavailable_matches: unavailableCount
|
|
12394
|
+
}
|
|
12395
|
+
} : {},
|
|
12322
12396
|
tools: output
|
|
12323
12397
|
},
|
|
12324
12398
|
null,
|
|
@@ -14909,25 +14983,39 @@ function registerCarouselTools(server2) {
|
|
|
14909
14983
|
]).optional().describe("Carousel template. Default: hormozi-authority."),
|
|
14910
14984
|
slide_count: z28.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
14911
14985
|
aspect_ratio: z28.enum(["1:1", "4:5", "9:16"]).optional().describe("Aspect ratio for both carousel and images. Default: 1:1."),
|
|
14912
|
-
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
14986
|
+
style: z28.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
14987
|
+
"Visual style. Default: hormozi for hormozi-authority template."
|
|
14988
|
+
),
|
|
14913
14989
|
image_style_suffix: z28.string().max(500).optional().describe(
|
|
14914
14990
|
'Style suffix appended to every image prompt for visual consistency across slides. Example: "dark moody lighting, cinematic, 35mm film grain".'
|
|
14915
14991
|
),
|
|
14916
14992
|
hook: z28.string().max(300).optional().describe(
|
|
14917
14993
|
"Explicit hook/opener for slide 1. Overrides any hook derived from topic. Keep under 15 words for strongest scroll-stop."
|
|
14918
14994
|
),
|
|
14919
|
-
hook_family: z28.enum([
|
|
14995
|
+
hook_family: z28.enum([
|
|
14996
|
+
"curiosity",
|
|
14997
|
+
"authority",
|
|
14998
|
+
"pain_point",
|
|
14999
|
+
"contrarian",
|
|
15000
|
+
"data_driven"
|
|
15001
|
+
]).optional().describe(
|
|
14920
15002
|
"Hook family tag. Recorded on the carousel so downstream analytics can attribute engagement to hook pattern."
|
|
14921
15003
|
),
|
|
14922
|
-
cta_text: z28.string().max(200).optional().describe(
|
|
14923
|
-
|
|
15004
|
+
cta_text: z28.string().max(200).optional().describe(
|
|
15005
|
+
"Explicit CTA copy for the final slide. If omitted, derived from topic."
|
|
15006
|
+
),
|
|
15007
|
+
cta_url: z28.string().url().optional().describe(
|
|
15008
|
+
"URL promoted on the CTA slide. Defaults to the project landing page."
|
|
15009
|
+
),
|
|
14924
15010
|
tone: z28.string().max(200).optional().describe(
|
|
14925
15011
|
'Voice/tone override. Composes with the brand profile voice when present. Example: "educational, confident, not arrogant".'
|
|
14926
15012
|
),
|
|
14927
15013
|
constraints: z28.string().max(500).optional().describe(
|
|
14928
15014
|
'Content constraints applied at generation time. Example: "No fabricated statistics. Sentence case only. No ALL CAPS."'
|
|
14929
15015
|
),
|
|
14930
|
-
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
15016
|
+
platform: z28.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe(
|
|
15017
|
+
"Target platform. Affects tone conventions and slide-count guardrails."
|
|
15018
|
+
),
|
|
14931
15019
|
brand_id: z28.string().optional().describe(
|
|
14932
15020
|
"Brand/project ID to pull visual context from (colors, logo, mood). Falls back to project_id, then default project."
|
|
14933
15021
|
),
|
|
@@ -14959,7 +15047,9 @@ function registerCarouselTools(server2) {
|
|
|
14959
15047
|
const slideCount = slide_count ?? 7;
|
|
14960
15048
|
const ratio = aspect_ratio ?? "1:1";
|
|
14961
15049
|
let brandContext = null;
|
|
14962
|
-
const
|
|
15050
|
+
const defaultProjectId = await getDefaultProjectId();
|
|
15051
|
+
const brandProjectId = brand_id || project_id || defaultProjectId;
|
|
15052
|
+
const resolvedProjectId = project_id || defaultProjectId;
|
|
14963
15053
|
if (brandProjectId) {
|
|
14964
15054
|
brandContext = await fetchBrandVisualContext(brandProjectId);
|
|
14965
15055
|
}
|
|
@@ -14968,10 +15058,7 @@ function registerCarouselTools(server2) {
|
|
|
14968
15058
|
const totalEstimatedCost = carouselTextCost + slideCount * perImageCost;
|
|
14969
15059
|
const budgetCheck = checkCreditBudget(totalEstimatedCost);
|
|
14970
15060
|
if (!budgetCheck.ok) {
|
|
14971
|
-
return
|
|
14972
|
-
content: [{ type: "text", text: budgetCheck.message }],
|
|
14973
|
-
isError: true
|
|
14974
|
-
};
|
|
15061
|
+
return budgetCheck.error;
|
|
14975
15062
|
}
|
|
14976
15063
|
const assetBudget = checkAssetBudget(slideCount);
|
|
14977
15064
|
if (!assetBudget.ok) {
|
|
@@ -14981,7 +15068,10 @@ function registerCarouselTools(server2) {
|
|
|
14981
15068
|
};
|
|
14982
15069
|
}
|
|
14983
15070
|
const userId = await getDefaultUserId();
|
|
14984
|
-
const rateLimit = checkRateLimit(
|
|
15071
|
+
const rateLimit = checkRateLimit(
|
|
15072
|
+
"generation",
|
|
15073
|
+
`create_carousel:${userId}`
|
|
15074
|
+
);
|
|
14985
15075
|
if (!rateLimit.allowed) {
|
|
14986
15076
|
return {
|
|
14987
15077
|
content: [
|
|
@@ -15001,7 +15091,7 @@ function registerCarouselTools(server2) {
|
|
|
15001
15091
|
slideCount,
|
|
15002
15092
|
aspectRatio: ratio,
|
|
15003
15093
|
style: resolvedStyle,
|
|
15004
|
-
projectId:
|
|
15094
|
+
projectId: resolvedProjectId,
|
|
15005
15095
|
hook,
|
|
15006
15096
|
hookFamily: hook_family,
|
|
15007
15097
|
ctaText: cta_text,
|
|
@@ -15015,7 +15105,12 @@ function registerCarouselTools(server2) {
|
|
|
15015
15105
|
if (carouselError || !carouselData?.carousel) {
|
|
15016
15106
|
const errMsg = carouselError ?? "No carousel data returned";
|
|
15017
15107
|
return {
|
|
15018
|
-
content: [
|
|
15108
|
+
content: [
|
|
15109
|
+
{
|
|
15110
|
+
type: "text",
|
|
15111
|
+
text: `Carousel text generation failed: ${errMsg}`
|
|
15112
|
+
}
|
|
15113
|
+
],
|
|
15019
15114
|
isError: true
|
|
15020
15115
|
};
|
|
15021
15116
|
}
|
|
@@ -15044,7 +15139,8 @@ function registerCarouselTools(server2) {
|
|
|
15044
15139
|
{
|
|
15045
15140
|
prompt: imagePrompt,
|
|
15046
15141
|
model: image_model,
|
|
15047
|
-
aspectRatio: ratio
|
|
15142
|
+
aspectRatio: ratio,
|
|
15143
|
+
...resolvedProjectId && { projectId: resolvedProjectId }
|
|
15048
15144
|
},
|
|
15049
15145
|
{ timeoutMs: 3e4 }
|
|
15050
15146
|
);
|
|
@@ -15101,14 +15197,19 @@ function registerCarouselTools(server2) {
|
|
|
15101
15197
|
type: "text",
|
|
15102
15198
|
text: JSON.stringify(
|
|
15103
15199
|
{
|
|
15104
|
-
_meta: {
|
|
15200
|
+
_meta: {
|
|
15201
|
+
version: MCP_VERSION,
|
|
15202
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
15203
|
+
},
|
|
15105
15204
|
data: {
|
|
15106
15205
|
carouselId: carousel.id,
|
|
15107
15206
|
templateId,
|
|
15108
15207
|
style: resolvedStyle,
|
|
15109
15208
|
slideCount: carousel.slides.length,
|
|
15110
15209
|
slides: carousel.slides.map((s) => {
|
|
15111
|
-
const job = imageJobs.find(
|
|
15210
|
+
const job = imageJobs.find(
|
|
15211
|
+
(j) => j.slideNumber === s.slideNumber
|
|
15212
|
+
);
|
|
15112
15213
|
return {
|
|
15113
15214
|
...s,
|
|
15114
15215
|
imageJobId: job?.jobId ?? null,
|
|
@@ -15135,9 +15236,17 @@ function registerCarouselTools(server2) {
|
|
|
15135
15236
|
credits: {
|
|
15136
15237
|
textGeneration: textCredits,
|
|
15137
15238
|
imagesEstimated: successfulJobs.length * perImageCost,
|
|
15138
|
-
imagesCharged: imageJobs.reduce(
|
|
15139
|
-
|
|
15140
|
-
|
|
15239
|
+
imagesCharged: imageJobs.reduce(
|
|
15240
|
+
(sum, job) => sum + (job.creditsCharged ?? 0),
|
|
15241
|
+
0
|
|
15242
|
+
),
|
|
15243
|
+
imagesRefunded: imageJobs.reduce(
|
|
15244
|
+
(sum, job) => sum + (job.creditsRefunded ?? 0),
|
|
15245
|
+
0
|
|
15246
|
+
),
|
|
15247
|
+
billingUnknownSlides: imageJobs.filter(
|
|
15248
|
+
(job) => job.billingStatus === "unknown"
|
|
15249
|
+
).length,
|
|
15141
15250
|
totalEstimated: textCredits + successfulJobs.length * perImageCost
|
|
15142
15251
|
}
|
|
15143
15252
|
}
|
|
@@ -15165,7 +15274,9 @@ function registerCarouselTools(server2) {
|
|
|
15165
15274
|
for (const slide of carousel.slides) {
|
|
15166
15275
|
const job = imageJobs.find((j) => j.slideNumber === slide.slideNumber);
|
|
15167
15276
|
const status = job?.jobId ? `image: ${job.jobId}` : `image FAILED: ${job?.error}`;
|
|
15168
|
-
lines.push(
|
|
15277
|
+
lines.push(
|
|
15278
|
+
` ${slide.slideNumber}. ${slide.headline || "(no headline)"} [${status}]`
|
|
15279
|
+
);
|
|
15169
15280
|
}
|
|
15170
15281
|
if (failedJobs.length > 0) {
|
|
15171
15282
|
lines.push("");
|
|
@@ -15176,7 +15287,9 @@ function registerCarouselTools(server2) {
|
|
|
15176
15287
|
const jobIdList = successfulJobs.map((j) => j.jobId).join(", ");
|
|
15177
15288
|
lines.push("");
|
|
15178
15289
|
lines.push("Next steps:");
|
|
15179
|
-
lines.push(
|
|
15290
|
+
lines.push(
|
|
15291
|
+
` 1. Poll each job: check_status with job_id for each of: ${jobIdList}`
|
|
15292
|
+
);
|
|
15180
15293
|
lines.push(
|
|
15181
15294
|
" 2. When all complete: schedule_post with job_ids=[...] and media_type=CAROUSEL_ALBUM"
|
|
15182
15295
|
);
|