@socialneuron/mcp-server 1.7.18 → 1.8.0
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 +23 -1
- package/README.md +8 -8
- package/dist/http.js +9139 -8660
- package/dist/index.js +9309 -8923
- package/dist/sn.js +549 -163
- package/package.json +5 -3
- package/tools.lock.json +32 -32
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.
|
|
22
|
+
MCP_VERSION = "1.8.0";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -34,6 +34,20 @@ function getRequestScopes() {
|
|
|
34
34
|
function getRequestToken() {
|
|
35
35
|
return requestContext.getStore()?.token ?? null;
|
|
36
36
|
}
|
|
37
|
+
function getRequestSurface() {
|
|
38
|
+
return requestContext.getStore()?.surface ?? null;
|
|
39
|
+
}
|
|
40
|
+
function getRequestProjectId() {
|
|
41
|
+
return requestContext.getStore()?.projectId ?? null;
|
|
42
|
+
}
|
|
43
|
+
function resolveSurface() {
|
|
44
|
+
const ctx = getRequestSurface();
|
|
45
|
+
if (ctx) return ctx;
|
|
46
|
+
const transport = process.env.MCP_TRANSPORT;
|
|
47
|
+
if (transport === "stdio") return "mcp-stdio";
|
|
48
|
+
if (transport === "http") return "mcp-http";
|
|
49
|
+
return "cli";
|
|
50
|
+
}
|
|
37
51
|
var requestContext;
|
|
38
52
|
var init_request_context = __esm({
|
|
39
53
|
"src/lib/request-context.ts"() {
|
|
@@ -348,7 +362,10 @@ async function captureToolEvent(args) {
|
|
|
348
362
|
properties: {
|
|
349
363
|
tool_name: args.toolName,
|
|
350
364
|
duration_ms: args.durationMs,
|
|
351
|
-
...args.details
|
|
365
|
+
...args.details,
|
|
366
|
+
// Which surface invoked the tool: mcp-stdio | mcp-http | rest | cli.
|
|
367
|
+
// Authoritative over any details.surface (there are no such callers today).
|
|
368
|
+
surface: resolveSurface()
|
|
352
369
|
}
|
|
353
370
|
});
|
|
354
371
|
}
|
|
@@ -363,6 +380,7 @@ var init_posthog = __esm({
|
|
|
363
380
|
"src/lib/posthog.ts"() {
|
|
364
381
|
"use strict";
|
|
365
382
|
init_supabase();
|
|
383
|
+
init_request_context();
|
|
366
384
|
POSTHOG_SALT = "socialneuron-mcp-ph-v1";
|
|
367
385
|
client = null;
|
|
368
386
|
}
|
|
@@ -377,6 +395,7 @@ __export(supabase_exports, {
|
|
|
377
395
|
getAuthenticatedApiKey: () => getAuthenticatedApiKey,
|
|
378
396
|
getAuthenticatedEmail: () => getAuthenticatedEmail,
|
|
379
397
|
getAuthenticatedExpiresAt: () => getAuthenticatedExpiresAt,
|
|
398
|
+
getAuthenticatedProjectId: () => getAuthenticatedProjectId,
|
|
380
399
|
getAuthenticatedScopes: () => getAuthenticatedScopes,
|
|
381
400
|
getDefaultProjectId: () => getDefaultProjectId,
|
|
382
401
|
getDefaultUserId: () => getDefaultUserId,
|
|
@@ -428,6 +447,9 @@ async function getDefaultUserId() {
|
|
|
428
447
|
throw new Error("No user ID available. Set SOCIALNEURON_USER_ID or authenticate via API key.");
|
|
429
448
|
}
|
|
430
449
|
async function getDefaultProjectId() {
|
|
450
|
+
const requestProjectId = getRequestProjectId();
|
|
451
|
+
if (requestProjectId) return requestProjectId;
|
|
452
|
+
if (authenticatedProjectId) return authenticatedProjectId;
|
|
431
453
|
const userId = await getDefaultUserId().catch(() => null);
|
|
432
454
|
if (userId) {
|
|
433
455
|
const cached = projectIdCache.get(userId);
|
|
@@ -469,6 +491,7 @@ async function initializeAuth() {
|
|
|
469
491
|
authenticatedScopes = result.scopes && result.scopes.length > 0 ? result.scopes : ["mcp:read"];
|
|
470
492
|
authenticatedEmail = result.email || null;
|
|
471
493
|
authenticatedExpiresAt = result.expiresAt || null;
|
|
494
|
+
authenticatedProjectId = result.projectId || null;
|
|
472
495
|
if (!_quietAuth) {
|
|
473
496
|
console.error(
|
|
474
497
|
"[MCP] Authenticated via API key (prefix: " + apiKey.substring(0, 6) + "..." + apiKey.slice(-4) + ")"
|
|
@@ -525,6 +548,9 @@ function getAuthMode() {
|
|
|
525
548
|
function getAuthenticatedApiKey() {
|
|
526
549
|
return authenticatedApiKey;
|
|
527
550
|
}
|
|
551
|
+
function getAuthenticatedProjectId() {
|
|
552
|
+
return authenticatedProjectId;
|
|
553
|
+
}
|
|
528
554
|
function isTelemetryDisabled() {
|
|
529
555
|
return process.env.DO_NOT_TRACK === "1" || process.env.DO_NOT_TRACK === "true" || process.env.SOCIALNEURON_NO_TELEMETRY === "1";
|
|
530
556
|
}
|
|
@@ -554,7 +580,7 @@ async function logMcpToolInvocation(args) {
|
|
|
554
580
|
Promise.resolve().then(() => (init_posthog(), posthog_exports)).then(({ captureToolEvent: captureToolEvent2 }) => captureToolEvent2(args)).catch(() => {
|
|
555
581
|
});
|
|
556
582
|
}
|
|
557
|
-
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes, authenticatedEmail, authenticatedExpiresAt, authenticatedApiKey, MCP_RUN_ID, CLOUD_SUPABASE_URL, CLOUD_SUPABASE_ANON_KEY, projectIdCache;
|
|
583
|
+
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes, authenticatedEmail, authenticatedExpiresAt, authenticatedApiKey, authenticatedProjectId, MCP_RUN_ID, CLOUD_SUPABASE_URL, CLOUD_SUPABASE_ANON_KEY, projectIdCache;
|
|
558
584
|
var init_supabase = __esm({
|
|
559
585
|
"src/lib/supabase.ts"() {
|
|
560
586
|
"use strict";
|
|
@@ -568,6 +594,7 @@ var init_supabase = __esm({
|
|
|
568
594
|
authenticatedEmail = null;
|
|
569
595
|
authenticatedExpiresAt = null;
|
|
570
596
|
authenticatedApiKey = null;
|
|
597
|
+
authenticatedProjectId = null;
|
|
571
598
|
MCP_RUN_ID = randomUUID();
|
|
572
599
|
CLOUD_SUPABASE_URL = "https://rhukkjscgzauutioyeei.supabase.co";
|
|
573
600
|
CLOUD_SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJodWtranNjZ3phdXV0aW95ZWVpIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ4NjM4ODYsImV4cCI6MjA4MDQzOTg4Nn0.JVtrviGvN0HaSh0JFS5KNl5FAB5ffG5Y1IMZsQFUrNQ";
|
|
@@ -2618,13 +2645,15 @@ var init_tool_catalog = __esm({
|
|
|
2618
2645
|
name: "get_loop_pulse",
|
|
2619
2646
|
description: "Read dynamic loop-health KPIs for the growth loop over the last 7 days (reflection/decision coverage, visual gate pass rate, learning-update application rate, per-platform uptake, autopilot lag) \u2014 each with an ok/warn/bad status. Use to decide whether the loop is closing or where it is stuck.",
|
|
2620
2647
|
module: "loop",
|
|
2621
|
-
scope: "mcp:read"
|
|
2648
|
+
scope: "mcp:read",
|
|
2649
|
+
internal: true
|
|
2622
2650
|
},
|
|
2623
2651
|
{
|
|
2624
2652
|
name: "get_bandit_state",
|
|
2625
2653
|
description: "Read the current content learning state for a project \u2014 top-K arms per (arm_type, platform) with expected performance and uncertainty. Use to reason about which hook family / format / timing slot currently performs best per platform.",
|
|
2626
2654
|
module: "loop",
|
|
2627
|
-
scope: "mcp:read"
|
|
2655
|
+
scope: "mcp:read",
|
|
2656
|
+
internal: true
|
|
2628
2657
|
}
|
|
2629
2658
|
];
|
|
2630
2659
|
}
|
|
@@ -2980,7 +3009,9 @@ var init_tool_annotations = __esm({
|
|
|
2980
3009
|
},
|
|
2981
3010
|
"mcp:write": {
|
|
2982
3011
|
readOnlyHint: false,
|
|
2983
|
-
|
|
3012
|
+
// Most write tools create additive drafts/assets. Per MCP semantics,
|
|
3013
|
+
// destructiveHint is reserved for overwrite/delete/irreversible behavior.
|
|
3014
|
+
destructiveHint: false,
|
|
2984
3015
|
idempotentHint: false,
|
|
2985
3016
|
openWorldHint: false
|
|
2986
3017
|
},
|
|
@@ -2998,21 +3029,30 @@ var init_tool_annotations = __esm({
|
|
|
2998
3029
|
},
|
|
2999
3030
|
"mcp:comments": {
|
|
3000
3031
|
readOnlyHint: false,
|
|
3001
|
-
destructiveHint:
|
|
3032
|
+
destructiveHint: false,
|
|
3002
3033
|
idempotentHint: false,
|
|
3003
3034
|
openWorldHint: true
|
|
3004
3035
|
},
|
|
3005
3036
|
"mcp:autopilot": {
|
|
3006
3037
|
readOnlyHint: false,
|
|
3007
|
-
destructiveHint:
|
|
3038
|
+
destructiveHint: false,
|
|
3008
3039
|
idempotentHint: false,
|
|
3009
3040
|
openWorldHint: false
|
|
3010
3041
|
}
|
|
3011
3042
|
};
|
|
3012
3043
|
OVERRIDES = {
|
|
3013
|
-
// Destructive tools
|
|
3044
|
+
// Destructive or overwrite tools
|
|
3014
3045
|
delete_comment: { destructiveHint: true },
|
|
3015
3046
|
moderate_comment: { destructiveHint: true },
|
|
3047
|
+
save_brand_profile: { destructiveHint: true, idempotentHint: true },
|
|
3048
|
+
update_platform_voice: { destructiveHint: true, idempotentHint: true },
|
|
3049
|
+
update_content_plan: { destructiveHint: true, idempotentHint: true },
|
|
3050
|
+
respond_plan_approval: { destructiveHint: true, idempotentHint: true },
|
|
3051
|
+
update_autopilot_config: { destructiveHint: true, idempotentHint: true },
|
|
3052
|
+
run_content_pipeline: { destructiveHint: true, openWorldHint: true },
|
|
3053
|
+
auto_approve_plan: { destructiveHint: true, idempotentHint: true },
|
|
3054
|
+
execute_recipe: { destructiveHint: true, openWorldHint: true },
|
|
3055
|
+
run_skill: { destructiveHint: true },
|
|
3016
3056
|
// Read-only tools in non-read scopes (must also clear destructiveHint from scope default)
|
|
3017
3057
|
list_comments: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
3018
3058
|
list_autopilot_configs: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
@@ -3023,14 +3063,12 @@ var init_tool_annotations = __esm({
|
|
|
3023
3063
|
// Analytics tool that triggers side effects (data refresh)
|
|
3024
3064
|
refresh_platform_analytics: { readOnlyHint: false, idempotentHint: true },
|
|
3025
3065
|
// Write tools that are idempotent
|
|
3026
|
-
save_brand_profile: { idempotentHint: true },
|
|
3027
|
-
update_platform_voice: { idempotentHint: true },
|
|
3028
|
-
update_autopilot_config: { idempotentHint: true },
|
|
3029
|
-
update_content_plan: { idempotentHint: true },
|
|
3030
|
-
respond_plan_approval: { idempotentHint: true },
|
|
3031
3066
|
// Distribution is open-world (publishes to external platforms)
|
|
3032
3067
|
schedule_post: { openWorldHint: true },
|
|
3033
3068
|
schedule_content_plan: { openWorldHint: true },
|
|
3069
|
+
// This only creates a short-lived OAuth deep link; it does not connect the
|
|
3070
|
+
// external account until the user completes the browser flow.
|
|
3071
|
+
start_platform_connection: { destructiveHint: false },
|
|
3034
3072
|
// Extraction reads external URLs
|
|
3035
3073
|
extract_url_content: { openWorldHint: true },
|
|
3036
3074
|
extract_brand: { openWorldHint: true },
|
|
@@ -3038,8 +3076,6 @@ var init_tool_annotations = __esm({
|
|
|
3038
3076
|
check_pipeline_readiness: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
3039
3077
|
get_pipeline_status: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
3040
3078
|
// Pipeline: orchestration tools (non-idempotent, may schedule externally)
|
|
3041
|
-
run_content_pipeline: { openWorldHint: true },
|
|
3042
|
-
auto_approve_plan: { idempotentHint: true },
|
|
3043
3079
|
// Suggest: read-only
|
|
3044
3080
|
suggest_next_content: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
3045
3081
|
// Digest/Anomalies: read-only analytics
|
|
@@ -3090,9 +3126,89 @@ function toolError(code, message, opts = {}) {
|
|
|
3090
3126
|
...opts.meta ? { _meta: opts.meta } : {}
|
|
3091
3127
|
};
|
|
3092
3128
|
}
|
|
3129
|
+
function classifyToolError(result) {
|
|
3130
|
+
const structured = result.structuredContent?.error?.error_type;
|
|
3131
|
+
if (structured && KNOWN_ERROR_TYPES.has(structured)) return structured;
|
|
3132
|
+
const text = result.content?.find((c) => c.type === "text")?.text ?? "";
|
|
3133
|
+
try {
|
|
3134
|
+
const parsed = JSON.parse(text);
|
|
3135
|
+
if (parsed.error_type && KNOWN_ERROR_TYPES.has(parsed.error_type)) {
|
|
3136
|
+
return parsed.error_type;
|
|
3137
|
+
}
|
|
3138
|
+
} catch {
|
|
3139
|
+
}
|
|
3140
|
+
if (/-32602|Input validation error|Invalid arguments/i.test(text)) {
|
|
3141
|
+
return "validation_error";
|
|
3142
|
+
}
|
|
3143
|
+
return "server_error";
|
|
3144
|
+
}
|
|
3145
|
+
var KNOWN_ERROR_TYPES;
|
|
3093
3146
|
var init_tool_error = __esm({
|
|
3094
3147
|
"src/lib/tool-error.ts"() {
|
|
3095
3148
|
"use strict";
|
|
3149
|
+
KNOWN_ERROR_TYPES = /* @__PURE__ */ new Set([
|
|
3150
|
+
"policy_block",
|
|
3151
|
+
"validation_error",
|
|
3152
|
+
"permission_denied",
|
|
3153
|
+
"billing_error",
|
|
3154
|
+
"rate_limited",
|
|
3155
|
+
"not_found",
|
|
3156
|
+
"upstream_error",
|
|
3157
|
+
"server_error"
|
|
3158
|
+
]);
|
|
3159
|
+
}
|
|
3160
|
+
});
|
|
3161
|
+
|
|
3162
|
+
// src/lib/tool-profile.ts
|
|
3163
|
+
function isToolAllowedByProfile(toolName, profile) {
|
|
3164
|
+
return profile === "full" || !ANTHROPIC_DIRECTORY_EXCLUDED_TOOLS.has(toolName);
|
|
3165
|
+
}
|
|
3166
|
+
function applyToolProfile(server, profile) {
|
|
3167
|
+
if (profile === "full") return;
|
|
3168
|
+
const currentTool = server.tool.bind(server);
|
|
3169
|
+
const currentRegisterTool = server.registerTool?.bind(server);
|
|
3170
|
+
server.tool = function profiledTool(...args) {
|
|
3171
|
+
const name = String(args[0] ?? "");
|
|
3172
|
+
if (!isToolAllowedByProfile(name, profile)) return void 0;
|
|
3173
|
+
return currentTool(...args);
|
|
3174
|
+
};
|
|
3175
|
+
if (currentRegisterTool) {
|
|
3176
|
+
server.registerTool = function profiledRegisterTool(...args) {
|
|
3177
|
+
const name = String(args[0] ?? "");
|
|
3178
|
+
if (!isToolAllowedByProfile(name, profile)) return void 0;
|
|
3179
|
+
return currentRegisterTool(...args);
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
var ANTHROPIC_DIRECTORY_EXCLUDED_TOOLS;
|
|
3184
|
+
var init_tool_profile = __esm({
|
|
3185
|
+
"src/lib/tool-profile.ts"() {
|
|
3186
|
+
"use strict";
|
|
3187
|
+
init_tool_catalog();
|
|
3188
|
+
ANTHROPIC_DIRECTORY_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
|
|
3189
|
+
"generate_video",
|
|
3190
|
+
"generate_image",
|
|
3191
|
+
"check_status",
|
|
3192
|
+
"create_storyboard",
|
|
3193
|
+
"generate_voiceover",
|
|
3194
|
+
"generate_carousel",
|
|
3195
|
+
"create_carousel",
|
|
3196
|
+
"render_demo_video",
|
|
3197
|
+
"list_compositions",
|
|
3198
|
+
"render_template_video",
|
|
3199
|
+
"check_pipeline_readiness",
|
|
3200
|
+
"run_content_pipeline",
|
|
3201
|
+
"get_pipeline_status",
|
|
3202
|
+
"auto_approve_plan",
|
|
3203
|
+
"list_recipes",
|
|
3204
|
+
"get_recipe_details",
|
|
3205
|
+
"execute_recipe",
|
|
3206
|
+
"get_recipe_run_status",
|
|
3207
|
+
"list_hyperframes_blocks",
|
|
3208
|
+
"render_hyperframes",
|
|
3209
|
+
"list_skills",
|
|
3210
|
+
"run_skill"
|
|
3211
|
+
]);
|
|
3096
3212
|
}
|
|
3097
3213
|
});
|
|
3098
3214
|
|
|
@@ -3759,6 +3875,52 @@ var init_ideation = __esm({
|
|
|
3759
3875
|
}
|
|
3760
3876
|
});
|
|
3761
3877
|
|
|
3878
|
+
// src/lib/checkStatusShape.ts
|
|
3879
|
+
function buildCheckStatusPayload(job, liveStatus) {
|
|
3880
|
+
const status = liveStatus?.status ?? job.status;
|
|
3881
|
+
const progress = liveStatus?.progress ?? null;
|
|
3882
|
+
const resultUrl = liveStatus?.resultUrl ?? job.result_url ?? null;
|
|
3883
|
+
const r2Key = resultUrl && !resultUrl.startsWith("http") ? resultUrl : null;
|
|
3884
|
+
const errorMessage = liveStatus?.error ?? job.error_message ?? null;
|
|
3885
|
+
const allUrls = job.result_metadata?.all_urls ?? null;
|
|
3886
|
+
const modelRequested = job.result_metadata?.model_requested ?? null;
|
|
3887
|
+
const modelDelivered = job.result_metadata?.model_delivered ?? null;
|
|
3888
|
+
const fallbackReason = job.result_metadata?.fallback_reason ?? null;
|
|
3889
|
+
return {
|
|
3890
|
+
job_id: job.id,
|
|
3891
|
+
job_type: job.job_type,
|
|
3892
|
+
model: job.model,
|
|
3893
|
+
status,
|
|
3894
|
+
progress,
|
|
3895
|
+
result_url: resultUrl,
|
|
3896
|
+
r2_key: r2Key,
|
|
3897
|
+
all_urls: allUrls,
|
|
3898
|
+
error: errorMessage,
|
|
3899
|
+
credits_cost: job.credits_cost,
|
|
3900
|
+
created_at: job.created_at,
|
|
3901
|
+
completed_at: job.completed_at,
|
|
3902
|
+
model_requested: modelRequested,
|
|
3903
|
+
model_delivered: modelDelivered,
|
|
3904
|
+
fallback_reason: fallbackReason,
|
|
3905
|
+
id: job.id,
|
|
3906
|
+
jobId: job.id,
|
|
3907
|
+
jobType: job.job_type,
|
|
3908
|
+
resultUrl,
|
|
3909
|
+
credits: job.credits_cost,
|
|
3910
|
+
error_message: errorMessage,
|
|
3911
|
+
createdAt: job.created_at,
|
|
3912
|
+
completedAt: job.completed_at,
|
|
3913
|
+
modelRequested,
|
|
3914
|
+
modelDelivered,
|
|
3915
|
+
fallbackReason
|
|
3916
|
+
};
|
|
3917
|
+
}
|
|
3918
|
+
var init_checkStatusShape = __esm({
|
|
3919
|
+
"src/lib/checkStatusShape.ts"() {
|
|
3920
|
+
"use strict";
|
|
3921
|
+
}
|
|
3922
|
+
});
|
|
3923
|
+
|
|
3762
3924
|
// src/lib/budget.ts
|
|
3763
3925
|
function getCreditsUsed() {
|
|
3764
3926
|
const ctx = requestContext.getStore();
|
|
@@ -3848,34 +4010,32 @@ function asEnvelope2(data) {
|
|
|
3848
4010
|
function registerContentTools(server) {
|
|
3849
4011
|
server.tool(
|
|
3850
4012
|
"generate_video",
|
|
3851
|
-
"Start an async AI video generation job \u2014 returns a job_id immediately. Poll with check_status every 10-30s until complete.
|
|
4013
|
+
"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). Check get_credit_balance first for expensive generations.",
|
|
3852
4014
|
{
|
|
3853
4015
|
prompt: z2.string().max(2500).describe(
|
|
3854
4016
|
'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.'
|
|
3855
4017
|
),
|
|
3856
|
-
model: z2.enum(
|
|
3857
|
-
"veo3-fast"
|
|
3858
|
-
"veo3-quality",
|
|
3859
|
-
"runway-aleph",
|
|
3860
|
-
"sora2",
|
|
3861
|
-
"sora2-pro",
|
|
3862
|
-
"kling",
|
|
3863
|
-
"kling-3",
|
|
3864
|
-
"kling-3-pro"
|
|
3865
|
-
]).describe(
|
|
3866
|
-
"Video model. veo3-fast: fastest (~15 credits/5s, ~60s render). veo3-quality: highest quality (~20 credits/5s, ~120s). sora2-pro: OpenAI premium (~60 credits/10s). kling-3: 4K with audio (~30 credits/5s). kling-3-pro: best Kling quality (~40 credits/5s)."
|
|
4018
|
+
model: z2.enum(VIDEO_MODEL_ENUM).describe(
|
|
4019
|
+
"Video model, quality ladder best->worst: seedance-2-fast (264cr/8s, S-tier, native audio, top quality/credit) > kling-3 (100cr/5s no-audio, compound prompts, up to 10s) > grok-imagine (30cr/6s, cheapest real model, great image-to-video) > veo3-fast (65cr/8s, Veo 3.1 Fast, native audio) > kling-3-pro (135cr/5s no-audio, up to 15s) > seedance-2 (328cr/8s 720p, premium cinematic, native audio) > veo3-quality (1000cr/8s, Veo 3.1 Quality, hero shots) > wan-2.6 (105cr/5s, fallback) > gemini-omni-video (126cr \u2014 pick this ONLY for multi-reference composites/edits, not raw quality). hailuo-02-standard (180cr), seedance-1.5-pro (150cr), kling (170cr/10s no-audio) are legacy/budget fallbacks."
|
|
3867
4020
|
),
|
|
3868
4021
|
duration: z2.number().min(3).max(30).optional().describe(
|
|
3869
|
-
"Video duration in seconds. kling: 5
|
|
4022
|
+
"Video duration in seconds. kling: 5 or 10s \xB7 kling-3: 5 or 10s \xB7 kling-3-pro: 5/10/15s \xB7 grok-imagine/hailuo: 6 or 10s \xB7 wan-2.6: 5/10/15s \xB7 seedance family: 4-15s \xB7 veo3-fast/veo3-quality: fixed 8s (duration ignored). Out-of-range values are clamped server-side, never rejected. Defaults to 5 seconds."
|
|
3870
4023
|
),
|
|
3871
4024
|
aspect_ratio: z2.enum(["16:9", "9:16", "1:1"]).optional().describe(
|
|
3872
4025
|
"Video aspect ratio. 16:9 for YouTube/landscape, 9:16 for TikTok/Reels/Shorts, 1:1 for Instagram feed/square. Defaults to 16:9."
|
|
3873
4026
|
),
|
|
3874
4027
|
enable_audio: z2.boolean().optional().describe(
|
|
3875
|
-
"Enable native audio generation.
|
|
4028
|
+
"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). seedance-2/-fast generate audio natively regardless. 5+ languages."
|
|
4029
|
+
),
|
|
4030
|
+
image_url: z2.string().optional().describe(
|
|
4031
|
+
"Start frame image URL for image-to-video (Kling 3.0 frame control)."
|
|
4032
|
+
),
|
|
4033
|
+
end_frame_url: z2.string().optional().describe(
|
|
4034
|
+
"End frame image URL (Kling 3.0 only). Enables seamless loop transitions."
|
|
4035
|
+
),
|
|
4036
|
+
project_id: z2.string().optional().describe(
|
|
4037
|
+
"Project ID to associate the video with (brand context is auto-injected from the project brand profile). Omit to generate without a project association."
|
|
3876
4038
|
),
|
|
3877
|
-
image_url: z2.string().optional().describe("Start frame image URL for image-to-video (Kling 3.0 frame control)."),
|
|
3878
|
-
end_frame_url: z2.string().optional().describe("End frame image URL (Kling 3.0 only). Enables seamless loop transitions."),
|
|
3879
4039
|
response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
3880
4040
|
},
|
|
3881
4041
|
async ({
|
|
@@ -3886,6 +4046,7 @@ function registerContentTools(server) {
|
|
|
3886
4046
|
enable_audio,
|
|
3887
4047
|
image_url,
|
|
3888
4048
|
end_frame_url,
|
|
4049
|
+
project_id,
|
|
3889
4050
|
response_format
|
|
3890
4051
|
}) => {
|
|
3891
4052
|
const format = response_format ?? "text";
|
|
@@ -3924,9 +4085,14 @@ function registerContentTools(server) {
|
|
|
3924
4085
|
model,
|
|
3925
4086
|
duration: duration ?? 5,
|
|
3926
4087
|
aspectRatio: aspect_ratio ?? "16:9",
|
|
3927
|
-
|
|
4088
|
+
// Default FALSE (2026-07-13): the old `?? true` default silently multiplied
|
|
4089
|
+
// kling-family costs (2.65x on kling 2.6) for callers that never asked for audio.
|
|
4090
|
+
enableAudio: enable_audio ?? false,
|
|
3928
4091
|
...image_url && { imageUrl: image_url },
|
|
3929
|
-
...end_frame_url && { endFrameUrl: end_frame_url }
|
|
4092
|
+
...end_frame_url && { endFrameUrl: end_frame_url },
|
|
4093
|
+
// The server reads projectId and enforces
|
|
4094
|
+
// ownership via resolveProjectAndContent (index.ts:416-423).
|
|
4095
|
+
...project_id && { projectId: project_id }
|
|
3930
4096
|
},
|
|
3931
4097
|
{ timeoutMs: 3e4 }
|
|
3932
4098
|
);
|
|
@@ -4124,7 +4290,7 @@ function registerContentTools(server) {
|
|
|
4124
4290
|
);
|
|
4125
4291
|
server.tool(
|
|
4126
4292
|
"check_status",
|
|
4127
|
-
'Poll an async job started by generate_video or generate_image. Returns status (queued/processing/completed/failed), progress %, and result URL on completion. Poll every 10-30s for video, 5-15s for images. On "failed" status, the error field explains why \u2014 check credits or try a different model.',
|
|
4293
|
+
'Poll an async job started by generate_video or generate_image. Returns status (queued/processing/completed/failed), progress %, and result URL on completion. Poll every 10-30s for video, 5-15s for images. On "failed" status, the error field explains why \u2014 check credits or try a different model. JSON shape (response_format=json) is stable across the whole poll lifecycle \u2014 canonical fields job_id, status, progress, result_url, r2_key, all_urls, error, credits_cost, created_at, completed_at are always present (never only camelCase or only snake_case); legacy aliases jobId/resultUrl/credits/createdAt/completedAt/error_message are also always populated for backward compatibility.',
|
|
4128
4294
|
{
|
|
4129
4295
|
job_id: z2.string().describe(
|
|
4130
4296
|
"The job ID returned by generate_video or generate_image. This is the asyncJobId or taskId value."
|
|
@@ -4170,10 +4336,13 @@ function registerContentTools(server) {
|
|
|
4170
4336
|
};
|
|
4171
4337
|
}
|
|
4172
4338
|
if (job.external_id && (job.status === "pending" || job.status === "processing")) {
|
|
4173
|
-
const { data: liveStatus } = await callEdgeFunction(
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4339
|
+
const { data: liveStatus } = await callEdgeFunction(
|
|
4340
|
+
"kie-task-status",
|
|
4341
|
+
{
|
|
4342
|
+
taskId: job.external_id,
|
|
4343
|
+
model: job.model
|
|
4344
|
+
}
|
|
4345
|
+
);
|
|
4177
4346
|
if (liveStatus) {
|
|
4178
4347
|
const lines2 = [
|
|
4179
4348
|
`Job: ${job.id}`,
|
|
@@ -4196,13 +4365,17 @@ function registerContentTools(server) {
|
|
|
4196
4365
|
{
|
|
4197
4366
|
type: "text",
|
|
4198
4367
|
text: JSON.stringify(
|
|
4368
|
+
// v1.8.0: spread legacy live-branch fields first for
|
|
4369
|
+
// backward compatibility, then overlay
|
|
4370
|
+
// buildCheckStatusPayload's canonical snake_case fields +
|
|
4371
|
+
// both-alias set so a consumer never sees a different
|
|
4372
|
+
// field name depending on which branch served the poll.
|
|
4373
|
+
// lib/checkStatusShape.ts is the single stable shape —
|
|
4374
|
+
// it derives jobId/jobType/model/credits/createdAt from
|
|
4375
|
+
// `job` + `liveStatus`; do not duplicate them here.
|
|
4199
4376
|
asEnvelope2({
|
|
4200
|
-
jobId: job.id,
|
|
4201
|
-
jobType: job.job_type,
|
|
4202
|
-
model: job.model,
|
|
4203
4377
|
...liveStatus,
|
|
4204
|
-
|
|
4205
|
-
createdAt: job.created_at
|
|
4378
|
+
...buildCheckStatusPayload(job, liveStatus)
|
|
4206
4379
|
}),
|
|
4207
4380
|
null,
|
|
4208
4381
|
2
|
|
@@ -4229,7 +4402,7 @@ function registerContentTools(server) {
|
|
|
4229
4402
|
const filename = segments[segments.length - 1] || "media";
|
|
4230
4403
|
lines.push(`Media ready: ${filename}`);
|
|
4231
4404
|
lines.push(
|
|
4232
|
-
"(Pass job_id directly to schedule_post, or
|
|
4405
|
+
"(Pass job_id directly to schedule_post, or pass response_format=json's r2_key to get_media_url for a download link)"
|
|
4233
4406
|
);
|
|
4234
4407
|
} else {
|
|
4235
4408
|
lines.push(`Result URL: ${job.result_url}`);
|
|
@@ -4253,11 +4426,15 @@ function registerContentTools(server) {
|
|
|
4253
4426
|
if (format === "json") {
|
|
4254
4427
|
const enriched = {
|
|
4255
4428
|
...job,
|
|
4256
|
-
|
|
4257
|
-
all_urls: allUrls ?? null
|
|
4429
|
+
...buildCheckStatusPayload(job)
|
|
4258
4430
|
};
|
|
4259
4431
|
return {
|
|
4260
|
-
content: [
|
|
4432
|
+
content: [
|
|
4433
|
+
{
|
|
4434
|
+
type: "text",
|
|
4435
|
+
text: JSON.stringify(asEnvelope2(enriched), null, 2)
|
|
4436
|
+
}
|
|
4437
|
+
]
|
|
4261
4438
|
};
|
|
4262
4439
|
}
|
|
4263
4440
|
return {
|
|
@@ -4275,7 +4452,15 @@ function registerContentTools(server) {
|
|
|
4275
4452
|
brand_context: z2.string().max(3e3).optional().describe(
|
|
4276
4453
|
"Brand context JSON from extract_brand. Include colors, voice tone, visual style keywords for consistent branding across frames."
|
|
4277
4454
|
),
|
|
4278
|
-
platform: z2.enum([
|
|
4455
|
+
platform: z2.enum([
|
|
4456
|
+
"tiktok",
|
|
4457
|
+
"instagram-reels",
|
|
4458
|
+
"youtube-shorts",
|
|
4459
|
+
"youtube",
|
|
4460
|
+
"general"
|
|
4461
|
+
]).describe(
|
|
4462
|
+
"Target platform. Determines aspect ratio, duration, and pacing."
|
|
4463
|
+
),
|
|
4279
4464
|
target_duration: z2.number().min(5).max(120).optional().describe(
|
|
4280
4465
|
"Target total duration in seconds. Defaults to 30s for short-form, 60s for YouTube."
|
|
4281
4466
|
),
|
|
@@ -4283,7 +4468,9 @@ function registerContentTools(server) {
|
|
|
4283
4468
|
style: z2.string().optional().describe(
|
|
4284
4469
|
'Visual style direction (e.g., "cinematic", "anime", "documentary", "motion graphics").'
|
|
4285
4470
|
),
|
|
4286
|
-
response_format: z2.enum(["text", "json"]).optional().describe(
|
|
4471
|
+
response_format: z2.enum(["text", "json"]).optional().describe(
|
|
4472
|
+
"Response format. Defaults to json for structured storyboard data."
|
|
4473
|
+
)
|
|
4287
4474
|
},
|
|
4288
4475
|
async ({
|
|
4289
4476
|
concept,
|
|
@@ -4295,7 +4482,11 @@ function registerContentTools(server) {
|
|
|
4295
4482
|
response_format
|
|
4296
4483
|
}) => {
|
|
4297
4484
|
const format = response_format ?? "json";
|
|
4298
|
-
const isShortForm = [
|
|
4485
|
+
const isShortForm = [
|
|
4486
|
+
"tiktok",
|
|
4487
|
+
"instagram-reels",
|
|
4488
|
+
"youtube-shorts"
|
|
4489
|
+
].includes(platform3);
|
|
4299
4490
|
const duration = target_duration ?? (isShortForm ? 30 : 60);
|
|
4300
4491
|
const scenes = num_scenes ?? (isShortForm ? 7 : 10);
|
|
4301
4492
|
const aspectRatio = isShortForm ? "9:16" : "16:9";
|
|
@@ -4376,7 +4567,12 @@ Return ONLY valid JSON in this exact format:
|
|
|
4376
4567
|
);
|
|
4377
4568
|
if (error) {
|
|
4378
4569
|
return {
|
|
4379
|
-
content: [
|
|
4570
|
+
content: [
|
|
4571
|
+
{
|
|
4572
|
+
type: "text",
|
|
4573
|
+
text: `Storyboard generation failed: ${error}`
|
|
4574
|
+
}
|
|
4575
|
+
],
|
|
4380
4576
|
isError: true
|
|
4381
4577
|
};
|
|
4382
4578
|
}
|
|
@@ -4386,7 +4582,12 @@ Return ONLY valid JSON in this exact format:
|
|
|
4386
4582
|
try {
|
|
4387
4583
|
const parsed = JSON.parse(rawContent);
|
|
4388
4584
|
return {
|
|
4389
|
-
content: [
|
|
4585
|
+
content: [
|
|
4586
|
+
{
|
|
4587
|
+
type: "text",
|
|
4588
|
+
text: JSON.stringify(asEnvelope2(parsed), null, 2)
|
|
4589
|
+
}
|
|
4590
|
+
]
|
|
4390
4591
|
};
|
|
4391
4592
|
} catch {
|
|
4392
4593
|
return {
|
|
@@ -4431,7 +4632,10 @@ Return ONLY valid JSON in this exact format:
|
|
|
4431
4632
|
isError: true
|
|
4432
4633
|
};
|
|
4433
4634
|
}
|
|
4434
|
-
const rateLimit = checkRateLimit(
|
|
4635
|
+
const rateLimit = checkRateLimit(
|
|
4636
|
+
"posting",
|
|
4637
|
+
`generate_voiceover:${userId}`
|
|
4638
|
+
);
|
|
4435
4639
|
if (!rateLimit.allowed) {
|
|
4436
4640
|
return {
|
|
4437
4641
|
content: [
|
|
@@ -4458,14 +4662,22 @@ Return ONLY valid JSON in this exact format:
|
|
|
4458
4662
|
);
|
|
4459
4663
|
if (error) {
|
|
4460
4664
|
return {
|
|
4461
|
-
content: [
|
|
4665
|
+
content: [
|
|
4666
|
+
{
|
|
4667
|
+
type: "text",
|
|
4668
|
+
text: `Voiceover generation failed: ${error}`
|
|
4669
|
+
}
|
|
4670
|
+
],
|
|
4462
4671
|
isError: true
|
|
4463
4672
|
};
|
|
4464
4673
|
}
|
|
4465
4674
|
if (!data?.audioUrl) {
|
|
4466
4675
|
return {
|
|
4467
4676
|
content: [
|
|
4468
|
-
{
|
|
4677
|
+
{
|
|
4678
|
+
type: "text",
|
|
4679
|
+
text: "Voiceover generation failed: no audio URL returned."
|
|
4680
|
+
}
|
|
4469
4681
|
],
|
|
4470
4682
|
isError: true
|
|
4471
4683
|
};
|
|
@@ -4527,20 +4739,30 @@ Return ONLY valid JSON in this exact format:
|
|
|
4527
4739
|
"Carousel template. hormozi-authority: bold typography, one idea per slide, dark backgrounds. educational-series: numbered tips. Default: hormozi-authority."
|
|
4528
4740
|
),
|
|
4529
4741
|
slide_count: z2.number().min(3).max(10).optional().describe("Number of slides (3-10). Default: 7."),
|
|
4530
|
-
aspect_ratio: z2.enum(["1:1", "4:5", "9:16"]).optional().describe(
|
|
4742
|
+
aspect_ratio: z2.enum(["1:1", "4:5", "9:16"]).optional().describe(
|
|
4743
|
+
"Aspect ratio. 1:1 square (default), 4:5 portrait, 9:16 story."
|
|
4744
|
+
),
|
|
4531
4745
|
style: z2.enum(["minimal", "bold", "professional", "playful", "hormozi"]).optional().describe(
|
|
4532
4746
|
"Visual style. hormozi: black bg, bold white text, gold accents. Default: hormozi (when using hormozi-authority template)."
|
|
4533
4747
|
),
|
|
4534
4748
|
hook: z2.string().max(300).optional().describe(
|
|
4535
4749
|
"Explicit hook/opener for slide 1. Overrides any hook derived from topic. Keep under 15 words."
|
|
4536
4750
|
),
|
|
4537
|
-
hook_family: z2.enum([
|
|
4538
|
-
"
|
|
4751
|
+
hook_family: z2.enum([
|
|
4752
|
+
"curiosity",
|
|
4753
|
+
"authority",
|
|
4754
|
+
"pain_point",
|
|
4755
|
+
"contrarian",
|
|
4756
|
+
"data_driven"
|
|
4757
|
+
]).optional().describe(
|
|
4758
|
+
"Hook family tag. Persisted with the carousel so downstream analytics can attribute engagement to hook pattern."
|
|
4539
4759
|
),
|
|
4540
4760
|
cta_text: z2.string().max(200).optional().describe("Explicit CTA copy for the final slide."),
|
|
4541
4761
|
cta_url: z2.string().url().optional().describe("URL promoted on the CTA slide."),
|
|
4542
4762
|
tone: z2.string().max(200).optional().describe("Voice/tone override. Composes with brand profile voice."),
|
|
4543
|
-
constraints: z2.string().max(500).optional().describe(
|
|
4763
|
+
constraints: z2.string().max(500).optional().describe(
|
|
4764
|
+
'Content constraints. Example: "No fabricated statistics. Sentence case only."'
|
|
4765
|
+
),
|
|
4544
4766
|
platform: z2.enum(["linkedin", "instagram", "tiktok", "x"]).optional().describe("Target platform. Affects tone and format guardrails."),
|
|
4545
4767
|
project_id: z2.string().optional().describe("Project ID to associate the carousel with."),
|
|
4546
4768
|
response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to json.")
|
|
@@ -4575,7 +4797,10 @@ Return ONLY valid JSON in this exact format:
|
|
|
4575
4797
|
};
|
|
4576
4798
|
}
|
|
4577
4799
|
const userId = await getDefaultUserId();
|
|
4578
|
-
const rateLimit = checkRateLimit(
|
|
4800
|
+
const rateLimit = checkRateLimit(
|
|
4801
|
+
"posting",
|
|
4802
|
+
`generate_carousel:${userId}`
|
|
4803
|
+
);
|
|
4579
4804
|
if (!rateLimit.allowed) {
|
|
4580
4805
|
return {
|
|
4581
4806
|
content: [
|
|
@@ -4608,13 +4833,23 @@ Return ONLY valid JSON in this exact format:
|
|
|
4608
4833
|
);
|
|
4609
4834
|
if (error) {
|
|
4610
4835
|
return {
|
|
4611
|
-
content: [
|
|
4836
|
+
content: [
|
|
4837
|
+
{
|
|
4838
|
+
type: "text",
|
|
4839
|
+
text: `Carousel generation failed: ${error}`
|
|
4840
|
+
}
|
|
4841
|
+
],
|
|
4612
4842
|
isError: true
|
|
4613
4843
|
};
|
|
4614
4844
|
}
|
|
4615
4845
|
if (!data?.carousel) {
|
|
4616
4846
|
return {
|
|
4617
|
-
content: [
|
|
4847
|
+
content: [
|
|
4848
|
+
{
|
|
4849
|
+
type: "text",
|
|
4850
|
+
text: "Carousel generation returned no data."
|
|
4851
|
+
}
|
|
4852
|
+
],
|
|
4618
4853
|
isError: true
|
|
4619
4854
|
};
|
|
4620
4855
|
}
|
|
@@ -4662,7 +4897,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
4662
4897
|
}
|
|
4663
4898
|
);
|
|
4664
4899
|
}
|
|
4665
|
-
var VIDEO_CREDIT_ESTIMATES, IMAGE_CREDIT_ESTIMATES;
|
|
4900
|
+
var VIDEO_CREDIT_ESTIMATES, VIDEO_MODEL_ENUM, IMAGE_CREDIT_ESTIMATES;
|
|
4666
4901
|
var init_content2 = __esm({
|
|
4667
4902
|
"src/tools/content.ts"() {
|
|
4668
4903
|
"use strict";
|
|
@@ -4670,17 +4905,36 @@ var init_content2 = __esm({
|
|
|
4670
4905
|
init_rate_limit();
|
|
4671
4906
|
init_supabase();
|
|
4672
4907
|
init_version();
|
|
4908
|
+
init_checkStatusShape();
|
|
4673
4909
|
init_budget();
|
|
4674
4910
|
VIDEO_CREDIT_ESTIMATES = {
|
|
4675
|
-
"
|
|
4676
|
-
"veo3-quality": 1e3,
|
|
4677
|
-
"runway-aleph": 340,
|
|
4678
|
-
sora2: 500,
|
|
4679
|
-
"sora2-pro": 1500,
|
|
4680
|
-
kling: 170,
|
|
4911
|
+
"seedance-2-fast": 264,
|
|
4681
4912
|
"kling-3": 100,
|
|
4682
|
-
"
|
|
4913
|
+
"grok-imagine": 30,
|
|
4914
|
+
"veo3-fast": 65,
|
|
4915
|
+
"kling-3-pro": 135,
|
|
4916
|
+
"seedance-2": 328,
|
|
4917
|
+
"veo3-quality": 1e3,
|
|
4918
|
+
"wan-2.6": 105,
|
|
4919
|
+
"gemini-omni-video": 126,
|
|
4920
|
+
"hailuo-02-standard": 180,
|
|
4921
|
+
"seedance-1.5-pro": 150,
|
|
4922
|
+
kling: 170
|
|
4683
4923
|
};
|
|
4924
|
+
VIDEO_MODEL_ENUM = [
|
|
4925
|
+
"seedance-2-fast",
|
|
4926
|
+
"kling-3",
|
|
4927
|
+
"grok-imagine",
|
|
4928
|
+
"veo3-fast",
|
|
4929
|
+
"kling-3-pro",
|
|
4930
|
+
"seedance-2",
|
|
4931
|
+
"veo3-quality",
|
|
4932
|
+
"wan-2.6",
|
|
4933
|
+
"gemini-omni-video",
|
|
4934
|
+
"hailuo-02-standard",
|
|
4935
|
+
"seedance-1.5-pro",
|
|
4936
|
+
"kling"
|
|
4937
|
+
];
|
|
4684
4938
|
IMAGE_CREDIT_ESTIMATES = {
|
|
4685
4939
|
midjourney: 20,
|
|
4686
4940
|
"nano-banana": 15,
|
|
@@ -4750,6 +5004,7 @@ var init_sanitize_error = __esm({
|
|
|
4750
5004
|
});
|
|
4751
5005
|
|
|
4752
5006
|
// src/lib/ssrf.ts
|
|
5007
|
+
import { promises as dnsPromises } from "node:dns";
|
|
4753
5008
|
function isBlockedIP(ip) {
|
|
4754
5009
|
const normalized = ip.replace(/^\[/, "").replace(/\]$/, "");
|
|
4755
5010
|
if (normalized.includes(":")) {
|
|
@@ -4803,8 +5058,7 @@ async function validateUrlForSSRF(urlString) {
|
|
|
4803
5058
|
let resolvedIP;
|
|
4804
5059
|
if (!isIPAddress(hostname)) {
|
|
4805
5060
|
try {
|
|
4806
|
-
const
|
|
4807
|
-
const resolver = new dns.promises.Resolver();
|
|
5061
|
+
const resolver = new dnsPromises.Resolver();
|
|
4808
5062
|
const resolvedIPs = [];
|
|
4809
5063
|
try {
|
|
4810
5064
|
const aRecords = await resolver.resolve4(hostname);
|
|
@@ -4930,6 +5184,24 @@ function asEnvelope3(data) {
|
|
|
4930
5184
|
data
|
|
4931
5185
|
};
|
|
4932
5186
|
}
|
|
5187
|
+
function accountEffectiveStatus(account) {
|
|
5188
|
+
return account.effective_status || account.status;
|
|
5189
|
+
}
|
|
5190
|
+
function isUsableAccount(account) {
|
|
5191
|
+
const status = accountEffectiveStatus(account);
|
|
5192
|
+
return status === "active" || status === "expires_soon";
|
|
5193
|
+
}
|
|
5194
|
+
function formatAccountChoice(account) {
|
|
5195
|
+
const name = account.username ? `@${account.username}` : "unnamed";
|
|
5196
|
+
const project = account.project_id ? `project_id=${account.project_id}` : "project_id=unassigned";
|
|
5197
|
+
const refresh = account.has_refresh_token ? "OAuth 2.0 refresh" : "no refresh token";
|
|
5198
|
+
return `${account.id} (${name}, ${project}, ${accountEffectiveStatus(account)}, ${refresh})`;
|
|
5199
|
+
}
|
|
5200
|
+
function requestedAccountIdForPlatform(accountId, accountIds, platform3) {
|
|
5201
|
+
if (accountId) return accountId;
|
|
5202
|
+
if (!accountIds) return void 0;
|
|
5203
|
+
return accountIds[platform3] || accountIds[platform3.toLowerCase()];
|
|
5204
|
+
}
|
|
4933
5205
|
function isAlreadyR2Signed(url) {
|
|
4934
5206
|
try {
|
|
4935
5207
|
const u = new URL(url);
|
|
@@ -5073,16 +5345,18 @@ function registerDistributionTools(server) {
|
|
|
5073
5345
|
schedule_at: z3.string().optional().describe(
|
|
5074
5346
|
'ISO 8601 UTC datetime for scheduled posting (e.g. "2026-03-20T14:00:00Z"). Omit to post immediately. Must be in the future.'
|
|
5075
5347
|
),
|
|
5076
|
-
project_id: z3.string().optional().describe(
|
|
5348
|
+
project_id: z3.string().optional().describe(
|
|
5349
|
+
"Social Neuron brand/project ID to associate this post with. Provide this when the account has multiple brands so brand voice and connected account routing stay scoped to the right brand."
|
|
5350
|
+
),
|
|
5077
5351
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text."),
|
|
5078
5352
|
attribution: z3.boolean().optional().describe(
|
|
5079
5353
|
'If true, appends "Created with Social Neuron" to the caption. Default: false.'
|
|
5080
5354
|
),
|
|
5081
5355
|
account_id: z3.string().optional().describe(
|
|
5082
|
-
"Connected account ID to post from. Use list_connected_accounts to find the right ID. Required when multiple accounts exist for the same platform."
|
|
5356
|
+
"Connected account ID to post from. Use list_connected_accounts to find the right ID. Required when multiple accounts exist for the same platform. If project_id is provided, the account must belong to that project or be unassigned."
|
|
5083
5357
|
),
|
|
5084
5358
|
account_ids: z3.record(z3.string(), z3.string()).optional().describe(
|
|
5085
|
-
'Per-platform account IDs when posting to multiple platforms. Example: {"twitter": "abc123", "instagram": "def456"}. Use list_connected_accounts to find IDs.'
|
|
5359
|
+
'Per-platform account IDs when posting to multiple platforms. Example: {"twitter": "abc123", "instagram": "def456"}. Use list_connected_accounts with the same project_id to find IDs.'
|
|
5086
5360
|
),
|
|
5087
5361
|
auto_rehost: z3.boolean().optional().describe(
|
|
5088
5362
|
"Whether to persist non-R2 media_url/media_urls into R2 before posting. Default: true. Set to false only if you know the source URL will outlive the scheduling window and every target platform supports URL ingest."
|
|
@@ -5292,22 +5566,53 @@ function registerDistributionTools(server) {
|
|
|
5292
5566
|
isError: true
|
|
5293
5567
|
};
|
|
5294
5568
|
}
|
|
5295
|
-
const { data: accountsData } = await callEdgeFunction(
|
|
5569
|
+
const { data: accountsData } = await callEdgeFunction(
|
|
5570
|
+
"mcp-data",
|
|
5571
|
+
{
|
|
5572
|
+
action: "connected-accounts",
|
|
5573
|
+
...project_id ? { projectId: project_id, project_id } : {}
|
|
5574
|
+
},
|
|
5575
|
+
{ timeoutMs: 1e4 }
|
|
5576
|
+
);
|
|
5296
5577
|
if (accountsData?.accounts) {
|
|
5297
5578
|
const accounts = accountsData.accounts;
|
|
5298
5579
|
const issues = [];
|
|
5299
5580
|
for (const platform3 of normalizedPlatforms) {
|
|
5300
5581
|
const platformAccounts = accounts.filter(
|
|
5301
|
-
(a) => a.platform.toLowerCase() === platform3.toLowerCase() && a
|
|
5582
|
+
(a) => a.platform.toLowerCase() === platform3.toLowerCase() && isUsableAccount(a)
|
|
5583
|
+
);
|
|
5584
|
+
const requestedAccountId = requestedAccountIdForPlatform(
|
|
5585
|
+
account_id,
|
|
5586
|
+
account_ids,
|
|
5587
|
+
platform3
|
|
5302
5588
|
);
|
|
5589
|
+
if (requestedAccountId) {
|
|
5590
|
+
const selected = accounts.find((a) => a.id === requestedAccountId);
|
|
5591
|
+
if (!selected) {
|
|
5592
|
+
issues.push(
|
|
5593
|
+
`${platform3}: Account "${requestedAccountId}" is not available${project_id ? ` for project_id ${project_id}` : ""}. Call \`list_connected_accounts\`${project_id ? " with the same project_id" : ""} and choose one of the returned IDs.`
|
|
5594
|
+
);
|
|
5595
|
+
} else if (selected.platform.toLowerCase() !== platform3.toLowerCase()) {
|
|
5596
|
+
issues.push(
|
|
5597
|
+
`${platform3}: Account "${requestedAccountId}" belongs to ${selected.platform}, not ${platform3}.`
|
|
5598
|
+
);
|
|
5599
|
+
} else if (!isUsableAccount(selected)) {
|
|
5600
|
+
issues.push(
|
|
5601
|
+
`${platform3}: Account "${selected.username || selected.id}" is ${accountEffectiveStatus(selected)}. Reconnect at socialneuron.com/settings/connections.`
|
|
5602
|
+
);
|
|
5603
|
+
} else if (project_id && selected.project_id && selected.project_id !== project_id) {
|
|
5604
|
+
issues.push(
|
|
5605
|
+
`${platform3}: Account "${selected.username || selected.id}" is bound to project_id ${selected.project_id}, not ${project_id}. Use the selected brand's account or reconnect/assign it in Settings > Integrations.`
|
|
5606
|
+
);
|
|
5607
|
+
}
|
|
5608
|
+
continue;
|
|
5609
|
+
}
|
|
5303
5610
|
if (platformAccounts.length === 0) {
|
|
5304
5611
|
issues.push(
|
|
5305
|
-
`${platform3}: not connected yet. This is a one-time browser setup on socialneuron.com \u2014 NOT another OAuth in Claude. Call \`start_platform_connection\` with platform="${platform3.toLowerCase()}" to get a deep link, ask the user to open it in their browser and approve on the platform, then call \`wait_for_connection\` before retrying schedule_post.`
|
|
5612
|
+
`${platform3}: not connected yet. This is a one-time browser setup on socialneuron.com \u2014 NOT another OAuth in Claude. Call \`start_platform_connection\` with platform="${platform3.toLowerCase()}"${project_id ? ` and project_id="${project_id}"` : ""} to get a deep link, ask the user to open it in their browser and approve on the platform, then call \`wait_for_connection\` before retrying schedule_post.`
|
|
5306
5613
|
);
|
|
5307
|
-
} else if (platformAccounts.length > 1
|
|
5308
|
-
const accountList = platformAccounts.map(
|
|
5309
|
-
(a) => ` - ${a.id} (${a.username || "unnamed"}${a.has_refresh_token ? ", OAuth 2.0" : ", no refresh token"})`
|
|
5310
|
-
).join("\n");
|
|
5614
|
+
} else if (platformAccounts.length > 1) {
|
|
5615
|
+
const accountList = platformAccounts.map((a) => ` - ${formatAccountChoice(a)}`).join("\n");
|
|
5311
5616
|
issues.push(
|
|
5312
5617
|
`${platform3}: Multiple accounts found. Specify account_id or account_ids to choose:
|
|
5313
5618
|
${accountList}`
|
|
@@ -5462,13 +5767,21 @@ Created with Social Neuron`;
|
|
|
5462
5767
|
);
|
|
5463
5768
|
server.tool(
|
|
5464
5769
|
"list_connected_accounts",
|
|
5465
|
-
"Check which social platforms have active OAuth connections for posting. Call this before schedule_post to verify credentials. If a platform is missing or expired, the user needs to reconnect at socialneuron.com/settings/connections.",
|
|
5770
|
+
"Check which social platforms have active OAuth connections for posting. Call this before schedule_post to verify credentials. Pass project_id to list the accounts for a specific brand/project, then pass the returned account id as account_id/account_ids when posting. If a platform is missing or expired, the user needs to reconnect at socialneuron.com/settings/connections.",
|
|
5466
5771
|
{
|
|
5772
|
+
project_id: z3.string().optional().describe(
|
|
5773
|
+
"Brand/project ID to scope connected accounts. Use the same project_id when calling schedule_post."
|
|
5774
|
+
),
|
|
5775
|
+
include_all: z3.boolean().optional().describe("If true, include expired or inactive accounts as well as usable accounts."),
|
|
5467
5776
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
5468
5777
|
},
|
|
5469
|
-
async ({ response_format }) => {
|
|
5778
|
+
async ({ project_id, include_all, response_format }) => {
|
|
5470
5779
|
const format = response_format ?? "text";
|
|
5471
|
-
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
5780
|
+
const { data: result, error: efError } = await callEdgeFunction("mcp-data", {
|
|
5781
|
+
action: "connected-accounts",
|
|
5782
|
+
...project_id ? { projectId: project_id, project_id } : {},
|
|
5783
|
+
...include_all ? { includeAll: true } : {}
|
|
5784
|
+
});
|
|
5472
5785
|
if (efError || !result?.success) {
|
|
5473
5786
|
return {
|
|
5474
5787
|
content: [
|
|
@@ -5501,12 +5814,17 @@ Created with Social Neuron`;
|
|
|
5501
5814
|
]
|
|
5502
5815
|
};
|
|
5503
5816
|
}
|
|
5504
|
-
const lines = [
|
|
5817
|
+
const lines = [
|
|
5818
|
+
`${accounts.length} connected account(s)${project_id ? ` for project ${project_id}` : ""}:`,
|
|
5819
|
+
""
|
|
5820
|
+
];
|
|
5505
5821
|
for (const account of accounts) {
|
|
5506
5822
|
const name = account.username || "(unnamed)";
|
|
5507
5823
|
const platformLower = account.platform.toLowerCase();
|
|
5824
|
+
const project = account.project_id ? `project_id=${account.project_id}` : "project_id=unassigned";
|
|
5825
|
+
const status = accountEffectiveStatus(account);
|
|
5508
5826
|
lines.push(
|
|
5509
|
-
` ${platformLower}: ${name} (connected ${account.created_at.split("T")[0]})`
|
|
5827
|
+
` ${platformLower}: ${name} | id=${account.id} | ${project} | status=${status} (connected ${account.created_at.split("T")[0]})`
|
|
5510
5828
|
);
|
|
5511
5829
|
}
|
|
5512
5830
|
if (format === "json") {
|
|
@@ -6714,7 +7032,8 @@ function registerMediaTools(server) {
|
|
|
6714
7032
|
},
|
|
6715
7033
|
async ({ r2_key, response_format }) => {
|
|
6716
7034
|
const format = response_format ?? "text";
|
|
6717
|
-
const
|
|
7035
|
+
const normalizedR2Key = r2_key.startsWith("r2://") ? r2_key.slice("r2://".length) : r2_key;
|
|
7036
|
+
const { data, error } = await callEdgeFunction("get-signed-url", { r2Key: normalizedR2Key, operation: "get" }, { timeoutMs: 1e4 });
|
|
6718
7037
|
const signedDownloadUrl = data?.url ?? data?.signedUrl;
|
|
6719
7038
|
if (error) {
|
|
6720
7039
|
return {
|
|
@@ -14122,7 +14441,7 @@ function registerCarouselTools(server) {
|
|
|
14122
14441
|
"Explicit hook/opener for slide 1. Overrides any hook derived from topic. Keep under 15 words for strongest scroll-stop."
|
|
14123
14442
|
),
|
|
14124
14443
|
hook_family: z28.enum(["curiosity", "authority", "pain_point", "contrarian", "data_driven"]).optional().describe(
|
|
14125
|
-
"Hook family tag. Recorded on the carousel so downstream analytics
|
|
14444
|
+
"Hook family tag. Recorded on the carousel so downstream analytics can attribute engagement to hook pattern."
|
|
14126
14445
|
),
|
|
14127
14446
|
cta_text: z28.string().max(200).optional().describe("Explicit CTA copy for the final slide. If omitted, derived from topic."),
|
|
14128
14447
|
cta_url: z28.string().url().optional().describe("URL promoted on the CTA slide. Defaults to the project landing page."),
|
|
@@ -14869,7 +15188,10 @@ var init_content_calendar = __esm({
|
|
|
14869
15188
|
import { z as z32 } from "zod";
|
|
14870
15189
|
function findActiveAccount(accounts, platform3) {
|
|
14871
15190
|
const target = platform3.toLowerCase();
|
|
14872
|
-
return accounts.find((a) =>
|
|
15191
|
+
return accounts.find((a) => {
|
|
15192
|
+
const effectiveStatus = a.effective_status || a.status;
|
|
15193
|
+
return a.platform.toLowerCase() === target && (effectiveStatus === "active" || effectiveStatus === "expires_soon");
|
|
15194
|
+
}) ?? null;
|
|
14873
15195
|
}
|
|
14874
15196
|
function registerConnectionTools(server) {
|
|
14875
15197
|
server.tool(
|
|
@@ -14877,11 +15199,15 @@ function registerConnectionTools(server) {
|
|
|
14877
15199
|
"Begin connecting a social platform (Instagram, TikTok, YouTube, etc.). Returns a single-use deep link the user opens in a browser to complete the one-time OAuth handshake on socialneuron.com. This is NOT another OAuth in Claude \u2014 platform connections require a browser session because the social platforms (Meta, Google, TikTok) only accept callbacks on socialneuron.com. After the user clicks the link and approves on the platform, call `wait_for_connection` to detect completion. Link expires in 2 minutes; mint a new one if needed. Use `list_connected_accounts` first to check whether the platform is already connected before calling this.",
|
|
14878
15200
|
{
|
|
14879
15201
|
platform: z32.enum(PLATFORM_ENUM4).describe("Platform to connect. Lower-case: instagram, tiktok, youtube, etc."),
|
|
15202
|
+
project_id: z32.string().optional().describe(
|
|
15203
|
+
"Brand/project ID to bind the new social account to. Use this when connecting a brand-specific account."
|
|
15204
|
+
),
|
|
14880
15205
|
response_format: z32.enum(["text", "json"]).optional().describe("Response format. Default: text.")
|
|
14881
15206
|
},
|
|
14882
|
-
async ({ platform: platform3, response_format }) => {
|
|
15207
|
+
async ({ platform: platform3, project_id, response_format }) => {
|
|
14883
15208
|
const format = response_format ?? "text";
|
|
14884
|
-
const
|
|
15209
|
+
const userId = await getDefaultUserId();
|
|
15210
|
+
const rl = checkRateLimit("read", `start_platform_connection:${userId}`);
|
|
14885
15211
|
if (!rl.allowed) {
|
|
14886
15212
|
return {
|
|
14887
15213
|
content: [
|
|
@@ -14893,7 +15219,15 @@ function registerConnectionTools(server) {
|
|
|
14893
15219
|
isError: true
|
|
14894
15220
|
};
|
|
14895
15221
|
}
|
|
14896
|
-
const { data, error } = await callEdgeFunction(
|
|
15222
|
+
const { data, error } = await callEdgeFunction(
|
|
15223
|
+
"mcp-data",
|
|
15224
|
+
{
|
|
15225
|
+
action: "mint-connection-nonce",
|
|
15226
|
+
platform: platform3,
|
|
15227
|
+
...project_id ? { projectId: project_id, project_id } : {}
|
|
15228
|
+
},
|
|
15229
|
+
{ timeoutMs: 1e4 }
|
|
15230
|
+
);
|
|
14897
15231
|
if (error || !data?.success || !data.deep_link) {
|
|
14898
15232
|
const errMsg = error ?? data?.error ?? "Unknown error";
|
|
14899
15233
|
return {
|
|
@@ -14914,6 +15248,7 @@ function registerConnectionTools(server) {
|
|
|
14914
15248
|
text: JSON.stringify(
|
|
14915
15249
|
{
|
|
14916
15250
|
platform: data.platform,
|
|
15251
|
+
project_id: project_id ?? null,
|
|
14917
15252
|
deep_link: data.deep_link,
|
|
14918
15253
|
expires_at: data.expires_at,
|
|
14919
15254
|
next_step: "Open deep_link in a browser, approve on the platform, then call wait_for_connection."
|
|
@@ -14932,6 +15267,7 @@ function registerConnectionTools(server) {
|
|
|
14932
15267
|
type: "text",
|
|
14933
15268
|
text: [
|
|
14934
15269
|
`${data.platform} connection ready.`,
|
|
15270
|
+
...project_id ? [`Brand/project: ${project_id}`] : [],
|
|
14935
15271
|
"",
|
|
14936
15272
|
'Ask the user to open this link in a browser and click "Connect" on the platform:',
|
|
14937
15273
|
` ${data.deep_link}`,
|
|
@@ -14952,17 +15288,21 @@ function registerConnectionTools(server) {
|
|
|
14952
15288
|
"Poll until a platform connection becomes active. Use after `start_platform_connection` while the user completes the browser OAuth flow. Returns when the account row appears with status=active, or when the timeout elapses. Default timeout 120s, max 600s.",
|
|
14953
15289
|
{
|
|
14954
15290
|
platform: z32.enum(PLATFORM_ENUM4).describe("Platform to wait for."),
|
|
15291
|
+
project_id: z32.string().optional().describe(
|
|
15292
|
+
"Brand/project ID to scope the connection poll. Use the same project_id passed to start_platform_connection."
|
|
15293
|
+
),
|
|
14955
15294
|
timeout_s: z32.number().min(5).max(600).optional().describe("How long to wait, in seconds. Default 120."),
|
|
14956
15295
|
poll_interval_s: z32.number().min(2).max(30).optional().describe("Poll interval in seconds. Default 5."),
|
|
14957
15296
|
response_format: z32.enum(["text", "json"]).optional().describe("Response format. Default: text.")
|
|
14958
15297
|
},
|
|
14959
|
-
async ({ platform: platform3, timeout_s, poll_interval_s, response_format }) => {
|
|
15298
|
+
async ({ platform: platform3, project_id, timeout_s, poll_interval_s, response_format }) => {
|
|
14960
15299
|
const format = response_format ?? "text";
|
|
14961
15300
|
const startedAt = Date.now();
|
|
14962
15301
|
const timeoutMs = (timeout_s ?? 120) * 1e3;
|
|
14963
15302
|
const intervalMs = (poll_interval_s ?? 5) * 1e3;
|
|
14964
15303
|
const deadline = startedAt + timeoutMs;
|
|
14965
|
-
const
|
|
15304
|
+
const userId = await getDefaultUserId();
|
|
15305
|
+
const rl = checkRateLimit("read", `wait_for_connection:${userId}`);
|
|
14966
15306
|
if (!rl.allowed) {
|
|
14967
15307
|
return {
|
|
14968
15308
|
content: [
|
|
@@ -14974,90 +15314,123 @@ function registerConnectionTools(server) {
|
|
|
14974
15314
|
isError: true
|
|
14975
15315
|
};
|
|
14976
15316
|
}
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
14983
|
-
|
|
14984
|
-
|
|
15317
|
+
const activeWaits = inFlightWaitsByUser.get(userId) ?? 0;
|
|
15318
|
+
if (activeWaits >= MAX_CONCURRENT_WAITS_PER_USER) {
|
|
15319
|
+
return {
|
|
15320
|
+
content: [
|
|
15321
|
+
{
|
|
15322
|
+
type: "text",
|
|
15323
|
+
text: `Too many concurrent \`wait_for_connection\` calls (max ${MAX_CONCURRENT_WAITS_PER_USER}). Let an existing wait finish, or poll \`list_connected_accounts\` instead.`
|
|
15324
|
+
}
|
|
15325
|
+
],
|
|
15326
|
+
isError: true
|
|
15327
|
+
};
|
|
15328
|
+
}
|
|
15329
|
+
inFlightWaitsByUser.set(userId, activeWaits + 1);
|
|
15330
|
+
try {
|
|
15331
|
+
let attempts = 0;
|
|
15332
|
+
while (Date.now() < deadline) {
|
|
15333
|
+
attempts++;
|
|
15334
|
+
const { data, error } = await callEdgeFunction(
|
|
15335
|
+
"mcp-data",
|
|
15336
|
+
{
|
|
15337
|
+
action: "connected-accounts",
|
|
15338
|
+
...project_id ? { projectId: project_id, project_id } : {}
|
|
15339
|
+
},
|
|
15340
|
+
{ timeoutMs: 1e4 }
|
|
15341
|
+
);
|
|
15342
|
+
if (!error && data?.success) {
|
|
15343
|
+
const found = findActiveAccount(data.accounts ?? [], platform3);
|
|
15344
|
+
if (found) {
|
|
15345
|
+
if (format === "json") {
|
|
15346
|
+
return {
|
|
15347
|
+
content: [
|
|
15348
|
+
{
|
|
15349
|
+
type: "text",
|
|
15350
|
+
text: JSON.stringify(
|
|
15351
|
+
{
|
|
15352
|
+
connected: true,
|
|
15353
|
+
platform: found.platform,
|
|
15354
|
+
project_id: found.project_id ?? project_id ?? null,
|
|
15355
|
+
account_id: found.id,
|
|
15356
|
+
username: found.username,
|
|
15357
|
+
connected_at: found.created_at,
|
|
15358
|
+
attempts
|
|
15359
|
+
},
|
|
15360
|
+
null,
|
|
15361
|
+
2
|
|
15362
|
+
)
|
|
15363
|
+
}
|
|
15364
|
+
],
|
|
15365
|
+
isError: false
|
|
15366
|
+
};
|
|
15367
|
+
}
|
|
14985
15368
|
return {
|
|
14986
15369
|
content: [
|
|
14987
15370
|
{
|
|
14988
15371
|
type: "text",
|
|
14989
|
-
text:
|
|
14990
|
-
{
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
attempts
|
|
14997
|
-
},
|
|
14998
|
-
null,
|
|
14999
|
-
2
|
|
15000
|
-
)
|
|
15372
|
+
text: [
|
|
15373
|
+
`${found.platform} is connected.`,
|
|
15374
|
+
...found.project_id || project_id ? [`Brand/project: ${found.project_id ?? project_id}`] : [],
|
|
15375
|
+
`Account: ${found.username || "(unnamed)"} (id=${found.id})`,
|
|
15376
|
+
`Detected after ${attempts} poll(s) in ${((Date.now() - startedAt) / 1e3).toFixed(1)}s.`,
|
|
15377
|
+
"Ready to call `schedule_post`."
|
|
15378
|
+
].join("\n")
|
|
15001
15379
|
}
|
|
15002
15380
|
],
|
|
15003
15381
|
isError: false
|
|
15004
15382
|
};
|
|
15005
15383
|
}
|
|
15006
|
-
return {
|
|
15007
|
-
content: [
|
|
15008
|
-
{
|
|
15009
|
-
type: "text",
|
|
15010
|
-
text: [
|
|
15011
|
-
`${found.platform} is connected.`,
|
|
15012
|
-
`Account: ${found.username || "(unnamed)"} (id=${found.id})`,
|
|
15013
|
-
`Detected after ${attempts} poll(s) in ${((Date.now() - startedAt) / 1e3).toFixed(1)}s.`,
|
|
15014
|
-
"Ready to call `schedule_post`."
|
|
15015
|
-
].join("\n")
|
|
15016
|
-
}
|
|
15017
|
-
],
|
|
15018
|
-
isError: false
|
|
15019
|
-
};
|
|
15020
15384
|
}
|
|
15385
|
+
const remaining = deadline - Date.now();
|
|
15386
|
+
if (remaining <= 0) break;
|
|
15387
|
+
await new Promise((resolve3) => setTimeout(resolve3, Math.min(intervalMs, remaining)));
|
|
15388
|
+
}
|
|
15389
|
+
const message = `${platform3} did not connect within ${timeout_s ?? 120}s (${attempts} polls). The user may not have completed the browser OAuth yet, or the link expired. Mint a new link with \`start_platform_connection\` and try again, or have the user go directly to socialneuron.com/settings/connections.`;
|
|
15390
|
+
if (format === "json") {
|
|
15391
|
+
return {
|
|
15392
|
+
content: [
|
|
15393
|
+
{
|
|
15394
|
+
type: "text",
|
|
15395
|
+
text: JSON.stringify(
|
|
15396
|
+
{
|
|
15397
|
+
connected: false,
|
|
15398
|
+
platform: platform3,
|
|
15399
|
+
project_id: project_id ?? null,
|
|
15400
|
+
attempts,
|
|
15401
|
+
timed_out: true,
|
|
15402
|
+
message
|
|
15403
|
+
},
|
|
15404
|
+
null,
|
|
15405
|
+
2
|
|
15406
|
+
)
|
|
15407
|
+
}
|
|
15408
|
+
],
|
|
15409
|
+
isError: true
|
|
15410
|
+
};
|
|
15021
15411
|
}
|
|
15022
|
-
const remaining = deadline - Date.now();
|
|
15023
|
-
if (remaining <= 0) break;
|
|
15024
|
-
await new Promise((resolve3) => setTimeout(resolve3, Math.min(intervalMs, remaining)));
|
|
15025
|
-
}
|
|
15026
|
-
const message = `${platform3} did not connect within ${timeout_s ?? 120}s (${attempts} polls). The user may not have completed the browser OAuth yet, or the link expired. Mint a new link with \`start_platform_connection\` and try again, or have the user go directly to socialneuron.com/settings/connections.`;
|
|
15027
|
-
if (format === "json") {
|
|
15028
15412
|
return {
|
|
15029
|
-
content: [
|
|
15030
|
-
{
|
|
15031
|
-
type: "text",
|
|
15032
|
-
text: JSON.stringify(
|
|
15033
|
-
{
|
|
15034
|
-
connected: false,
|
|
15035
|
-
platform: platform3,
|
|
15036
|
-
attempts,
|
|
15037
|
-
timed_out: true,
|
|
15038
|
-
message
|
|
15039
|
-
},
|
|
15040
|
-
null,
|
|
15041
|
-
2
|
|
15042
|
-
)
|
|
15043
|
-
}
|
|
15044
|
-
],
|
|
15413
|
+
content: [{ type: "text", text: message }],
|
|
15045
15414
|
isError: true
|
|
15046
15415
|
};
|
|
15416
|
+
} finally {
|
|
15417
|
+
const remainingWaits = (inFlightWaitsByUser.get(userId) ?? 1) - 1;
|
|
15418
|
+
if (remainingWaits <= 0) {
|
|
15419
|
+
inFlightWaitsByUser.delete(userId);
|
|
15420
|
+
} else {
|
|
15421
|
+
inFlightWaitsByUser.set(userId, remainingWaits);
|
|
15422
|
+
}
|
|
15047
15423
|
}
|
|
15048
|
-
return {
|
|
15049
|
-
content: [{ type: "text", text: message }],
|
|
15050
|
-
isError: true
|
|
15051
|
-
};
|
|
15052
15424
|
}
|
|
15053
15425
|
);
|
|
15054
15426
|
}
|
|
15055
|
-
var PLATFORM_ENUM4;
|
|
15427
|
+
var PLATFORM_ENUM4, MAX_CONCURRENT_WAITS_PER_USER, inFlightWaitsByUser;
|
|
15056
15428
|
var init_connections = __esm({
|
|
15057
15429
|
"src/tools/connections.ts"() {
|
|
15058
15430
|
"use strict";
|
|
15059
15431
|
init_edge_function();
|
|
15060
15432
|
init_rate_limit();
|
|
15433
|
+
init_supabase();
|
|
15061
15434
|
PLATFORM_ENUM4 = [
|
|
15062
15435
|
"youtube",
|
|
15063
15436
|
"tiktok",
|
|
@@ -15070,6 +15443,8 @@ var init_connections = __esm({
|
|
|
15070
15443
|
"shopify",
|
|
15071
15444
|
"etsy"
|
|
15072
15445
|
];
|
|
15446
|
+
MAX_CONCURRENT_WAITS_PER_USER = 3;
|
|
15447
|
+
inFlightWaitsByUser = /* @__PURE__ */ new Map();
|
|
15073
15448
|
}
|
|
15074
15449
|
});
|
|
15075
15450
|
|
|
@@ -15934,7 +16309,12 @@ function applyScopeEnforcement(server, scopeResolver) {
|
|
|
15934
16309
|
toolName: name,
|
|
15935
16310
|
status,
|
|
15936
16311
|
durationMs: Date.now() - startedAt,
|
|
15937
|
-
details: {
|
|
16312
|
+
details: {
|
|
16313
|
+
source: "wrapper",
|
|
16314
|
+
// Classify failures so the tool-error rate is diagnosable
|
|
16315
|
+
// (validation vs permission vs billing vs upstream vs server).
|
|
16316
|
+
...status === "error" ? { error_type: classifyToolError(result) } : {}
|
|
16317
|
+
}
|
|
15938
16318
|
});
|
|
15939
16319
|
return truncateResponse(result);
|
|
15940
16320
|
} catch (err) {
|
|
@@ -15944,6 +16324,8 @@ function applyScopeEnforcement(server, scopeResolver) {
|
|
|
15944
16324
|
durationMs: Date.now() - startedAt,
|
|
15945
16325
|
details: {
|
|
15946
16326
|
source: "wrapper",
|
|
16327
|
+
// A thrown exception escaped the handler — an unclassified fault.
|
|
16328
|
+
error_type: "server_error",
|
|
15947
16329
|
exception: err instanceof Error ? err.message.slice(0, 200) : "unknown"
|
|
15948
16330
|
}
|
|
15949
16331
|
});
|
|
@@ -16037,6 +16419,7 @@ function truncateResponse(result) {
|
|
|
16037
16419
|
return { ...result, content: truncated };
|
|
16038
16420
|
}
|
|
16039
16421
|
function registerAllTools(server, options) {
|
|
16422
|
+
applyToolProfile(server, options?.toolProfile ?? "full");
|
|
16040
16423
|
registerIdeationTools(server);
|
|
16041
16424
|
registerContentTools(server);
|
|
16042
16425
|
registerDistributionTools(server);
|
|
@@ -16089,6 +16472,7 @@ var init_register_tools = __esm({
|
|
|
16089
16472
|
init_supabase();
|
|
16090
16473
|
init_www_authenticate();
|
|
16091
16474
|
init_tool_error();
|
|
16475
|
+
init_tool_profile();
|
|
16092
16476
|
init_scanner();
|
|
16093
16477
|
init_ideation();
|
|
16094
16478
|
init_content2();
|
|
@@ -16173,13 +16557,13 @@ async function invokeToolRest(name, args) {
|
|
|
16173
16557
|
}
|
|
16174
16558
|
function extractRestError(result) {
|
|
16175
16559
|
const sc = result.structuredContent?.error;
|
|
16176
|
-
if (sc?.error_type &&
|
|
16560
|
+
if (sc?.error_type && KNOWN_ERROR_TYPES2.has(sc.error_type)) {
|
|
16177
16561
|
return { error_type: sc.error_type, message: sc.message ?? "Tool error." };
|
|
16178
16562
|
}
|
|
16179
16563
|
const text = result.content?.find((c) => c.type === "text")?.text ?? "";
|
|
16180
16564
|
try {
|
|
16181
16565
|
const parsed = JSON.parse(text);
|
|
16182
|
-
if (parsed.error_type &&
|
|
16566
|
+
if (parsed.error_type && KNOWN_ERROR_TYPES2.has(parsed.error_type)) {
|
|
16183
16567
|
return { error_type: parsed.error_type, message: parsed.message ?? text };
|
|
16184
16568
|
}
|
|
16185
16569
|
} catch {
|
|
@@ -16189,7 +16573,7 @@ function extractRestError(result) {
|
|
|
16189
16573
|
}
|
|
16190
16574
|
return { error_type: "server_error", message: text || "Tool error." };
|
|
16191
16575
|
}
|
|
16192
|
-
var cachedCallHandler,
|
|
16576
|
+
var cachedCallHandler, KNOWN_ERROR_TYPES2;
|
|
16193
16577
|
var init_rest_invoke = __esm({
|
|
16194
16578
|
"src/lib/rest-invoke.ts"() {
|
|
16195
16579
|
"use strict";
|
|
@@ -16197,7 +16581,7 @@ var init_rest_invoke = __esm({
|
|
|
16197
16581
|
init_request_context();
|
|
16198
16582
|
init_version();
|
|
16199
16583
|
init_tool_catalog();
|
|
16200
|
-
|
|
16584
|
+
KNOWN_ERROR_TYPES2 = /* @__PURE__ */ new Set([
|
|
16201
16585
|
"policy_block",
|
|
16202
16586
|
"validation_error",
|
|
16203
16587
|
"permission_denied",
|
|
@@ -16274,7 +16658,9 @@ async function handleCall(args, asJson) {
|
|
|
16274
16658
|
scopes: auth.scopes ?? [],
|
|
16275
16659
|
token: apiKey,
|
|
16276
16660
|
creditsUsed: 0,
|
|
16277
|
-
assetsGenerated: 0
|
|
16661
|
+
assetsGenerated: 0,
|
|
16662
|
+
surface: "cli",
|
|
16663
|
+
projectId: auth.projectId ?? null
|
|
16278
16664
|
},
|
|
16279
16665
|
() => invokeToolRest(toolName, toolArgs)
|
|
16280
16666
|
);
|