@socialneuron/mcp-server 1.4.1 → 1.5.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 +17 -0
- package/dist/http.js +78 -57
- package/dist/index.js +131 -117
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@socialneuron/mcp-server` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.5.0] - 2026-03-19
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **LLM-optimized tool descriptions**: Rewrote 27 tool descriptions and enriched 15 parameters for agent comprehension. Every tool now answers "when to call", "what to pass", and "what comes next" — following Arcade ToolBench patterns (Tool Description, Constrained Input, Dependency Hint, Performance Hint).
|
|
9
|
+
- **API key cache TTL**: Reduced from 60s to 10s to limit revocation exposure window.
|
|
10
|
+
- **OAuth issuer URL**: Production metadata now derives from `MCP_SERVER_URL` instead of defaulting to localhost.
|
|
11
|
+
- **SECURITY.md**: Updated supported versions, added scanner false-positive documentation.
|
|
12
|
+
- **CLI setup URL**: Fixed `app.socialneuron.com` → `www.socialneuron.com`.
|
|
13
|
+
|
|
14
|
+
### Dependencies
|
|
15
|
+
- `@supabase/supabase-js` 2.98.0 → 2.99.2
|
|
16
|
+
- `open` 10.0.0 → 11.0.0 (requires Node.js 20+)
|
|
17
|
+
- `posthog-node` 5.28.1 → 5.28.3
|
|
18
|
+
- `vitest` 3.2.4 → 4.1.0
|
|
19
|
+
- `esbuild` 0.27.3 → 0.27.4
|
|
20
|
+
- `@types/node` 25.4.0 → 25.5.0
|
|
21
|
+
|
|
5
22
|
## [1.4.0] - 2026-03-13
|
|
6
23
|
|
|
7
24
|
### Changed
|
package/dist/http.js
CHANGED
|
@@ -393,7 +393,9 @@ async function getDefaultUserId() {
|
|
|
393
393
|
if (authenticatedUserId) return authenticatedUserId;
|
|
394
394
|
const envUserId = process.env.SOCIALNEURON_USER_ID;
|
|
395
395
|
if (envUserId) return envUserId;
|
|
396
|
-
throw new Error(
|
|
396
|
+
throw new Error(
|
|
397
|
+
"No user ID available. Set SOCIALNEURON_USER_ID or authenticate via API key."
|
|
398
|
+
);
|
|
397
399
|
}
|
|
398
400
|
async function getDefaultProjectId() {
|
|
399
401
|
const userId = await getDefaultUserId().catch(() => null);
|
|
@@ -437,7 +439,9 @@ async function initializeAuth() {
|
|
|
437
439
|
console.error("[MCP] Scopes: " + authenticatedScopes.join(", "));
|
|
438
440
|
if (authenticatedExpiresAt) {
|
|
439
441
|
const expiresMs = new Date(authenticatedExpiresAt).getTime();
|
|
440
|
-
const daysLeft = Math.ceil(
|
|
442
|
+
const daysLeft = Math.ceil(
|
|
443
|
+
(expiresMs - Date.now()) / (1e3 * 60 * 60 * 24)
|
|
444
|
+
);
|
|
441
445
|
console.error("[MCP] Key expires: " + authenticatedExpiresAt);
|
|
442
446
|
if (daysLeft <= 7) {
|
|
443
447
|
console.error(
|
|
@@ -460,8 +464,12 @@ async function initializeAuth() {
|
|
|
460
464
|
console.error(
|
|
461
465
|
"[MCP] \u26A0 DEPRECATED: Service role keys grant full admin access to your database."
|
|
462
466
|
);
|
|
463
|
-
console.error(
|
|
464
|
-
|
|
467
|
+
console.error(
|
|
468
|
+
"[MCP] Migrate to API key auth: npx @socialneuron/mcp-server setup"
|
|
469
|
+
);
|
|
470
|
+
console.error(
|
|
471
|
+
"[MCP] Then remove SOCIALNEURON_SERVICE_KEY from your environment."
|
|
472
|
+
);
|
|
465
473
|
if (!process.env.SOCIALNEURON_USER_ID) {
|
|
466
474
|
console.error(
|
|
467
475
|
"[MCP] Warning: SOCIALNEURON_USER_ID not set. Tools requiring a user will fail."
|
|
@@ -848,10 +856,10 @@ init_supabase();
|
|
|
848
856
|
function registerIdeationTools(server) {
|
|
849
857
|
server.tool(
|
|
850
858
|
"generate_content",
|
|
851
|
-
"
|
|
859
|
+
"Create a script, caption, hook, or blog post tailored to a specific platform. Pass project_id to auto-load brand profile and performance context, or call get_ideation_context first for full context. Output is draft text ready for quality_check then schedule_post.",
|
|
852
860
|
{
|
|
853
861
|
prompt: z.string().max(1e4).describe(
|
|
854
|
-
|
|
862
|
+
'Detailed content prompt. Include topic, angle, audience, and requirements. Example: "LinkedIn post about AI productivity for CTOs, 300 words, include 3 actionable tips, conversational tone." Richer prompts produce better results.'
|
|
855
863
|
),
|
|
856
864
|
content_type: z.enum(["script", "caption", "blog", "hook"]).describe(
|
|
857
865
|
'Type of content to generate. "script" for video scripts, "caption" for social media captions, "blog" for blog posts, "hook" for attention-grabbing hooks.'
|
|
@@ -869,7 +877,7 @@ function registerIdeationTools(server) {
|
|
|
869
877
|
"Target social media platform. Helps tailor tone, length, and format."
|
|
870
878
|
),
|
|
871
879
|
brand_voice: z.string().max(500).optional().describe(
|
|
872
|
-
'
|
|
880
|
+
'Tone directive (e.g. "direct, no jargon, second person" or "witty Gen-Z energy with emoji"). Leave blank to auto-load from project brand profile if project_id is set.'
|
|
873
881
|
),
|
|
874
882
|
model: z.enum(["gemini-2.0-flash", "gemini-2.5-flash", "gemini-2.5-pro"]).optional().describe(
|
|
875
883
|
"AI model to use. Defaults to gemini-2.5-flash. Use gemini-2.5-pro for highest quality."
|
|
@@ -1025,7 +1033,7 @@ Content Type: ${content_type}`;
|
|
|
1025
1033
|
);
|
|
1026
1034
|
server.tool(
|
|
1027
1035
|
"fetch_trends",
|
|
1028
|
-
|
|
1036
|
+
'Get current trending topics for content inspiration. Source "youtube" returns trending videos with view counts, "google_trends" returns rising search terms, "rss"/"url" extracts topics from any feed or page. Results cached 1 hour \u2014 set force_refresh=true for real-time. Feed results into generate_content or plan_content_week.',
|
|
1029
1037
|
{
|
|
1030
1038
|
source: z.enum(["youtube", "google_trends", "rss", "url"]).describe(
|
|
1031
1039
|
'Data source. "youtube" fetches trending videos, "google_trends" fetches daily search trends, "rss" fetches from a custom RSS feed URL, "url" extracts trend data from a web page.'
|
|
@@ -1111,7 +1119,7 @@ Content Type: ${content_type}`;
|
|
|
1111
1119
|
);
|
|
1112
1120
|
server.tool(
|
|
1113
1121
|
"adapt_content",
|
|
1114
|
-
"
|
|
1122
|
+
"Rewrite existing content for a different platform \u2014 adjusts character limits, hashtag style, tone, and CTA format automatically. Use after generate_content when you need the same message across multiple platforms. Pass project_id to apply platform-specific voice overrides from your brand profile.",
|
|
1115
1123
|
{
|
|
1116
1124
|
content: z.string().max(5e3).describe(
|
|
1117
1125
|
"The content to adapt. Can be a caption, script, blog excerpt, or any text."
|
|
@@ -1304,7 +1312,7 @@ function sanitizeDbError(error) {
|
|
|
1304
1312
|
init_request_context();
|
|
1305
1313
|
|
|
1306
1314
|
// src/lib/version.ts
|
|
1307
|
-
var MCP_VERSION = "1.
|
|
1315
|
+
var MCP_VERSION = "1.5.0";
|
|
1308
1316
|
|
|
1309
1317
|
// src/tools/content.ts
|
|
1310
1318
|
var MAX_CREDITS_PER_RUN = Math.max(
|
|
@@ -1412,10 +1420,10 @@ function checkAssetBudget() {
|
|
|
1412
1420
|
function registerContentTools(server) {
|
|
1413
1421
|
server.tool(
|
|
1414
1422
|
"generate_video",
|
|
1415
|
-
"Start an AI video generation job
|
|
1423
|
+
"Start an async AI video generation job \u2014 returns a job_id immediately. Poll with check_status every 10-30s until complete. Cost varies by model: veo3-fast (~15 credits/5s), kling-3 (~30 credits/5s), sora2-pro (~60 credits/10s). Check get_credit_balance first for expensive generations.",
|
|
1416
1424
|
{
|
|
1417
1425
|
prompt: z2.string().max(2500).describe(
|
|
1418
|
-
|
|
1426
|
+
'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.'
|
|
1419
1427
|
),
|
|
1420
1428
|
model: z2.enum([
|
|
1421
1429
|
"veo3-fast",
|
|
@@ -1427,13 +1435,13 @@ function registerContentTools(server) {
|
|
|
1427
1435
|
"kling-3",
|
|
1428
1436
|
"kling-3-pro"
|
|
1429
1437
|
]).describe(
|
|
1430
|
-
"Video
|
|
1438
|
+
"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)."
|
|
1431
1439
|
),
|
|
1432
1440
|
duration: z2.number().min(3).max(30).optional().describe(
|
|
1433
1441
|
"Video duration in seconds. kling: 5-30s, kling-3/kling-3-pro: 3-15s, sora2: 10-15s. Defaults to 5 seconds."
|
|
1434
1442
|
),
|
|
1435
1443
|
aspect_ratio: z2.enum(["16:9", "9:16", "1:1"]).optional().describe(
|
|
1436
|
-
"
|
|
1444
|
+
"Video aspect ratio. 16:9 for YouTube/landscape, 9:16 for TikTok/Reels/Shorts, 1:1 for Instagram feed/square. Defaults to 16:9."
|
|
1437
1445
|
),
|
|
1438
1446
|
enable_audio: z2.boolean().optional().describe(
|
|
1439
1447
|
"Enable native audio generation. Kling 2.6: doubles cost. Kling 3.0: 50% more (std 30/sec, pro 40/sec). 5+ languages."
|
|
@@ -1620,7 +1628,7 @@ function registerContentTools(server) {
|
|
|
1620
1628
|
);
|
|
1621
1629
|
server.tool(
|
|
1622
1630
|
"generate_image",
|
|
1623
|
-
"Start an AI image generation job
|
|
1631
|
+
"Start an async AI image generation job \u2014 returns a job_id immediately. Poll with check_status every 5-15s until complete. Costs 2-10 credits depending on model. Use for social media posts, carousel slides, or as input to generate_video (image-to-video).",
|
|
1624
1632
|
{
|
|
1625
1633
|
prompt: z2.string().max(2e3).describe(
|
|
1626
1634
|
"Text prompt describing the image to generate. Be specific about style, composition, colors, lighting, and subject matter."
|
|
@@ -1800,7 +1808,7 @@ function registerContentTools(server) {
|
|
|
1800
1808
|
);
|
|
1801
1809
|
server.tool(
|
|
1802
1810
|
"check_status",
|
|
1803
|
-
|
|
1811
|
+
'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.',
|
|
1804
1812
|
{
|
|
1805
1813
|
job_id: z2.string().describe(
|
|
1806
1814
|
"The job ID returned by generate_video or generate_image. This is the asyncJobId or taskId value."
|
|
@@ -1976,7 +1984,7 @@ function registerContentTools(server) {
|
|
|
1976
1984
|
);
|
|
1977
1985
|
server.tool(
|
|
1978
1986
|
"create_storyboard",
|
|
1979
|
-
"
|
|
1987
|
+
"Plan a multi-scene video storyboard with AI-generated prompts, durations, captions, and voiceover text per frame. Use before generate_video or generate_image to create cohesive multi-shot content. Include brand_context from get_brand_profile for consistent visual branding across frames.",
|
|
1980
1988
|
{
|
|
1981
1989
|
concept: z2.string().max(2e3).describe(
|
|
1982
1990
|
'The video concept/idea. Include: hook, key messages, target audience, and desired outcome (e.g., "TikTok ad for VPN app targeting privacy-conscious millennials, hook with shocking stat about data leaks").'
|
|
@@ -2163,7 +2171,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2163
2171
|
);
|
|
2164
2172
|
server.tool(
|
|
2165
2173
|
"generate_voiceover",
|
|
2166
|
-
"Generate a
|
|
2174
|
+
"Generate a voiceover audio file for video narration. Returns an R2-hosted audio URL. Use after create_storyboard to add narration to each scene, or standalone for podcast intros and ad reads. Costs ~2 credits per generation.",
|
|
2167
2175
|
{
|
|
2168
2176
|
text: z2.string().max(5e3).describe("The script/text to convert to speech."),
|
|
2169
2177
|
voice: z2.enum([
|
|
@@ -2314,10 +2322,10 @@ Return ONLY valid JSON in this exact format:
|
|
|
2314
2322
|
);
|
|
2315
2323
|
server.tool(
|
|
2316
2324
|
"generate_carousel",
|
|
2317
|
-
"Generate
|
|
2325
|
+
"Generate carousel slide content (headlines, body text, emphasis words per slide). Supports Hormozi-style authority format and educational templates. Returns structured slide data \u2014 render visually then publish via schedule_post with media_type=CAROUSEL_ALBUM and 2-10 media_urls on Instagram.",
|
|
2318
2326
|
{
|
|
2319
2327
|
topic: z2.string().max(200).describe(
|
|
2320
|
-
'
|
|
2328
|
+
'Carousel hook/angle \u2014 specific beats general. Example: "5 pricing mistakes that kill SaaS startups" beats "SaaS tips". Include a curiosity gap or strong opinion for better Hook Strength scores.'
|
|
2321
2329
|
),
|
|
2322
2330
|
template_id: z2.enum([
|
|
2323
2331
|
"educational-series",
|
|
@@ -2645,14 +2653,12 @@ function asEnvelope2(data) {
|
|
|
2645
2653
|
function registerDistributionTools(server) {
|
|
2646
2654
|
server.tool(
|
|
2647
2655
|
"schedule_post",
|
|
2648
|
-
|
|
2656
|
+
'Publish or schedule a post to connected social platforms. Check list_connected_accounts first to verify active OAuth for each target platform. For Instagram carousels: use media_type=CAROUSEL_ALBUM with 2-10 media_urls. For YouTube: title is required. schedule_at uses ISO 8601 (e.g. "2026-03-20T14:00:00Z") \u2014 omit to post immediately.',
|
|
2649
2657
|
{
|
|
2650
2658
|
media_url: z3.string().optional().describe(
|
|
2651
2659
|
"Optional URL of the media file (video or image) to post. This should be a publicly accessible URL or a Cloudflare R2 signed URL from a previous generation. Required for platforms that enforce media uploads. Not needed if media_urls is provided."
|
|
2652
2660
|
),
|
|
2653
|
-
media_urls: z3.array(z3.string()).optional().describe(
|
|
2654
|
-
"Array of image URLs for Instagram carousel posts (2-10 images). Each URL should be publicly accessible or a Cloudflare R2 URL. When provided with media_type=CAROUSEL_ALBUM, creates an Instagram carousel."
|
|
2655
|
-
),
|
|
2661
|
+
media_urls: z3.array(z3.string()).optional().describe("Array of 2-10 image URLs for Instagram carousel posts. Each URL must be publicly accessible or a Cloudflare R2 signed URL. Use with media_type=CAROUSEL_ALBUM."),
|
|
2656
2662
|
media_type: z3.enum(["IMAGE", "VIDEO", "CAROUSEL_ALBUM"]).optional().describe(
|
|
2657
2663
|
"Media type. Set to CAROUSEL_ALBUM with media_urls for Instagram carousels. Default: auto-detected from media_url."
|
|
2658
2664
|
),
|
|
@@ -2668,16 +2674,10 @@ function registerDistributionTools(server) {
|
|
|
2668
2674
|
"threads",
|
|
2669
2675
|
"bluesky"
|
|
2670
2676
|
])
|
|
2671
|
-
).min(1).describe(
|
|
2672
|
-
"Target platforms to post to. Each must have an active OAuth connection."
|
|
2673
|
-
),
|
|
2677
|
+
).min(1).describe("Target platforms (array). Each must have active OAuth \u2014 check list_connected_accounts first. Values: youtube, tiktok, instagram, twitter, linkedin, facebook, threads, bluesky."),
|
|
2674
2678
|
title: z3.string().optional().describe("Post title (used by YouTube and some other platforms)."),
|
|
2675
|
-
hashtags: z3.array(z3.string()).optional().describe(
|
|
2676
|
-
|
|
2677
|
-
),
|
|
2678
|
-
schedule_at: z3.string().optional().describe(
|
|
2679
|
-
'ISO 8601 datetime for scheduled posting (e.g. "2026-03-15T14:00:00Z"). Omit for immediate posting.'
|
|
2680
|
-
),
|
|
2679
|
+
hashtags: z3.array(z3.string()).optional().describe('Hashtags to append to caption. Include or omit the "#" prefix \u2014 both work. Example: ["ai", "contentcreator"] or ["#ai", "#contentcreator"].'),
|
|
2680
|
+
schedule_at: z3.string().optional().describe('ISO 8601 UTC datetime for scheduled posting (e.g. "2026-03-20T14:00:00Z"). Omit to post immediately. Must be in the future.'),
|
|
2681
2681
|
project_id: z3.string().optional().describe("Social Neuron project ID to associate this post with."),
|
|
2682
2682
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text."),
|
|
2683
2683
|
attribution: z3.boolean().optional().describe(
|
|
@@ -2831,7 +2831,7 @@ Created with Social Neuron`;
|
|
|
2831
2831
|
);
|
|
2832
2832
|
server.tool(
|
|
2833
2833
|
"list_connected_accounts",
|
|
2834
|
-
"
|
|
2834
|
+
"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.",
|
|
2835
2835
|
{
|
|
2836
2836
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
2837
2837
|
},
|
|
@@ -2896,7 +2896,7 @@ Created with Social Neuron`;
|
|
|
2896
2896
|
);
|
|
2897
2897
|
server.tool(
|
|
2898
2898
|
"list_recent_posts",
|
|
2899
|
-
"List recent
|
|
2899
|
+
"List recent published and scheduled posts with status, platform, title, and timestamps. Use to check what has been posted before planning new content, or to find post IDs for fetch_analytics. Filter by platform or status to narrow results.",
|
|
2900
2900
|
{
|
|
2901
2901
|
platform: z3.enum([
|
|
2902
2902
|
"youtube",
|
|
@@ -3735,7 +3735,7 @@ function registerAnalyticsTools(server) {
|
|
|
3735
3735
|
"threads",
|
|
3736
3736
|
"bluesky"
|
|
3737
3737
|
]).optional().describe("Filter analytics to a specific platform."),
|
|
3738
|
-
days: z4.number().min(1).max(365).optional().describe("
|
|
3738
|
+
days: z4.number().min(1).max(365).optional().describe("Lookback window in days (1-365). Default 30. Use 7 for weekly review, 30 for monthly summary, 90 for quarterly trends."),
|
|
3739
3739
|
content_id: z4.string().uuid().optional().describe(
|
|
3740
3740
|
"Filter to a specific content_history ID to see performance of one piece of content."
|
|
3741
3741
|
),
|
|
@@ -3915,7 +3915,7 @@ function registerAnalyticsTools(server) {
|
|
|
3915
3915
|
);
|
|
3916
3916
|
server.tool(
|
|
3917
3917
|
"refresh_platform_analytics",
|
|
3918
|
-
"
|
|
3918
|
+
"Queue analytics refresh jobs for all posts from the last 7 days across connected platforms. Call this before fetch_analytics if you need fresh data. Returns immediately \u2014 data updates asynchronously over the next 1-5 minutes.",
|
|
3919
3919
|
{
|
|
3920
3920
|
response_format: z4.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
3921
3921
|
},
|
|
@@ -5845,13 +5845,11 @@ function asEnvelope6(data) {
|
|
|
5845
5845
|
function registerCommentsTools(server) {
|
|
5846
5846
|
server.tool(
|
|
5847
5847
|
"list_comments",
|
|
5848
|
-
|
|
5848
|
+
'List YouTube comments \u2014 pass video_id (11-char string, e.g. "dQw4w9WgXcQ") for a specific video, or omit for recent comments across all channel videos. Returns comment text, author, like count, and reply count. Use page_token from previous response for pagination.',
|
|
5849
5849
|
{
|
|
5850
|
-
video_id: z10.string().optional().describe(
|
|
5851
|
-
"YouTube video ID. If omitted, returns comments across all channel videos."
|
|
5852
|
-
),
|
|
5850
|
+
video_id: z10.string().optional().describe('YouTube video ID \u2014 the 11-character string from the URL (e.g. "dQw4w9WgXcQ" from youtube.com/watch?v=dQw4w9WgXcQ). Omit to get recent comments across all channel videos.'),
|
|
5853
5851
|
max_results: z10.number().min(1).max(100).optional().describe("Maximum number of comments to return. Defaults to 50."),
|
|
5854
|
-
page_token: z10.string().optional().describe("Pagination
|
|
5852
|
+
page_token: z10.string().optional().describe("Pagination cursor from previous list_comments response nextPageToken field. Omit for first page of results."),
|
|
5855
5853
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
5856
5854
|
},
|
|
5857
5855
|
async ({ video_id, max_results, page_token, response_format }) => {
|
|
@@ -5916,7 +5914,7 @@ function registerCommentsTools(server) {
|
|
|
5916
5914
|
);
|
|
5917
5915
|
server.tool(
|
|
5918
5916
|
"reply_to_comment",
|
|
5919
|
-
"Reply to a YouTube comment.
|
|
5917
|
+
"Reply to a YouTube comment. Get the parent_id from list_comments results. Reply appears as the authenticated channel. Use for community engagement after checking list_comments for questions or feedback.",
|
|
5920
5918
|
{
|
|
5921
5919
|
parent_id: z10.string().describe(
|
|
5922
5920
|
"The ID of the parent comment to reply to (from list_comments)."
|
|
@@ -6447,7 +6445,7 @@ function asEnvelope8(data) {
|
|
|
6447
6445
|
function registerCreditsTools(server) {
|
|
6448
6446
|
server.tool(
|
|
6449
6447
|
"get_credit_balance",
|
|
6450
|
-
"
|
|
6448
|
+
"Check remaining credits, monthly limit, spending cap, and plan tier. Call this before expensive operations \u2014 generate_video costs 15-80 credits, generate_image costs 2-10. Returns current balance, monthly allocation, and spending cap (2.5x allocation).",
|
|
6451
6449
|
{
|
|
6452
6450
|
response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6453
6451
|
},
|
|
@@ -6500,7 +6498,7 @@ Monthly used: ${payload.monthlyUsed}` + (payload.monthlyLimit ? ` / ${payload.mo
|
|
|
6500
6498
|
);
|
|
6501
6499
|
server.tool(
|
|
6502
6500
|
"get_budget_status",
|
|
6503
|
-
"
|
|
6501
|
+
"Check how much of the per-session budget has been consumed. Tracks credits spent and assets created in this MCP session against configured limits. Use to avoid hitting budget caps mid-workflow.",
|
|
6504
6502
|
{
|
|
6505
6503
|
response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6506
6504
|
},
|
|
@@ -6743,7 +6741,7 @@ function asEnvelope11(data) {
|
|
|
6743
6741
|
function registerAutopilotTools(server) {
|
|
6744
6742
|
server.tool(
|
|
6745
6743
|
"list_autopilot_configs",
|
|
6746
|
-
"List
|
|
6744
|
+
"List autopilot configurations showing schedules, credit budgets, last run times, and active/inactive status. Use to check what is automated before creating new configs, or to find config_id for update_autopilot_config.",
|
|
6747
6745
|
{
|
|
6748
6746
|
active_only: z15.boolean().optional().describe("If true, only return active configs. Defaults to false (show all)."),
|
|
6749
6747
|
response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
@@ -6889,7 +6887,7 @@ Schedule: ${JSON.stringify(updated.schedule_config)}`
|
|
|
6889
6887
|
);
|
|
6890
6888
|
server.tool(
|
|
6891
6889
|
"get_autopilot_status",
|
|
6892
|
-
"Get
|
|
6890
|
+
"Get autopilot system overview: active config count, recent execution results, credits consumed, and next scheduled run time. Use as a dashboard check before modifying autopilot settings.",
|
|
6893
6891
|
{
|
|
6894
6892
|
response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6895
6893
|
},
|
|
@@ -7006,7 +7004,7 @@ ${content.suggested_hooks.map((h) => ` - ${h}`).join("\n")}`
|
|
|
7006
7004
|
function registerExtractionTools(server) {
|
|
7007
7005
|
server.tool(
|
|
7008
7006
|
"extract_url_content",
|
|
7009
|
-
"Extract content from
|
|
7007
|
+
"Extract text content from any URL \u2014 YouTube video transcripts, article text, or product page features/benefits/USP. YouTube URLs auto-route to transcript extraction with optional comments. Use before generate_content to repurpose existing content, or before plan_content_week to base a content plan on a source URL.",
|
|
7010
7008
|
{
|
|
7011
7009
|
url: z16.string().url().describe("URL to extract content from"),
|
|
7012
7010
|
extract_type: z16.enum(["auto", "transcript", "article", "product"]).default("auto").describe("Type of extraction"),
|
|
@@ -7204,9 +7202,9 @@ function asEnvelope13(data) {
|
|
|
7204
7202
|
function registerQualityTools(server) {
|
|
7205
7203
|
server.tool(
|
|
7206
7204
|
"quality_check",
|
|
7207
|
-
"Score
|
|
7205
|
+
"Score post quality across 7 categories: Hook Strength, Message Clarity, Platform Fit, Brand Alignment, Novelty, CTA Strength, and Safety/Claims. Each scored 0-5, total 35. Default pass threshold is 26 (~75%). Run after generate_content and before schedule_post. Include hashtags in caption if they will be published \u2014 they affect Platform Fit and Safety scores.",
|
|
7208
7206
|
{
|
|
7209
|
-
caption: z17.string().describe("
|
|
7207
|
+
caption: z17.string().describe("The post text to score. Include hashtags if they will be published \u2014 they affect Platform Fit and Safety/Claims scores."),
|
|
7210
7208
|
title: z17.string().optional().describe("Post title (important for YouTube)"),
|
|
7211
7209
|
platforms: z17.array(
|
|
7212
7210
|
z17.enum([
|
|
@@ -7220,7 +7218,7 @@ function registerQualityTools(server) {
|
|
|
7220
7218
|
"bluesky"
|
|
7221
7219
|
])
|
|
7222
7220
|
).min(1).describe("Target platforms"),
|
|
7223
|
-
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass"),
|
|
7221
|
+
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass (max 35, scored across 7 categories at 0-5 each). Default 26 (~75%). Use 20 for rough drafts, 28+ for final posts going to large audiences."),
|
|
7224
7222
|
brand_keyword: z17.string().optional().describe("Brand keyword for alignment check"),
|
|
7225
7223
|
brand_avoid_patterns: z17.array(z17.string()).optional(),
|
|
7226
7224
|
custom_banned_terms: z17.array(z17.string()).optional(),
|
|
@@ -7291,7 +7289,7 @@ function registerQualityTools(server) {
|
|
|
7291
7289
|
);
|
|
7292
7290
|
server.tool(
|
|
7293
7291
|
"quality_check_plan",
|
|
7294
|
-
"
|
|
7292
|
+
"Batch quality check all posts in a content plan. Returns per-post scores and aggregate pass/fail summary. Use after plan_content_week and before schedule_content_plan to catch low-quality posts before publishing.",
|
|
7295
7293
|
{
|
|
7296
7294
|
plan: z17.object({
|
|
7297
7295
|
posts: z17.array(
|
|
@@ -7303,7 +7301,7 @@ function registerQualityTools(server) {
|
|
|
7303
7301
|
})
|
|
7304
7302
|
)
|
|
7305
7303
|
}).passthrough().describe("Content plan with posts array"),
|
|
7306
|
-
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass"),
|
|
7304
|
+
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass (max 35, scored across 7 categories at 0-5 each). Default 26 (~75%). Use 20 for rough drafts, 28+ for final posts going to large audiences."),
|
|
7307
7305
|
response_format: z17.enum(["text", "json"]).default("text")
|
|
7308
7306
|
},
|
|
7309
7307
|
async ({ plan, threshold, response_format }) => {
|
|
@@ -7484,7 +7482,7 @@ function formatPlanAsText(plan) {
|
|
|
7484
7482
|
function registerPlanningTools(server) {
|
|
7485
7483
|
server.tool(
|
|
7486
7484
|
"plan_content_week",
|
|
7487
|
-
"Generate a full
|
|
7485
|
+
"Generate a full content plan with platform-specific drafts, hooks, angles, and optimal schedule times. Pass a topic or source_url \u2014 brand context and performance insights auto-load via project_id. Output feeds directly into quality_check_plan then schedule_content_plan. Costs ~5-15 credits depending on post count.",
|
|
7488
7486
|
{
|
|
7489
7487
|
topic: z18.string().describe("Main topic or content theme"),
|
|
7490
7488
|
source_url: z18.string().optional().describe("URL to extract content from (YouTube, article)"),
|
|
@@ -7820,7 +7818,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7820
7818
|
);
|
|
7821
7819
|
server.tool(
|
|
7822
7820
|
"save_content_plan",
|
|
7823
|
-
"
|
|
7821
|
+
"Save a content plan to the database for team review, approval workflows, and scheduled publishing. Creates a plan_id you can reference in get_content_plan, update_content_plan, and schedule_content_plan.",
|
|
7824
7822
|
{
|
|
7825
7823
|
plan: z18.object({
|
|
7826
7824
|
topic: z18.string(),
|
|
@@ -8833,7 +8831,7 @@ function searchTools(query) {
|
|
|
8833
8831
|
function registerDiscoveryTools(server) {
|
|
8834
8832
|
server.tool(
|
|
8835
8833
|
"search_tools",
|
|
8836
|
-
'Search
|
|
8834
|
+
'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.',
|
|
8837
8835
|
{
|
|
8838
8836
|
query: z20.string().optional().describe("Search query to filter tools by name or description"),
|
|
8839
8837
|
module: z20.string().optional().describe('Filter by module name (e.g. "planning", "content", "analytics")'),
|
|
@@ -8960,7 +8958,7 @@ function getJWKS(supabaseUrl) {
|
|
|
8960
8958
|
return jwks;
|
|
8961
8959
|
}
|
|
8962
8960
|
var apiKeyCache = /* @__PURE__ */ new Map();
|
|
8963
|
-
var API_KEY_CACHE_TTL_MS =
|
|
8961
|
+
var API_KEY_CACHE_TTL_MS = 1e4;
|
|
8964
8962
|
function createTokenVerifier(options) {
|
|
8965
8963
|
const { supabaseUrl, supabaseAnonKey } = options;
|
|
8966
8964
|
return {
|
|
@@ -9061,7 +9059,30 @@ var PORT = parseInt(process.env.PORT ?? "8080", 10);
|
|
|
9061
9059
|
var SUPABASE_URL2 = process.env.SUPABASE_URL ?? "";
|
|
9062
9060
|
var SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY ?? "";
|
|
9063
9061
|
var MCP_SERVER_URL = process.env.MCP_SERVER_URL ?? `http://localhost:${PORT}/mcp`;
|
|
9062
|
+
var APP_BASE_URL = process.env.APP_BASE_URL ?? "https://www.socialneuron.com";
|
|
9064
9063
|
var NODE_ENV = process.env.NODE_ENV ?? "development";
|
|
9064
|
+
function deriveOAuthIssuerUrl() {
|
|
9065
|
+
if (process.env.OAUTH_ISSUER_URL) {
|
|
9066
|
+
return process.env.OAUTH_ISSUER_URL;
|
|
9067
|
+
}
|
|
9068
|
+
try {
|
|
9069
|
+
const mcpUrl = new URL(MCP_SERVER_URL);
|
|
9070
|
+
const isLocalhost = mcpUrl.hostname === "localhost" || mcpUrl.hostname === "127.0.0.1";
|
|
9071
|
+
if (isLocalhost) {
|
|
9072
|
+
if (NODE_ENV === "development") {
|
|
9073
|
+
return `${mcpUrl.protocol}//${mcpUrl.host}`;
|
|
9074
|
+
}
|
|
9075
|
+
} else {
|
|
9076
|
+
return `${mcpUrl.protocol}//${mcpUrl.host}`;
|
|
9077
|
+
}
|
|
9078
|
+
} catch {
|
|
9079
|
+
}
|
|
9080
|
+
if (APP_BASE_URL && !APP_BASE_URL.includes("localhost")) {
|
|
9081
|
+
return APP_BASE_URL;
|
|
9082
|
+
}
|
|
9083
|
+
return "https://mcp.socialneuron.com";
|
|
9084
|
+
}
|
|
9085
|
+
var OAUTH_ISSUER_URL = deriveOAuthIssuerUrl();
|
|
9065
9086
|
if (!SUPABASE_URL2 || !SUPABASE_ANON_KEY) {
|
|
9066
9087
|
console.error("[MCP HTTP] Missing SUPABASE_URL or SUPABASE_ANON_KEY");
|
|
9067
9088
|
process.exit(1);
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var MCP_VERSION;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/lib/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
MCP_VERSION = "1.
|
|
17
|
+
MCP_VERSION = "1.5.0";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -400,7 +400,9 @@ async function getDefaultUserId() {
|
|
|
400
400
|
if (authenticatedUserId) return authenticatedUserId;
|
|
401
401
|
const envUserId = process.env.SOCIALNEURON_USER_ID;
|
|
402
402
|
if (envUserId) return envUserId;
|
|
403
|
-
throw new Error(
|
|
403
|
+
throw new Error(
|
|
404
|
+
"No user ID available. Set SOCIALNEURON_USER_ID or authenticate via API key."
|
|
405
|
+
);
|
|
404
406
|
}
|
|
405
407
|
async function getDefaultProjectId() {
|
|
406
408
|
const userId = await getDefaultUserId().catch(() => null);
|
|
@@ -444,7 +446,9 @@ async function initializeAuth() {
|
|
|
444
446
|
console.error("[MCP] Scopes: " + authenticatedScopes.join(", "));
|
|
445
447
|
if (authenticatedExpiresAt) {
|
|
446
448
|
const expiresMs = new Date(authenticatedExpiresAt).getTime();
|
|
447
|
-
const daysLeft = Math.ceil(
|
|
449
|
+
const daysLeft = Math.ceil(
|
|
450
|
+
(expiresMs - Date.now()) / (1e3 * 60 * 60 * 24)
|
|
451
|
+
);
|
|
448
452
|
console.error("[MCP] Key expires: " + authenticatedExpiresAt);
|
|
449
453
|
if (daysLeft <= 7) {
|
|
450
454
|
console.error(
|
|
@@ -467,8 +471,12 @@ async function initializeAuth() {
|
|
|
467
471
|
console.error(
|
|
468
472
|
"[MCP] \u26A0 DEPRECATED: Service role keys grant full admin access to your database."
|
|
469
473
|
);
|
|
470
|
-
console.error(
|
|
471
|
-
|
|
474
|
+
console.error(
|
|
475
|
+
"[MCP] Migrate to API key auth: npx @socialneuron/mcp-server setup"
|
|
476
|
+
);
|
|
477
|
+
console.error(
|
|
478
|
+
"[MCP] Then remove SOCIALNEURON_SERVICE_KEY from your environment."
|
|
479
|
+
);
|
|
472
480
|
if (!process.env.SOCIALNEURON_USER_ID) {
|
|
473
481
|
console.error(
|
|
474
482
|
"[MCP] Warning: SOCIALNEURON_USER_ID not set. Tools requiring a user will fail."
|
|
@@ -2947,7 +2955,9 @@ __export(setup_exports, {
|
|
|
2947
2955
|
runSetup: () => runSetup
|
|
2948
2956
|
});
|
|
2949
2957
|
import { createHash as createHash4, randomBytes, randomUUID as randomUUID3 } from "node:crypto";
|
|
2950
|
-
import {
|
|
2958
|
+
import {
|
|
2959
|
+
createServer
|
|
2960
|
+
} from "node:http";
|
|
2951
2961
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2952
2962
|
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
2953
2963
|
import { join as join3 } from "node:path";
|
|
@@ -2962,7 +2972,7 @@ function generatePKCE() {
|
|
|
2962
2972
|
return { codeVerifier, codeChallenge };
|
|
2963
2973
|
}
|
|
2964
2974
|
function getAppBaseUrl() {
|
|
2965
|
-
return process.env.SOCIALNEURON_APP_URL || "https://
|
|
2975
|
+
return process.env.SOCIALNEURON_APP_URL || "https://www.socialneuron.com";
|
|
2966
2976
|
}
|
|
2967
2977
|
function getDefaultSupabaseUrl() {
|
|
2968
2978
|
return process.env.SOCIALNEURON_SUPABASE_URL || process.env.SUPABASE_URL || CLOUD_SUPABASE_URL;
|
|
@@ -3033,11 +3043,14 @@ function readBody(req) {
|
|
|
3033
3043
|
async function completePkceExchange(codeVerifier, state) {
|
|
3034
3044
|
const supabaseUrl = getDefaultSupabaseUrl();
|
|
3035
3045
|
try {
|
|
3036
|
-
const response = await fetch(
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3046
|
+
const response = await fetch(
|
|
3047
|
+
`${supabaseUrl}/functions/v1/mcp-auth?action=exchange-key`,
|
|
3048
|
+
{
|
|
3049
|
+
method: "POST",
|
|
3050
|
+
headers: { "Content-Type": "application/json" },
|
|
3051
|
+
body: JSON.stringify({ code_verifier: codeVerifier, state })
|
|
3052
|
+
}
|
|
3053
|
+
);
|
|
3041
3054
|
if (!response.ok) {
|
|
3042
3055
|
const text = await response.text();
|
|
3043
3056
|
console.error(` PKCE exchange failed: ${text}`);
|
|
@@ -3046,7 +3059,9 @@ async function completePkceExchange(codeVerifier, state) {
|
|
|
3046
3059
|
const data = await response.json();
|
|
3047
3060
|
return data.success === true;
|
|
3048
3061
|
} catch (err) {
|
|
3049
|
-
console.error(
|
|
3062
|
+
console.error(
|
|
3063
|
+
` PKCE exchange error: ${err instanceof Error ? err.message : String(err)}`
|
|
3064
|
+
);
|
|
3050
3065
|
return false;
|
|
3051
3066
|
}
|
|
3052
3067
|
}
|
|
@@ -3057,9 +3072,13 @@ async function runSetup() {
|
|
|
3057
3072
|
console.error("");
|
|
3058
3073
|
console.error(" Privacy Notice:");
|
|
3059
3074
|
console.error(" - Your API key is stored locally in your OS keychain");
|
|
3060
|
-
console.error(
|
|
3075
|
+
console.error(
|
|
3076
|
+
" - Tool invocations are logged for usage metering (no content stored)"
|
|
3077
|
+
);
|
|
3061
3078
|
console.error(" - Set DO_NOT_TRACK=1 to disable telemetry");
|
|
3062
|
-
console.error(
|
|
3079
|
+
console.error(
|
|
3080
|
+
" - Data export/delete: https://www.socialneuron.com/settings"
|
|
3081
|
+
);
|
|
3063
3082
|
console.error("");
|
|
3064
3083
|
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
3065
3084
|
const state = randomUUID3();
|
|
@@ -3092,49 +3111,54 @@ async function runSetup() {
|
|
|
3092
3111
|
console.error("");
|
|
3093
3112
|
console.error(" Waiting for authorization (timeout: 120s)...");
|
|
3094
3113
|
}
|
|
3095
|
-
const result = await new Promise(
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
try {
|
|
3111
|
-
const body = await readBody(req);
|
|
3112
|
-
const data = JSON.parse(body);
|
|
3113
|
-
if (data.state !== state) {
|
|
3114
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3115
|
-
res.end(JSON.stringify({ error: "State mismatch" }));
|
|
3114
|
+
const result = await new Promise(
|
|
3115
|
+
(resolve3) => {
|
|
3116
|
+
const timeout = setTimeout(() => {
|
|
3117
|
+
server2.close();
|
|
3118
|
+
resolve3({ error: "Authorization timed out after 120 seconds." });
|
|
3119
|
+
}, 12e4);
|
|
3120
|
+
server2.on(
|
|
3121
|
+
"request",
|
|
3122
|
+
async (req, res) => {
|
|
3123
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
3124
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
3125
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
3126
|
+
if (req.method === "OPTIONS") {
|
|
3127
|
+
res.writeHead(204);
|
|
3128
|
+
res.end();
|
|
3116
3129
|
return;
|
|
3117
3130
|
}
|
|
3118
|
-
if (
|
|
3119
|
-
|
|
3120
|
-
|
|
3131
|
+
if (req.method === "POST" && req.url === "/callback") {
|
|
3132
|
+
try {
|
|
3133
|
+
const body = await readBody(req);
|
|
3134
|
+
const data = JSON.parse(body);
|
|
3135
|
+
if (data.state !== state) {
|
|
3136
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3137
|
+
res.end(JSON.stringify({ error: "State mismatch" }));
|
|
3138
|
+
return;
|
|
3139
|
+
}
|
|
3140
|
+
if (!data.api_key) {
|
|
3141
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3142
|
+
res.end(JSON.stringify({ error: "Missing api_key" }));
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3146
|
+
res.end(JSON.stringify({ success: true }));
|
|
3147
|
+
clearTimeout(timeout);
|
|
3148
|
+
server2.close();
|
|
3149
|
+
resolve3({ apiKey: data.api_key });
|
|
3150
|
+
} catch {
|
|
3151
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3152
|
+
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
3153
|
+
}
|
|
3121
3154
|
return;
|
|
3122
3155
|
}
|
|
3123
|
-
res.writeHead(
|
|
3124
|
-
res.end(
|
|
3125
|
-
clearTimeout(timeout);
|
|
3126
|
-
server2.close();
|
|
3127
|
-
resolve3({ apiKey: data.api_key });
|
|
3128
|
-
} catch {
|
|
3129
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3130
|
-
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
3156
|
+
res.writeHead(404);
|
|
3157
|
+
res.end("Not found");
|
|
3131
3158
|
}
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
res.end("Not found");
|
|
3136
|
-
});
|
|
3137
|
-
});
|
|
3159
|
+
);
|
|
3160
|
+
}
|
|
3161
|
+
);
|
|
3138
3162
|
if ("error" in result) {
|
|
3139
3163
|
console.error("");
|
|
3140
3164
|
console.error(` Error: ${result.error}`);
|
|
@@ -3146,7 +3170,9 @@ async function runSetup() {
|
|
|
3146
3170
|
const exchangeSuccess = await completePkceExchange(codeVerifier, state);
|
|
3147
3171
|
if (!exchangeSuccess) {
|
|
3148
3172
|
console.error(" Warning: PKCE exchange failed. Key may not be activated.");
|
|
3149
|
-
console.error(
|
|
3173
|
+
console.error(
|
|
3174
|
+
" The key will still work if the server was in legacy mode."
|
|
3175
|
+
);
|
|
3150
3176
|
} else {
|
|
3151
3177
|
console.error(" PKCE verification complete.");
|
|
3152
3178
|
}
|
|
@@ -3167,7 +3193,9 @@ async function runSetup() {
|
|
|
3167
3193
|
}
|
|
3168
3194
|
if (!configured) {
|
|
3169
3195
|
console.error("");
|
|
3170
|
-
console.error(
|
|
3196
|
+
console.error(
|
|
3197
|
+
" No MCP client config found. Add this to your MCP config manually:"
|
|
3198
|
+
);
|
|
3171
3199
|
console.error("");
|
|
3172
3200
|
console.error(' "socialneuron": {');
|
|
3173
3201
|
console.error(' "command": "npx",');
|
|
@@ -3305,17 +3333,13 @@ async function runLoginDevice() {
|
|
|
3305
3333
|
if (pollResponse.status === 200) {
|
|
3306
3334
|
const pollData = await pollResponse.json();
|
|
3307
3335
|
if (pollData.api_key) {
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
console.error(` Key prefix: ${pollData.api_key.substring(0, 12)}...`);
|
|
3316
|
-
console.error("");
|
|
3317
|
-
return;
|
|
3318
|
-
}
|
|
3336
|
+
await saveApiKey(pollData.api_key);
|
|
3337
|
+
await saveSupabaseUrl(supabaseUrl);
|
|
3338
|
+
console.error("");
|
|
3339
|
+
console.error(" Authorized!");
|
|
3340
|
+
console.error(` Key prefix: ${pollData.api_key.substring(0, 12)}...`);
|
|
3341
|
+
console.error("");
|
|
3342
|
+
return;
|
|
3319
3343
|
}
|
|
3320
3344
|
}
|
|
3321
3345
|
if (pollResponse.status === 410) {
|
|
@@ -3874,10 +3898,10 @@ init_supabase();
|
|
|
3874
3898
|
function registerIdeationTools(server2) {
|
|
3875
3899
|
server2.tool(
|
|
3876
3900
|
"generate_content",
|
|
3877
|
-
"
|
|
3901
|
+
"Create a script, caption, hook, or blog post tailored to a specific platform. Pass project_id to auto-load brand profile and performance context, or call get_ideation_context first for full context. Output is draft text ready for quality_check then schedule_post.",
|
|
3878
3902
|
{
|
|
3879
3903
|
prompt: z.string().max(1e4).describe(
|
|
3880
|
-
|
|
3904
|
+
'Detailed content prompt. Include topic, angle, audience, and requirements. Example: "LinkedIn post about AI productivity for CTOs, 300 words, include 3 actionable tips, conversational tone." Richer prompts produce better results.'
|
|
3881
3905
|
),
|
|
3882
3906
|
content_type: z.enum(["script", "caption", "blog", "hook"]).describe(
|
|
3883
3907
|
'Type of content to generate. "script" for video scripts, "caption" for social media captions, "blog" for blog posts, "hook" for attention-grabbing hooks.'
|
|
@@ -3895,7 +3919,7 @@ function registerIdeationTools(server2) {
|
|
|
3895
3919
|
"Target social media platform. Helps tailor tone, length, and format."
|
|
3896
3920
|
),
|
|
3897
3921
|
brand_voice: z.string().max(500).optional().describe(
|
|
3898
|
-
'
|
|
3922
|
+
'Tone directive (e.g. "direct, no jargon, second person" or "witty Gen-Z energy with emoji"). Leave blank to auto-load from project brand profile if project_id is set.'
|
|
3899
3923
|
),
|
|
3900
3924
|
model: z.enum(["gemini-2.0-flash", "gemini-2.5-flash", "gemini-2.5-pro"]).optional().describe(
|
|
3901
3925
|
"AI model to use. Defaults to gemini-2.5-flash. Use gemini-2.5-pro for highest quality."
|
|
@@ -4051,7 +4075,7 @@ Content Type: ${content_type}`;
|
|
|
4051
4075
|
);
|
|
4052
4076
|
server2.tool(
|
|
4053
4077
|
"fetch_trends",
|
|
4054
|
-
|
|
4078
|
+
'Get current trending topics for content inspiration. Source "youtube" returns trending videos with view counts, "google_trends" returns rising search terms, "rss"/"url" extracts topics from any feed or page. Results cached 1 hour \u2014 set force_refresh=true for real-time. Feed results into generate_content or plan_content_week.',
|
|
4055
4079
|
{
|
|
4056
4080
|
source: z.enum(["youtube", "google_trends", "rss", "url"]).describe(
|
|
4057
4081
|
'Data source. "youtube" fetches trending videos, "google_trends" fetches daily search trends, "rss" fetches from a custom RSS feed URL, "url" extracts trend data from a web page.'
|
|
@@ -4137,7 +4161,7 @@ Content Type: ${content_type}`;
|
|
|
4137
4161
|
);
|
|
4138
4162
|
server2.tool(
|
|
4139
4163
|
"adapt_content",
|
|
4140
|
-
"
|
|
4164
|
+
"Rewrite existing content for a different platform \u2014 adjusts character limits, hashtag style, tone, and CTA format automatically. Use after generate_content when you need the same message across multiple platforms. Pass project_id to apply platform-specific voice overrides from your brand profile.",
|
|
4141
4165
|
{
|
|
4142
4166
|
content: z.string().max(5e3).describe(
|
|
4143
4167
|
"The content to adapt. Can be a caption, script, blog excerpt, or any text."
|
|
@@ -4435,10 +4459,10 @@ function checkAssetBudget() {
|
|
|
4435
4459
|
function registerContentTools(server2) {
|
|
4436
4460
|
server2.tool(
|
|
4437
4461
|
"generate_video",
|
|
4438
|
-
"Start an AI video generation job
|
|
4462
|
+
"Start an async AI video generation job \u2014 returns a job_id immediately. Poll with check_status every 10-30s until complete. Cost varies by model: veo3-fast (~15 credits/5s), kling-3 (~30 credits/5s), sora2-pro (~60 credits/10s). Check get_credit_balance first for expensive generations.",
|
|
4439
4463
|
{
|
|
4440
4464
|
prompt: z2.string().max(2500).describe(
|
|
4441
|
-
|
|
4465
|
+
'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.'
|
|
4442
4466
|
),
|
|
4443
4467
|
model: z2.enum([
|
|
4444
4468
|
"veo3-fast",
|
|
@@ -4450,13 +4474,13 @@ function registerContentTools(server2) {
|
|
|
4450
4474
|
"kling-3",
|
|
4451
4475
|
"kling-3-pro"
|
|
4452
4476
|
]).describe(
|
|
4453
|
-
"Video
|
|
4477
|
+
"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)."
|
|
4454
4478
|
),
|
|
4455
4479
|
duration: z2.number().min(3).max(30).optional().describe(
|
|
4456
4480
|
"Video duration in seconds. kling: 5-30s, kling-3/kling-3-pro: 3-15s, sora2: 10-15s. Defaults to 5 seconds."
|
|
4457
4481
|
),
|
|
4458
4482
|
aspect_ratio: z2.enum(["16:9", "9:16", "1:1"]).optional().describe(
|
|
4459
|
-
"
|
|
4483
|
+
"Video aspect ratio. 16:9 for YouTube/landscape, 9:16 for TikTok/Reels/Shorts, 1:1 for Instagram feed/square. Defaults to 16:9."
|
|
4460
4484
|
),
|
|
4461
4485
|
enable_audio: z2.boolean().optional().describe(
|
|
4462
4486
|
"Enable native audio generation. Kling 2.6: doubles cost. Kling 3.0: 50% more (std 30/sec, pro 40/sec). 5+ languages."
|
|
@@ -4643,7 +4667,7 @@ function registerContentTools(server2) {
|
|
|
4643
4667
|
);
|
|
4644
4668
|
server2.tool(
|
|
4645
4669
|
"generate_image",
|
|
4646
|
-
"Start an AI image generation job
|
|
4670
|
+
"Start an async AI image generation job \u2014 returns a job_id immediately. Poll with check_status every 5-15s until complete. Costs 2-10 credits depending on model. Use for social media posts, carousel slides, or as input to generate_video (image-to-video).",
|
|
4647
4671
|
{
|
|
4648
4672
|
prompt: z2.string().max(2e3).describe(
|
|
4649
4673
|
"Text prompt describing the image to generate. Be specific about style, composition, colors, lighting, and subject matter."
|
|
@@ -4823,7 +4847,7 @@ function registerContentTools(server2) {
|
|
|
4823
4847
|
);
|
|
4824
4848
|
server2.tool(
|
|
4825
4849
|
"check_status",
|
|
4826
|
-
|
|
4850
|
+
'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.',
|
|
4827
4851
|
{
|
|
4828
4852
|
job_id: z2.string().describe(
|
|
4829
4853
|
"The job ID returned by generate_video or generate_image. This is the asyncJobId or taskId value."
|
|
@@ -4999,7 +5023,7 @@ function registerContentTools(server2) {
|
|
|
4999
5023
|
);
|
|
5000
5024
|
server2.tool(
|
|
5001
5025
|
"create_storyboard",
|
|
5002
|
-
"
|
|
5026
|
+
"Plan a multi-scene video storyboard with AI-generated prompts, durations, captions, and voiceover text per frame. Use before generate_video or generate_image to create cohesive multi-shot content. Include brand_context from get_brand_profile for consistent visual branding across frames.",
|
|
5003
5027
|
{
|
|
5004
5028
|
concept: z2.string().max(2e3).describe(
|
|
5005
5029
|
'The video concept/idea. Include: hook, key messages, target audience, and desired outcome (e.g., "TikTok ad for VPN app targeting privacy-conscious millennials, hook with shocking stat about data leaks").'
|
|
@@ -5186,7 +5210,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
5186
5210
|
);
|
|
5187
5211
|
server2.tool(
|
|
5188
5212
|
"generate_voiceover",
|
|
5189
|
-
"Generate a
|
|
5213
|
+
"Generate a voiceover audio file for video narration. Returns an R2-hosted audio URL. Use after create_storyboard to add narration to each scene, or standalone for podcast intros and ad reads. Costs ~2 credits per generation.",
|
|
5190
5214
|
{
|
|
5191
5215
|
text: z2.string().max(5e3).describe("The script/text to convert to speech."),
|
|
5192
5216
|
voice: z2.enum([
|
|
@@ -5337,10 +5361,10 @@ Return ONLY valid JSON in this exact format:
|
|
|
5337
5361
|
);
|
|
5338
5362
|
server2.tool(
|
|
5339
5363
|
"generate_carousel",
|
|
5340
|
-
"Generate
|
|
5364
|
+
"Generate carousel slide content (headlines, body text, emphasis words per slide). Supports Hormozi-style authority format and educational templates. Returns structured slide data \u2014 render visually then publish via schedule_post with media_type=CAROUSEL_ALBUM and 2-10 media_urls on Instagram.",
|
|
5341
5365
|
{
|
|
5342
5366
|
topic: z2.string().max(200).describe(
|
|
5343
|
-
'
|
|
5367
|
+
'Carousel hook/angle \u2014 specific beats general. Example: "5 pricing mistakes that kill SaaS startups" beats "SaaS tips". Include a curiosity gap or strong opinion for better Hook Strength scores.'
|
|
5344
5368
|
),
|
|
5345
5369
|
template_id: z2.enum([
|
|
5346
5370
|
"educational-series",
|
|
@@ -5547,14 +5571,12 @@ function asEnvelope2(data) {
|
|
|
5547
5571
|
function registerDistributionTools(server2) {
|
|
5548
5572
|
server2.tool(
|
|
5549
5573
|
"schedule_post",
|
|
5550
|
-
|
|
5574
|
+
'Publish or schedule a post to connected social platforms. Check list_connected_accounts first to verify active OAuth for each target platform. For Instagram carousels: use media_type=CAROUSEL_ALBUM with 2-10 media_urls. For YouTube: title is required. schedule_at uses ISO 8601 (e.g. "2026-03-20T14:00:00Z") \u2014 omit to post immediately.',
|
|
5551
5575
|
{
|
|
5552
5576
|
media_url: z3.string().optional().describe(
|
|
5553
5577
|
"Optional URL of the media file (video or image) to post. This should be a publicly accessible URL or a Cloudflare R2 signed URL from a previous generation. Required for platforms that enforce media uploads. Not needed if media_urls is provided."
|
|
5554
5578
|
),
|
|
5555
|
-
media_urls: z3.array(z3.string()).optional().describe(
|
|
5556
|
-
"Array of image URLs for Instagram carousel posts (2-10 images). Each URL should be publicly accessible or a Cloudflare R2 URL. When provided with media_type=CAROUSEL_ALBUM, creates an Instagram carousel."
|
|
5557
|
-
),
|
|
5579
|
+
media_urls: z3.array(z3.string()).optional().describe("Array of 2-10 image URLs for Instagram carousel posts. Each URL must be publicly accessible or a Cloudflare R2 signed URL. Use with media_type=CAROUSEL_ALBUM."),
|
|
5558
5580
|
media_type: z3.enum(["IMAGE", "VIDEO", "CAROUSEL_ALBUM"]).optional().describe(
|
|
5559
5581
|
"Media type. Set to CAROUSEL_ALBUM with media_urls for Instagram carousels. Default: auto-detected from media_url."
|
|
5560
5582
|
),
|
|
@@ -5570,16 +5592,10 @@ function registerDistributionTools(server2) {
|
|
|
5570
5592
|
"threads",
|
|
5571
5593
|
"bluesky"
|
|
5572
5594
|
])
|
|
5573
|
-
).min(1).describe(
|
|
5574
|
-
"Target platforms to post to. Each must have an active OAuth connection."
|
|
5575
|
-
),
|
|
5595
|
+
).min(1).describe("Target platforms (array). Each must have active OAuth \u2014 check list_connected_accounts first. Values: youtube, tiktok, instagram, twitter, linkedin, facebook, threads, bluesky."),
|
|
5576
5596
|
title: z3.string().optional().describe("Post title (used by YouTube and some other platforms)."),
|
|
5577
|
-
hashtags: z3.array(z3.string()).optional().describe(
|
|
5578
|
-
|
|
5579
|
-
),
|
|
5580
|
-
schedule_at: z3.string().optional().describe(
|
|
5581
|
-
'ISO 8601 datetime for scheduled posting (e.g. "2026-03-15T14:00:00Z"). Omit for immediate posting.'
|
|
5582
|
-
),
|
|
5597
|
+
hashtags: z3.array(z3.string()).optional().describe('Hashtags to append to caption. Include or omit the "#" prefix \u2014 both work. Example: ["ai", "contentcreator"] or ["#ai", "#contentcreator"].'),
|
|
5598
|
+
schedule_at: z3.string().optional().describe('ISO 8601 UTC datetime for scheduled posting (e.g. "2026-03-20T14:00:00Z"). Omit to post immediately. Must be in the future.'),
|
|
5583
5599
|
project_id: z3.string().optional().describe("Social Neuron project ID to associate this post with."),
|
|
5584
5600
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text."),
|
|
5585
5601
|
attribution: z3.boolean().optional().describe(
|
|
@@ -5733,7 +5749,7 @@ Created with Social Neuron`;
|
|
|
5733
5749
|
);
|
|
5734
5750
|
server2.tool(
|
|
5735
5751
|
"list_connected_accounts",
|
|
5736
|
-
"
|
|
5752
|
+
"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.",
|
|
5737
5753
|
{
|
|
5738
5754
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
5739
5755
|
},
|
|
@@ -5798,7 +5814,7 @@ Created with Social Neuron`;
|
|
|
5798
5814
|
);
|
|
5799
5815
|
server2.tool(
|
|
5800
5816
|
"list_recent_posts",
|
|
5801
|
-
"List recent
|
|
5817
|
+
"List recent published and scheduled posts with status, platform, title, and timestamps. Use to check what has been posted before planning new content, or to find post IDs for fetch_analytics. Filter by platform or status to narrow results.",
|
|
5802
5818
|
{
|
|
5803
5819
|
platform: z3.enum([
|
|
5804
5820
|
"youtube",
|
|
@@ -6639,7 +6655,7 @@ function registerAnalyticsTools(server2) {
|
|
|
6639
6655
|
"threads",
|
|
6640
6656
|
"bluesky"
|
|
6641
6657
|
]).optional().describe("Filter analytics to a specific platform."),
|
|
6642
|
-
days: z4.number().min(1).max(365).optional().describe("
|
|
6658
|
+
days: z4.number().min(1).max(365).optional().describe("Lookback window in days (1-365). Default 30. Use 7 for weekly review, 30 for monthly summary, 90 for quarterly trends."),
|
|
6643
6659
|
content_id: z4.string().uuid().optional().describe(
|
|
6644
6660
|
"Filter to a specific content_history ID to see performance of one piece of content."
|
|
6645
6661
|
),
|
|
@@ -6819,7 +6835,7 @@ function registerAnalyticsTools(server2) {
|
|
|
6819
6835
|
);
|
|
6820
6836
|
server2.tool(
|
|
6821
6837
|
"refresh_platform_analytics",
|
|
6822
|
-
"
|
|
6838
|
+
"Queue analytics refresh jobs for all posts from the last 7 days across connected platforms. Call this before fetch_analytics if you need fresh data. Returns immediately \u2014 data updates asynchronously over the next 1-5 minutes.",
|
|
6823
6839
|
{
|
|
6824
6840
|
response_format: z4.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6825
6841
|
},
|
|
@@ -8755,13 +8771,11 @@ function asEnvelope6(data) {
|
|
|
8755
8771
|
function registerCommentsTools(server2) {
|
|
8756
8772
|
server2.tool(
|
|
8757
8773
|
"list_comments",
|
|
8758
|
-
|
|
8774
|
+
'List YouTube comments \u2014 pass video_id (11-char string, e.g. "dQw4w9WgXcQ") for a specific video, or omit for recent comments across all channel videos. Returns comment text, author, like count, and reply count. Use page_token from previous response for pagination.',
|
|
8759
8775
|
{
|
|
8760
|
-
video_id: z10.string().optional().describe(
|
|
8761
|
-
"YouTube video ID. If omitted, returns comments across all channel videos."
|
|
8762
|
-
),
|
|
8776
|
+
video_id: z10.string().optional().describe('YouTube video ID \u2014 the 11-character string from the URL (e.g. "dQw4w9WgXcQ" from youtube.com/watch?v=dQw4w9WgXcQ). Omit to get recent comments across all channel videos.'),
|
|
8763
8777
|
max_results: z10.number().min(1).max(100).optional().describe("Maximum number of comments to return. Defaults to 50."),
|
|
8764
|
-
page_token: z10.string().optional().describe("Pagination
|
|
8778
|
+
page_token: z10.string().optional().describe("Pagination cursor from previous list_comments response nextPageToken field. Omit for first page of results."),
|
|
8765
8779
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
8766
8780
|
},
|
|
8767
8781
|
async ({ video_id, max_results, page_token, response_format }) => {
|
|
@@ -8826,7 +8840,7 @@ function registerCommentsTools(server2) {
|
|
|
8826
8840
|
);
|
|
8827
8841
|
server2.tool(
|
|
8828
8842
|
"reply_to_comment",
|
|
8829
|
-
"Reply to a YouTube comment.
|
|
8843
|
+
"Reply to a YouTube comment. Get the parent_id from list_comments results. Reply appears as the authenticated channel. Use for community engagement after checking list_comments for questions or feedback.",
|
|
8830
8844
|
{
|
|
8831
8845
|
parent_id: z10.string().describe(
|
|
8832
8846
|
"The ID of the parent comment to reply to (from list_comments)."
|
|
@@ -9359,7 +9373,7 @@ function asEnvelope8(data) {
|
|
|
9359
9373
|
function registerCreditsTools(server2) {
|
|
9360
9374
|
server2.tool(
|
|
9361
9375
|
"get_credit_balance",
|
|
9362
|
-
"
|
|
9376
|
+
"Check remaining credits, monthly limit, spending cap, and plan tier. Call this before expensive operations \u2014 generate_video costs 15-80 credits, generate_image costs 2-10. Returns current balance, monthly allocation, and spending cap (2.5x allocation).",
|
|
9363
9377
|
{
|
|
9364
9378
|
response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
9365
9379
|
},
|
|
@@ -9412,7 +9426,7 @@ Monthly used: ${payload.monthlyUsed}` + (payload.monthlyLimit ? ` / ${payload.mo
|
|
|
9412
9426
|
);
|
|
9413
9427
|
server2.tool(
|
|
9414
9428
|
"get_budget_status",
|
|
9415
|
-
"
|
|
9429
|
+
"Check how much of the per-session budget has been consumed. Tracks credits spent and assets created in this MCP session against configured limits. Use to avoid hitting budget caps mid-workflow.",
|
|
9416
9430
|
{
|
|
9417
9431
|
response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
9418
9432
|
},
|
|
@@ -9658,7 +9672,7 @@ function asEnvelope11(data) {
|
|
|
9658
9672
|
function registerAutopilotTools(server2) {
|
|
9659
9673
|
server2.tool(
|
|
9660
9674
|
"list_autopilot_configs",
|
|
9661
|
-
"List
|
|
9675
|
+
"List autopilot configurations showing schedules, credit budgets, last run times, and active/inactive status. Use to check what is automated before creating new configs, or to find config_id for update_autopilot_config.",
|
|
9662
9676
|
{
|
|
9663
9677
|
active_only: z15.boolean().optional().describe("If true, only return active configs. Defaults to false (show all)."),
|
|
9664
9678
|
response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
@@ -9804,7 +9818,7 @@ Schedule: ${JSON.stringify(updated.schedule_config)}`
|
|
|
9804
9818
|
);
|
|
9805
9819
|
server2.tool(
|
|
9806
9820
|
"get_autopilot_status",
|
|
9807
|
-
"Get
|
|
9821
|
+
"Get autopilot system overview: active config count, recent execution results, credits consumed, and next scheduled run time. Use as a dashboard check before modifying autopilot settings.",
|
|
9808
9822
|
{
|
|
9809
9823
|
response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
9810
9824
|
},
|
|
@@ -9923,7 +9937,7 @@ ${content.suggested_hooks.map((h) => ` - ${h}`).join("\n")}`
|
|
|
9923
9937
|
function registerExtractionTools(server2) {
|
|
9924
9938
|
server2.tool(
|
|
9925
9939
|
"extract_url_content",
|
|
9926
|
-
"Extract content from
|
|
9940
|
+
"Extract text content from any URL \u2014 YouTube video transcripts, article text, or product page features/benefits/USP. YouTube URLs auto-route to transcript extraction with optional comments. Use before generate_content to repurpose existing content, or before plan_content_week to base a content plan on a source URL.",
|
|
9927
9941
|
{
|
|
9928
9942
|
url: z16.string().url().describe("URL to extract content from"),
|
|
9929
9943
|
extract_type: z16.enum(["auto", "transcript", "article", "product"]).default("auto").describe("Type of extraction"),
|
|
@@ -10123,9 +10137,9 @@ function asEnvelope13(data) {
|
|
|
10123
10137
|
function registerQualityTools(server2) {
|
|
10124
10138
|
server2.tool(
|
|
10125
10139
|
"quality_check",
|
|
10126
|
-
"Score
|
|
10140
|
+
"Score post quality across 7 categories: Hook Strength, Message Clarity, Platform Fit, Brand Alignment, Novelty, CTA Strength, and Safety/Claims. Each scored 0-5, total 35. Default pass threshold is 26 (~75%). Run after generate_content and before schedule_post. Include hashtags in caption if they will be published \u2014 they affect Platform Fit and Safety scores.",
|
|
10127
10141
|
{
|
|
10128
|
-
caption: z17.string().describe("
|
|
10142
|
+
caption: z17.string().describe("The post text to score. Include hashtags if they will be published \u2014 they affect Platform Fit and Safety/Claims scores."),
|
|
10129
10143
|
title: z17.string().optional().describe("Post title (important for YouTube)"),
|
|
10130
10144
|
platforms: z17.array(
|
|
10131
10145
|
z17.enum([
|
|
@@ -10139,7 +10153,7 @@ function registerQualityTools(server2) {
|
|
|
10139
10153
|
"bluesky"
|
|
10140
10154
|
])
|
|
10141
10155
|
).min(1).describe("Target platforms"),
|
|
10142
|
-
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass"),
|
|
10156
|
+
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass (max 35, scored across 7 categories at 0-5 each). Default 26 (~75%). Use 20 for rough drafts, 28+ for final posts going to large audiences."),
|
|
10143
10157
|
brand_keyword: z17.string().optional().describe("Brand keyword for alignment check"),
|
|
10144
10158
|
brand_avoid_patterns: z17.array(z17.string()).optional(),
|
|
10145
10159
|
custom_banned_terms: z17.array(z17.string()).optional(),
|
|
@@ -10210,7 +10224,7 @@ function registerQualityTools(server2) {
|
|
|
10210
10224
|
);
|
|
10211
10225
|
server2.tool(
|
|
10212
10226
|
"quality_check_plan",
|
|
10213
|
-
"
|
|
10227
|
+
"Batch quality check all posts in a content plan. Returns per-post scores and aggregate pass/fail summary. Use after plan_content_week and before schedule_content_plan to catch low-quality posts before publishing.",
|
|
10214
10228
|
{
|
|
10215
10229
|
plan: z17.object({
|
|
10216
10230
|
posts: z17.array(
|
|
@@ -10222,7 +10236,7 @@ function registerQualityTools(server2) {
|
|
|
10222
10236
|
})
|
|
10223
10237
|
)
|
|
10224
10238
|
}).passthrough().describe("Content plan with posts array"),
|
|
10225
|
-
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass"),
|
|
10239
|
+
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass (max 35, scored across 7 categories at 0-5 each). Default 26 (~75%). Use 20 for rough drafts, 28+ for final posts going to large audiences."),
|
|
10226
10240
|
response_format: z17.enum(["text", "json"]).default("text")
|
|
10227
10241
|
},
|
|
10228
10242
|
async ({ plan, threshold, response_format }) => {
|
|
@@ -10405,7 +10419,7 @@ function formatPlanAsText(plan) {
|
|
|
10405
10419
|
function registerPlanningTools(server2) {
|
|
10406
10420
|
server2.tool(
|
|
10407
10421
|
"plan_content_week",
|
|
10408
|
-
"Generate a full
|
|
10422
|
+
"Generate a full content plan with platform-specific drafts, hooks, angles, and optimal schedule times. Pass a topic or source_url \u2014 brand context and performance insights auto-load via project_id. Output feeds directly into quality_check_plan then schedule_content_plan. Costs ~5-15 credits depending on post count.",
|
|
10409
10423
|
{
|
|
10410
10424
|
topic: z18.string().describe("Main topic or content theme"),
|
|
10411
10425
|
source_url: z18.string().optional().describe("URL to extract content from (YouTube, article)"),
|
|
@@ -10741,7 +10755,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
10741
10755
|
);
|
|
10742
10756
|
server2.tool(
|
|
10743
10757
|
"save_content_plan",
|
|
10744
|
-
"
|
|
10758
|
+
"Save a content plan to the database for team review, approval workflows, and scheduled publishing. Creates a plan_id you can reference in get_content_plan, update_content_plan, and schedule_content_plan.",
|
|
10745
10759
|
{
|
|
10746
10760
|
plan: z18.object({
|
|
10747
10761
|
topic: z18.string(),
|
|
@@ -11406,7 +11420,7 @@ import { z as z20 } from "zod";
|
|
|
11406
11420
|
function registerDiscoveryTools(server2) {
|
|
11407
11421
|
server2.tool(
|
|
11408
11422
|
"search_tools",
|
|
11409
|
-
'Search
|
|
11423
|
+
'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.',
|
|
11410
11424
|
{
|
|
11411
11425
|
query: z20.string().optional().describe("Search query to filter tools by name or description"),
|
|
11412
11426
|
module: z20.string().optional().describe('Filter by module name (e.g. "planning", "content", "analytics")'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socialneuron/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "MCP server for Social Neuron - AI content creation platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -61,10 +61,10 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
64
|
-
"@supabase/supabase-js": "2.
|
|
64
|
+
"@supabase/supabase-js": "2.99.2",
|
|
65
65
|
"express": "^5.1.0",
|
|
66
66
|
"jose": "^6.2.1",
|
|
67
|
-
"open": "
|
|
67
|
+
"open": "11.0.0",
|
|
68
68
|
"zod": "^4.0.0"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@types/node": "^25.3.5",
|
|
76
76
|
"esbuild": "^0.27.3",
|
|
77
77
|
"typescript": "^5.9.3",
|
|
78
|
-
"vitest": "^
|
|
78
|
+
"vitest": "^4.1.0"
|
|
79
79
|
},
|
|
80
80
|
"engines": {
|
|
81
81
|
"node": ">=20.0.0 <21.0.0 || >=22.0.0"
|