@socialneuron/mcp-server 1.5.2 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +62 -13
- package/dist/http.js +1015 -13
- package/dist/index.js +11 -18
- package/package.json +3 -1
package/dist/http.js
CHANGED
|
@@ -343,7 +343,6 @@ __export(supabase_exports, {
|
|
|
343
343
|
CLOUD_SUPABASE_URL: () => CLOUD_SUPABASE_URL,
|
|
344
344
|
getAuthMode: () => getAuthMode,
|
|
345
345
|
getAuthenticatedApiKey: () => getAuthenticatedApiKey,
|
|
346
|
-
getAuthenticatedEmail: () => getAuthenticatedEmail,
|
|
347
346
|
getAuthenticatedExpiresAt: () => getAuthenticatedExpiresAt,
|
|
348
347
|
getAuthenticatedScopes: () => getAuthenticatedScopes,
|
|
349
348
|
getDefaultProjectId: () => getDefaultProjectId,
|
|
@@ -431,7 +430,6 @@ async function initializeAuth() {
|
|
|
431
430
|
_authMode = "api-key";
|
|
432
431
|
authenticatedUserId = result.userId;
|
|
433
432
|
authenticatedScopes = result.scopes && result.scopes.length > 0 ? result.scopes : ["mcp:read"];
|
|
434
|
-
authenticatedEmail = result.email || null;
|
|
435
433
|
authenticatedExpiresAt = result.expiresAt || null;
|
|
436
434
|
console.error(
|
|
437
435
|
"[MCP] Authenticated via API key (prefix: " + apiKey.substring(0, 6) + "..." + apiKey.slice(-4) + ")"
|
|
@@ -487,9 +485,6 @@ function getMcpRunId() {
|
|
|
487
485
|
function getAuthenticatedScopes() {
|
|
488
486
|
return authenticatedScopes;
|
|
489
487
|
}
|
|
490
|
-
function getAuthenticatedEmail() {
|
|
491
|
-
return authenticatedEmail;
|
|
492
|
-
}
|
|
493
488
|
function getAuthenticatedExpiresAt() {
|
|
494
489
|
return authenticatedExpiresAt;
|
|
495
490
|
}
|
|
@@ -528,7 +523,7 @@ async function logMcpToolInvocation(args) {
|
|
|
528
523
|
captureToolEvent(args).catch(() => {
|
|
529
524
|
});
|
|
530
525
|
}
|
|
531
|
-
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes,
|
|
526
|
+
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes, authenticatedExpiresAt, authenticatedApiKey, MCP_RUN_ID, CLOUD_SUPABASE_URL, CLOUD_SUPABASE_ANON_KEY, projectIdCache;
|
|
532
527
|
var init_supabase = __esm({
|
|
533
528
|
"src/lib/supabase.ts"() {
|
|
534
529
|
"use strict";
|
|
@@ -540,7 +535,6 @@ var init_supabase = __esm({
|
|
|
540
535
|
_authMode = "service-role";
|
|
541
536
|
authenticatedUserId = null;
|
|
542
537
|
authenticatedScopes = [];
|
|
543
|
-
authenticatedEmail = null;
|
|
544
538
|
authenticatedExpiresAt = null;
|
|
545
539
|
authenticatedApiKey = null;
|
|
546
540
|
MCP_RUN_ID = randomUUID();
|
|
@@ -787,6 +781,8 @@ async function callEdgeFunction(functionName, body, options) {
|
|
|
787
781
|
var CATEGORY_CONFIGS = {
|
|
788
782
|
posting: { maxTokens: 30, refillRate: 30 / 60 },
|
|
789
783
|
// 30 req/min
|
|
784
|
+
generation: { maxTokens: 20, refillRate: 20 / 60 },
|
|
785
|
+
// 20 req/min — AI content generation (mcp:write)
|
|
790
786
|
screenshot: { maxTokens: 10, refillRate: 10 / 60 },
|
|
791
787
|
// 10 req/min
|
|
792
788
|
read: { maxTokens: 60, refillRate: 60 / 60 }
|
|
@@ -1363,12 +1359,24 @@ function sanitizeDbError(error) {
|
|
|
1363
1359
|
}
|
|
1364
1360
|
return "Database operation failed. Please try again.";
|
|
1365
1361
|
}
|
|
1362
|
+
function sanitizeError(error) {
|
|
1363
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
1364
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1365
|
+
console.error("[Error]", msg);
|
|
1366
|
+
}
|
|
1367
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
1368
|
+
if (pattern.test(msg)) {
|
|
1369
|
+
return userMessage;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return "An unexpected error occurred. Please try again.";
|
|
1373
|
+
}
|
|
1366
1374
|
|
|
1367
1375
|
// src/tools/content.ts
|
|
1368
1376
|
init_request_context();
|
|
1369
1377
|
|
|
1370
1378
|
// src/lib/version.ts
|
|
1371
|
-
var MCP_VERSION = "1.
|
|
1379
|
+
var MCP_VERSION = "1.6.0";
|
|
1372
1380
|
|
|
1373
1381
|
// src/tools/content.ts
|
|
1374
1382
|
var MAX_CREDITS_PER_RUN = Math.max(
|
|
@@ -9449,7 +9457,7 @@ async function verifyApiKey(apiKey, supabaseUrl, supabaseAnonKey) {
|
|
|
9449
9457
|
clientId: "api-key",
|
|
9450
9458
|
scopes: data.scopes ?? ["mcp:read"],
|
|
9451
9459
|
expiresAt,
|
|
9452
|
-
extra: { userId: data.userId
|
|
9460
|
+
extra: { userId: data.userId }
|
|
9453
9461
|
};
|
|
9454
9462
|
} catch (err) {
|
|
9455
9463
|
clearTimeout(timer);
|
|
@@ -9462,6 +9470,986 @@ async function verifyApiKey(apiKey, supabaseUrl, supabaseAnonKey) {
|
|
|
9462
9470
|
|
|
9463
9471
|
// src/http.ts
|
|
9464
9472
|
init_posthog();
|
|
9473
|
+
|
|
9474
|
+
// src/api/tool-executor.ts
|
|
9475
|
+
var toolHandlers = /* @__PURE__ */ new Map();
|
|
9476
|
+
function captureToolHandlers(server) {
|
|
9477
|
+
const original = server.tool.bind(server);
|
|
9478
|
+
server.tool = function capturedTool(...args) {
|
|
9479
|
+
const name = args[0];
|
|
9480
|
+
const handlerIndex = args.findIndex(
|
|
9481
|
+
(a, i) => i > 0 && typeof a === "function"
|
|
9482
|
+
);
|
|
9483
|
+
if (handlerIndex !== -1) {
|
|
9484
|
+
toolHandlers.set(name, args[handlerIndex]);
|
|
9485
|
+
}
|
|
9486
|
+
return original(...args);
|
|
9487
|
+
};
|
|
9488
|
+
}
|
|
9489
|
+
async function executeToolDirect(name, args) {
|
|
9490
|
+
const meta = {
|
|
9491
|
+
tool: name,
|
|
9492
|
+
version: MCP_VERSION,
|
|
9493
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9494
|
+
};
|
|
9495
|
+
const handler = toolHandlers.get(name);
|
|
9496
|
+
if (!handler) {
|
|
9497
|
+
return {
|
|
9498
|
+
data: null,
|
|
9499
|
+
error: `Tool '${name}' not found. Use GET /v1/tools to list available tools.`,
|
|
9500
|
+
isError: true,
|
|
9501
|
+
_meta: meta
|
|
9502
|
+
};
|
|
9503
|
+
}
|
|
9504
|
+
try {
|
|
9505
|
+
const result = await handler(args);
|
|
9506
|
+
const textContent = result.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
9507
|
+
if (result.isError) {
|
|
9508
|
+
return { data: null, error: textContent, isError: true, _meta: meta };
|
|
9509
|
+
}
|
|
9510
|
+
let data;
|
|
9511
|
+
try {
|
|
9512
|
+
data = JSON.parse(textContent);
|
|
9513
|
+
} catch {
|
|
9514
|
+
data = { text: textContent };
|
|
9515
|
+
}
|
|
9516
|
+
return { data, error: null, isError: false, _meta: meta };
|
|
9517
|
+
} catch (err) {
|
|
9518
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9519
|
+
return { data: null, error: message, isError: true, _meta: meta };
|
|
9520
|
+
}
|
|
9521
|
+
}
|
|
9522
|
+
function hasRegisteredTool(name) {
|
|
9523
|
+
return toolHandlers.has(name);
|
|
9524
|
+
}
|
|
9525
|
+
function getRegisteredToolCount() {
|
|
9526
|
+
return toolHandlers.size;
|
|
9527
|
+
}
|
|
9528
|
+
function checkToolScope(toolName, userScopes) {
|
|
9529
|
+
const requiredScope = TOOL_SCOPES[toolName];
|
|
9530
|
+
if (!requiredScope) {
|
|
9531
|
+
return { allowed: false, requiredScope: null };
|
|
9532
|
+
}
|
|
9533
|
+
return { allowed: hasScope(userScopes, requiredScope), requiredScope };
|
|
9534
|
+
}
|
|
9535
|
+
function getToolCatalogForApi() {
|
|
9536
|
+
return TOOL_CATALOG.filter((tool) => toolHandlers.has(tool.name)).map(
|
|
9537
|
+
(tool) => ({
|
|
9538
|
+
...tool,
|
|
9539
|
+
endpoint: `/v1/tools/${tool.name}`,
|
|
9540
|
+
method: "POST"
|
|
9541
|
+
})
|
|
9542
|
+
);
|
|
9543
|
+
}
|
|
9544
|
+
|
|
9545
|
+
// src/api/router.ts
|
|
9546
|
+
import { Router } from "express";
|
|
9547
|
+
init_request_context();
|
|
9548
|
+
|
|
9549
|
+
// src/api/openapi.ts
|
|
9550
|
+
function generateOpenApiSpec() {
|
|
9551
|
+
const modules = [...new Set(TOOL_CATALOG.map((t) => t.module))];
|
|
9552
|
+
const toolPaths = {};
|
|
9553
|
+
for (const tool of TOOL_CATALOG) {
|
|
9554
|
+
toolPaths[`/v1/tools/${tool.name}`] = {
|
|
9555
|
+
post: {
|
|
9556
|
+
operationId: tool.name,
|
|
9557
|
+
summary: tool.description,
|
|
9558
|
+
tags: [tool.module],
|
|
9559
|
+
"x-required-scope": tool.scope,
|
|
9560
|
+
security: [{ bearerAuth: [] }],
|
|
9561
|
+
requestBody: {
|
|
9562
|
+
required: false,
|
|
9563
|
+
content: {
|
|
9564
|
+
"application/json": {
|
|
9565
|
+
schema: {
|
|
9566
|
+
type: "object",
|
|
9567
|
+
description: `Input parameters for ${tool.name}. Pass tool-specific arguments as JSON.`
|
|
9568
|
+
}
|
|
9569
|
+
}
|
|
9570
|
+
}
|
|
9571
|
+
},
|
|
9572
|
+
responses: {
|
|
9573
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9574
|
+
"400": { $ref: "#/components/responses/ToolError" },
|
|
9575
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9576
|
+
"403": { $ref: "#/components/responses/InsufficientScope" },
|
|
9577
|
+
"404": { $ref: "#/components/responses/NotFound" },
|
|
9578
|
+
"429": { $ref: "#/components/responses/RateLimited" }
|
|
9579
|
+
}
|
|
9580
|
+
}
|
|
9581
|
+
};
|
|
9582
|
+
}
|
|
9583
|
+
return {
|
|
9584
|
+
openapi: "3.1.0",
|
|
9585
|
+
info: {
|
|
9586
|
+
title: "Social Neuron API",
|
|
9587
|
+
version: MCP_VERSION,
|
|
9588
|
+
description: "AI content creation platform \u2014 generate, schedule, and analyze social media content across platforms. 52 tools accessible via REST API, MCP, CLI, or SDK. Same auth, scopes, and credit system across all methods.",
|
|
9589
|
+
contact: {
|
|
9590
|
+
name: "Social Neuron",
|
|
9591
|
+
email: "socialneuronteam@gmail.com",
|
|
9592
|
+
url: "https://socialneuron.com/for-developers"
|
|
9593
|
+
},
|
|
9594
|
+
license: { name: "MIT", url: "https://opensource.org/licenses/MIT" },
|
|
9595
|
+
termsOfService: "https://socialneuron.com/terms"
|
|
9596
|
+
},
|
|
9597
|
+
servers: [
|
|
9598
|
+
{
|
|
9599
|
+
url: "https://mcp.socialneuron.com",
|
|
9600
|
+
description: "Production"
|
|
9601
|
+
}
|
|
9602
|
+
],
|
|
9603
|
+
tags: [
|
|
9604
|
+
{
|
|
9605
|
+
name: "tools",
|
|
9606
|
+
description: "Tool discovery and universal tool proxy"
|
|
9607
|
+
},
|
|
9608
|
+
{
|
|
9609
|
+
name: "credits",
|
|
9610
|
+
description: "Credit balance and budget tracking"
|
|
9611
|
+
},
|
|
9612
|
+
{
|
|
9613
|
+
name: "brand",
|
|
9614
|
+
description: "Brand profile management"
|
|
9615
|
+
},
|
|
9616
|
+
{
|
|
9617
|
+
name: "analytics",
|
|
9618
|
+
description: "Performance analytics and insights"
|
|
9619
|
+
},
|
|
9620
|
+
{
|
|
9621
|
+
name: "content",
|
|
9622
|
+
description: "Content generation (text, image, video)"
|
|
9623
|
+
},
|
|
9624
|
+
{
|
|
9625
|
+
name: "distribution",
|
|
9626
|
+
description: "Post scheduling and publishing"
|
|
9627
|
+
},
|
|
9628
|
+
{
|
|
9629
|
+
name: "posts",
|
|
9630
|
+
description: "Post listing and status"
|
|
9631
|
+
},
|
|
9632
|
+
...modules.map((m) => ({
|
|
9633
|
+
name: m,
|
|
9634
|
+
description: `${m} tools (via tool proxy)`
|
|
9635
|
+
}))
|
|
9636
|
+
],
|
|
9637
|
+
paths: {
|
|
9638
|
+
"/v1/": {
|
|
9639
|
+
get: {
|
|
9640
|
+
operationId: "getApiInfo",
|
|
9641
|
+
summary: "API info and discovery",
|
|
9642
|
+
tags: ["tools"],
|
|
9643
|
+
security: [{ bearerAuth: [] }],
|
|
9644
|
+
responses: {
|
|
9645
|
+
"200": {
|
|
9646
|
+
description: "API metadata",
|
|
9647
|
+
content: {
|
|
9648
|
+
"application/json": {
|
|
9649
|
+
schema: { type: "object" }
|
|
9650
|
+
}
|
|
9651
|
+
}
|
|
9652
|
+
}
|
|
9653
|
+
}
|
|
9654
|
+
}
|
|
9655
|
+
},
|
|
9656
|
+
"/v1/tools": {
|
|
9657
|
+
get: {
|
|
9658
|
+
operationId: "listTools",
|
|
9659
|
+
summary: "List available tools with optional filtering",
|
|
9660
|
+
tags: ["tools"],
|
|
9661
|
+
security: [{ bearerAuth: [] }],
|
|
9662
|
+
parameters: [
|
|
9663
|
+
{
|
|
9664
|
+
name: "module",
|
|
9665
|
+
in: "query",
|
|
9666
|
+
schema: { type: "string" },
|
|
9667
|
+
description: "Filter by module name"
|
|
9668
|
+
},
|
|
9669
|
+
{
|
|
9670
|
+
name: "scope",
|
|
9671
|
+
in: "query",
|
|
9672
|
+
schema: { type: "string" },
|
|
9673
|
+
description: "Filter by required scope"
|
|
9674
|
+
},
|
|
9675
|
+
{
|
|
9676
|
+
name: "q",
|
|
9677
|
+
in: "query",
|
|
9678
|
+
schema: { type: "string" },
|
|
9679
|
+
description: "Search tools by keyword"
|
|
9680
|
+
}
|
|
9681
|
+
],
|
|
9682
|
+
responses: {
|
|
9683
|
+
"200": {
|
|
9684
|
+
description: "Tool catalog",
|
|
9685
|
+
content: {
|
|
9686
|
+
"application/json": {
|
|
9687
|
+
schema: {
|
|
9688
|
+
type: "object",
|
|
9689
|
+
properties: {
|
|
9690
|
+
data: {
|
|
9691
|
+
type: "object",
|
|
9692
|
+
properties: {
|
|
9693
|
+
tools: {
|
|
9694
|
+
type: "array",
|
|
9695
|
+
items: { $ref: "#/components/schemas/ToolEntry" }
|
|
9696
|
+
},
|
|
9697
|
+
total: { type: "integer" },
|
|
9698
|
+
modules: {
|
|
9699
|
+
type: "array",
|
|
9700
|
+
items: { type: "string" }
|
|
9701
|
+
}
|
|
9702
|
+
}
|
|
9703
|
+
},
|
|
9704
|
+
_meta: { $ref: "#/components/schemas/Meta" }
|
|
9705
|
+
}
|
|
9706
|
+
}
|
|
9707
|
+
}
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
}
|
|
9712
|
+
},
|
|
9713
|
+
"/v1/credits": {
|
|
9714
|
+
get: {
|
|
9715
|
+
operationId: "getCreditBalance",
|
|
9716
|
+
summary: "Get credit balance, plan, and monthly usage",
|
|
9717
|
+
tags: ["credits"],
|
|
9718
|
+
"x-tool-name": "get_credit_balance",
|
|
9719
|
+
"x-required-scope": "mcp:read",
|
|
9720
|
+
security: [{ bearerAuth: [] }],
|
|
9721
|
+
responses: {
|
|
9722
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9723
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9724
|
+
}
|
|
9725
|
+
}
|
|
9726
|
+
},
|
|
9727
|
+
"/v1/credits/budget": {
|
|
9728
|
+
get: {
|
|
9729
|
+
operationId: "getBudgetStatus",
|
|
9730
|
+
summary: "Get per-session budget and spending status",
|
|
9731
|
+
tags: ["credits"],
|
|
9732
|
+
"x-tool-name": "get_budget_status",
|
|
9733
|
+
"x-required-scope": "mcp:read",
|
|
9734
|
+
security: [{ bearerAuth: [] }],
|
|
9735
|
+
responses: {
|
|
9736
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9737
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9738
|
+
}
|
|
9739
|
+
}
|
|
9740
|
+
},
|
|
9741
|
+
"/v1/brand": {
|
|
9742
|
+
get: {
|
|
9743
|
+
operationId: "getBrandProfile",
|
|
9744
|
+
summary: "Get current brand profile",
|
|
9745
|
+
tags: ["brand"],
|
|
9746
|
+
"x-tool-name": "get_brand_profile",
|
|
9747
|
+
"x-required-scope": "mcp:read",
|
|
9748
|
+
security: [{ bearerAuth: [] }],
|
|
9749
|
+
responses: {
|
|
9750
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9751
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9754
|
+
},
|
|
9755
|
+
"/v1/analytics": {
|
|
9756
|
+
get: {
|
|
9757
|
+
operationId: "fetchAnalytics",
|
|
9758
|
+
summary: "Fetch post performance analytics",
|
|
9759
|
+
tags: ["analytics"],
|
|
9760
|
+
"x-tool-name": "fetch_analytics",
|
|
9761
|
+
"x-required-scope": "mcp:read",
|
|
9762
|
+
security: [{ bearerAuth: [] }],
|
|
9763
|
+
responses: {
|
|
9764
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9765
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
},
|
|
9769
|
+
"/v1/analytics/insights": {
|
|
9770
|
+
get: {
|
|
9771
|
+
operationId: "getPerformanceInsights",
|
|
9772
|
+
summary: "Get AI-generated performance insights",
|
|
9773
|
+
tags: ["analytics"],
|
|
9774
|
+
"x-tool-name": "get_performance_insights",
|
|
9775
|
+
"x-required-scope": "mcp:read",
|
|
9776
|
+
security: [{ bearerAuth: [] }],
|
|
9777
|
+
responses: {
|
|
9778
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9779
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9780
|
+
}
|
|
9781
|
+
}
|
|
9782
|
+
},
|
|
9783
|
+
"/v1/analytics/best-times": {
|
|
9784
|
+
get: {
|
|
9785
|
+
operationId: "getBestPostingTimes",
|
|
9786
|
+
summary: "Get recommended posting times based on audience data",
|
|
9787
|
+
tags: ["analytics"],
|
|
9788
|
+
"x-tool-name": "get_best_posting_times",
|
|
9789
|
+
"x-required-scope": "mcp:read",
|
|
9790
|
+
security: [{ bearerAuth: [] }],
|
|
9791
|
+
responses: {
|
|
9792
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9793
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
},
|
|
9797
|
+
"/v1/posts": {
|
|
9798
|
+
get: {
|
|
9799
|
+
operationId: "listRecentPosts",
|
|
9800
|
+
summary: "List recently published or scheduled posts",
|
|
9801
|
+
tags: ["posts"],
|
|
9802
|
+
"x-tool-name": "list_recent_posts",
|
|
9803
|
+
"x-required-scope": "mcp:read",
|
|
9804
|
+
security: [{ bearerAuth: [] }],
|
|
9805
|
+
parameters: [
|
|
9806
|
+
{
|
|
9807
|
+
name: "limit",
|
|
9808
|
+
in: "query",
|
|
9809
|
+
schema: { type: "integer", default: 20 },
|
|
9810
|
+
description: "Max number of posts to return"
|
|
9811
|
+
}
|
|
9812
|
+
],
|
|
9813
|
+
responses: {
|
|
9814
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9815
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
},
|
|
9819
|
+
"/v1/accounts": {
|
|
9820
|
+
get: {
|
|
9821
|
+
operationId: "listConnectedAccounts",
|
|
9822
|
+
summary: "List connected social media accounts",
|
|
9823
|
+
tags: ["posts"],
|
|
9824
|
+
"x-tool-name": "list_connected_accounts",
|
|
9825
|
+
"x-required-scope": "mcp:read",
|
|
9826
|
+
security: [{ bearerAuth: [] }],
|
|
9827
|
+
responses: {
|
|
9828
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9829
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9830
|
+
}
|
|
9831
|
+
}
|
|
9832
|
+
},
|
|
9833
|
+
"/v1/content/generate": {
|
|
9834
|
+
post: {
|
|
9835
|
+
operationId: "generateContent",
|
|
9836
|
+
summary: "Generate social media content with AI",
|
|
9837
|
+
tags: ["content"],
|
|
9838
|
+
"x-tool-name": "generate_content",
|
|
9839
|
+
"x-required-scope": "mcp:write",
|
|
9840
|
+
security: [{ bearerAuth: [] }],
|
|
9841
|
+
requestBody: {
|
|
9842
|
+
required: true,
|
|
9843
|
+
content: {
|
|
9844
|
+
"application/json": {
|
|
9845
|
+
schema: {
|
|
9846
|
+
type: "object",
|
|
9847
|
+
properties: {
|
|
9848
|
+
topic: {
|
|
9849
|
+
type: "string",
|
|
9850
|
+
description: "Content topic or prompt"
|
|
9851
|
+
},
|
|
9852
|
+
platforms: {
|
|
9853
|
+
type: "array",
|
|
9854
|
+
items: { type: "string" },
|
|
9855
|
+
description: "Target platforms"
|
|
9856
|
+
},
|
|
9857
|
+
tone: { type: "string", description: "Content tone" },
|
|
9858
|
+
content_type: {
|
|
9859
|
+
type: "string",
|
|
9860
|
+
description: "Type of content to generate"
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
}
|
|
9864
|
+
}
|
|
9865
|
+
}
|
|
9866
|
+
},
|
|
9867
|
+
responses: {
|
|
9868
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9869
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9870
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
9871
|
+
}
|
|
9872
|
+
}
|
|
9873
|
+
},
|
|
9874
|
+
"/v1/content/adapt": {
|
|
9875
|
+
post: {
|
|
9876
|
+
operationId: "adaptContent",
|
|
9877
|
+
summary: "Adapt existing content for different platforms",
|
|
9878
|
+
tags: ["content"],
|
|
9879
|
+
"x-tool-name": "adapt_content",
|
|
9880
|
+
"x-required-scope": "mcp:write",
|
|
9881
|
+
security: [{ bearerAuth: [] }],
|
|
9882
|
+
requestBody: {
|
|
9883
|
+
required: true,
|
|
9884
|
+
content: {
|
|
9885
|
+
"application/json": {
|
|
9886
|
+
schema: {
|
|
9887
|
+
type: "object",
|
|
9888
|
+
properties: {
|
|
9889
|
+
content: {
|
|
9890
|
+
type: "string",
|
|
9891
|
+
description: "Content to adapt"
|
|
9892
|
+
},
|
|
9893
|
+
target_platforms: {
|
|
9894
|
+
type: "array",
|
|
9895
|
+
items: { type: "string" },
|
|
9896
|
+
description: "Target platforms for adaptation"
|
|
9897
|
+
}
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
}
|
|
9901
|
+
}
|
|
9902
|
+
},
|
|
9903
|
+
responses: {
|
|
9904
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9905
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9906
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
9907
|
+
}
|
|
9908
|
+
}
|
|
9909
|
+
},
|
|
9910
|
+
"/v1/content/video": {
|
|
9911
|
+
post: {
|
|
9912
|
+
operationId: "generateVideo",
|
|
9913
|
+
summary: "Generate video content using AI models",
|
|
9914
|
+
tags: ["content"],
|
|
9915
|
+
"x-tool-name": "generate_video",
|
|
9916
|
+
"x-required-scope": "mcp:write",
|
|
9917
|
+
security: [{ bearerAuth: [] }],
|
|
9918
|
+
requestBody: {
|
|
9919
|
+
required: true,
|
|
9920
|
+
content: {
|
|
9921
|
+
"application/json": {
|
|
9922
|
+
schema: {
|
|
9923
|
+
type: "object",
|
|
9924
|
+
properties: {
|
|
9925
|
+
prompt: { type: "string" },
|
|
9926
|
+
aspect_ratio: { type: "string" },
|
|
9927
|
+
duration: { type: "integer" }
|
|
9928
|
+
}
|
|
9929
|
+
}
|
|
9930
|
+
}
|
|
9931
|
+
}
|
|
9932
|
+
},
|
|
9933
|
+
responses: {
|
|
9934
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9935
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9936
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
9937
|
+
}
|
|
9938
|
+
}
|
|
9939
|
+
},
|
|
9940
|
+
"/v1/content/image": {
|
|
9941
|
+
post: {
|
|
9942
|
+
operationId: "generateImage",
|
|
9943
|
+
summary: "Generate images using AI models",
|
|
9944
|
+
tags: ["content"],
|
|
9945
|
+
"x-tool-name": "generate_image",
|
|
9946
|
+
"x-required-scope": "mcp:write",
|
|
9947
|
+
security: [{ bearerAuth: [] }],
|
|
9948
|
+
requestBody: {
|
|
9949
|
+
required: true,
|
|
9950
|
+
content: {
|
|
9951
|
+
"application/json": {
|
|
9952
|
+
schema: {
|
|
9953
|
+
type: "object",
|
|
9954
|
+
properties: {
|
|
9955
|
+
prompt: { type: "string" },
|
|
9956
|
+
aspect_ratio: { type: "string" },
|
|
9957
|
+
style: { type: "string" }
|
|
9958
|
+
}
|
|
9959
|
+
}
|
|
9960
|
+
}
|
|
9961
|
+
}
|
|
9962
|
+
},
|
|
9963
|
+
responses: {
|
|
9964
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9965
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9966
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
9967
|
+
}
|
|
9968
|
+
}
|
|
9969
|
+
},
|
|
9970
|
+
"/v1/content/status/{jobId}": {
|
|
9971
|
+
get: {
|
|
9972
|
+
operationId: "checkJobStatus",
|
|
9973
|
+
summary: "Check status of async content generation job",
|
|
9974
|
+
tags: ["content"],
|
|
9975
|
+
"x-tool-name": "check_status",
|
|
9976
|
+
"x-required-scope": "mcp:read",
|
|
9977
|
+
security: [{ bearerAuth: [] }],
|
|
9978
|
+
parameters: [
|
|
9979
|
+
{
|
|
9980
|
+
name: "jobId",
|
|
9981
|
+
in: "path",
|
|
9982
|
+
required: true,
|
|
9983
|
+
schema: { type: "string" },
|
|
9984
|
+
description: "Job ID to check"
|
|
9985
|
+
}
|
|
9986
|
+
],
|
|
9987
|
+
responses: {
|
|
9988
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9989
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9990
|
+
"404": { $ref: "#/components/responses/NotFound" }
|
|
9991
|
+
}
|
|
9992
|
+
}
|
|
9993
|
+
},
|
|
9994
|
+
"/v1/distribution/schedule": {
|
|
9995
|
+
post: {
|
|
9996
|
+
operationId: "schedulePost",
|
|
9997
|
+
summary: "Schedule or publish content to social platforms",
|
|
9998
|
+
tags: ["distribution"],
|
|
9999
|
+
"x-tool-name": "schedule_post",
|
|
10000
|
+
"x-required-scope": "mcp:distribute",
|
|
10001
|
+
security: [{ bearerAuth: [] }],
|
|
10002
|
+
requestBody: {
|
|
10003
|
+
required: true,
|
|
10004
|
+
content: {
|
|
10005
|
+
"application/json": {
|
|
10006
|
+
schema: {
|
|
10007
|
+
type: "object",
|
|
10008
|
+
properties: {
|
|
10009
|
+
media_url: {
|
|
10010
|
+
type: "string",
|
|
10011
|
+
description: "URL of media to post"
|
|
10012
|
+
},
|
|
10013
|
+
caption: {
|
|
10014
|
+
type: "string",
|
|
10015
|
+
description: "Post caption text"
|
|
10016
|
+
},
|
|
10017
|
+
platforms: {
|
|
10018
|
+
type: "array",
|
|
10019
|
+
items: { type: "string" },
|
|
10020
|
+
description: "Target platforms"
|
|
10021
|
+
},
|
|
10022
|
+
schedule_at: {
|
|
10023
|
+
type: "string",
|
|
10024
|
+
format: "date-time",
|
|
10025
|
+
description: "ISO 8601 schedule time (omit for immediate)"
|
|
10026
|
+
}
|
|
10027
|
+
}
|
|
10028
|
+
}
|
|
10029
|
+
}
|
|
10030
|
+
}
|
|
10031
|
+
},
|
|
10032
|
+
responses: {
|
|
10033
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
10034
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
10035
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
10036
|
+
}
|
|
10037
|
+
}
|
|
10038
|
+
},
|
|
10039
|
+
"/v1/loop": {
|
|
10040
|
+
get: {
|
|
10041
|
+
operationId: "getLoopSummary",
|
|
10042
|
+
summary: "Get growth loop summary and optimization recommendations",
|
|
10043
|
+
tags: ["analytics"],
|
|
10044
|
+
"x-tool-name": "get_loop_summary",
|
|
10045
|
+
"x-required-scope": "mcp:read",
|
|
10046
|
+
security: [{ bearerAuth: [] }],
|
|
10047
|
+
responses: {
|
|
10048
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
10049
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
10050
|
+
}
|
|
10051
|
+
}
|
|
10052
|
+
},
|
|
10053
|
+
// Spread all tool proxy paths
|
|
10054
|
+
...toolPaths
|
|
10055
|
+
},
|
|
10056
|
+
components: {
|
|
10057
|
+
securitySchemes: {
|
|
10058
|
+
bearerAuth: {
|
|
10059
|
+
type: "http",
|
|
10060
|
+
scheme: "bearer",
|
|
10061
|
+
description: "API key from Settings > Developer. Format: snk_live_..."
|
|
10062
|
+
}
|
|
10063
|
+
},
|
|
10064
|
+
schemas: {
|
|
10065
|
+
Meta: {
|
|
10066
|
+
type: "object",
|
|
10067
|
+
properties: {
|
|
10068
|
+
tool: { type: "string" },
|
|
10069
|
+
version: { type: "string" },
|
|
10070
|
+
timestamp: { type: "string", format: "date-time" }
|
|
10071
|
+
}
|
|
10072
|
+
},
|
|
10073
|
+
ToolEntry: {
|
|
10074
|
+
type: "object",
|
|
10075
|
+
properties: {
|
|
10076
|
+
name: { type: "string" },
|
|
10077
|
+
description: { type: "string" },
|
|
10078
|
+
module: { type: "string" },
|
|
10079
|
+
scope: { type: "string" },
|
|
10080
|
+
endpoint: { type: "string" },
|
|
10081
|
+
method: { type: "string" }
|
|
10082
|
+
}
|
|
10083
|
+
},
|
|
10084
|
+
ApiError: {
|
|
10085
|
+
type: "object",
|
|
10086
|
+
properties: {
|
|
10087
|
+
error: {
|
|
10088
|
+
type: "object",
|
|
10089
|
+
properties: {
|
|
10090
|
+
code: { type: "string" },
|
|
10091
|
+
message: { type: "string" },
|
|
10092
|
+
status: { type: "integer" }
|
|
10093
|
+
},
|
|
10094
|
+
required: ["code", "message", "status"]
|
|
10095
|
+
}
|
|
10096
|
+
}
|
|
10097
|
+
}
|
|
10098
|
+
},
|
|
10099
|
+
responses: {
|
|
10100
|
+
ToolSuccess: {
|
|
10101
|
+
description: "Successful tool execution",
|
|
10102
|
+
content: {
|
|
10103
|
+
"application/json": {
|
|
10104
|
+
schema: {
|
|
10105
|
+
type: "object",
|
|
10106
|
+
properties: {
|
|
10107
|
+
data: { type: "object" },
|
|
10108
|
+
_meta: { $ref: "#/components/schemas/Meta" }
|
|
10109
|
+
}
|
|
10110
|
+
}
|
|
10111
|
+
}
|
|
10112
|
+
}
|
|
10113
|
+
},
|
|
10114
|
+
ToolError: {
|
|
10115
|
+
description: "Tool execution error",
|
|
10116
|
+
content: {
|
|
10117
|
+
"application/json": {
|
|
10118
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10119
|
+
}
|
|
10120
|
+
}
|
|
10121
|
+
},
|
|
10122
|
+
Unauthorized: {
|
|
10123
|
+
description: "Missing or invalid Bearer token",
|
|
10124
|
+
content: {
|
|
10125
|
+
"application/json": {
|
|
10126
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10127
|
+
}
|
|
10128
|
+
}
|
|
10129
|
+
},
|
|
10130
|
+
InsufficientScope: {
|
|
10131
|
+
description: "API key lacks required scope",
|
|
10132
|
+
content: {
|
|
10133
|
+
"application/json": {
|
|
10134
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10135
|
+
}
|
|
10136
|
+
}
|
|
10137
|
+
},
|
|
10138
|
+
NotFound: {
|
|
10139
|
+
description: "Tool or resource not found",
|
|
10140
|
+
content: {
|
|
10141
|
+
"application/json": {
|
|
10142
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10143
|
+
}
|
|
10144
|
+
}
|
|
10145
|
+
},
|
|
10146
|
+
RateLimited: {
|
|
10147
|
+
description: "Rate limit exceeded",
|
|
10148
|
+
headers: {
|
|
10149
|
+
"Retry-After": {
|
|
10150
|
+
schema: { type: "integer" },
|
|
10151
|
+
description: "Seconds to wait before retrying"
|
|
10152
|
+
}
|
|
10153
|
+
},
|
|
10154
|
+
content: {
|
|
10155
|
+
"application/json": {
|
|
10156
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10157
|
+
}
|
|
10158
|
+
}
|
|
10159
|
+
}
|
|
10160
|
+
}
|
|
10161
|
+
},
|
|
10162
|
+
security: [{ bearerAuth: [] }]
|
|
10163
|
+
};
|
|
10164
|
+
}
|
|
10165
|
+
|
|
10166
|
+
// src/api/router.ts
|
|
10167
|
+
function createRestApiRouter(options) {
|
|
10168
|
+
const router = Router();
|
|
10169
|
+
const tokenVerifier2 = createTokenVerifier({
|
|
10170
|
+
supabaseUrl: options.supabaseUrl,
|
|
10171
|
+
supabaseAnonKey: options.supabaseAnonKey
|
|
10172
|
+
});
|
|
10173
|
+
async function authenticate(req, res, next) {
|
|
10174
|
+
const authHeader = req.headers.authorization;
|
|
10175
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
10176
|
+
res.status(401).json({
|
|
10177
|
+
error: {
|
|
10178
|
+
code: "unauthorized",
|
|
10179
|
+
message: "Bearer token required. Get your API key at https://socialneuron.com/settings/developer",
|
|
10180
|
+
status: 401
|
|
10181
|
+
}
|
|
10182
|
+
});
|
|
10183
|
+
return;
|
|
10184
|
+
}
|
|
10185
|
+
const token = authHeader.slice(7);
|
|
10186
|
+
try {
|
|
10187
|
+
const authInfo = await tokenVerifier2.verifyAccessToken(token);
|
|
10188
|
+
req.auth = {
|
|
10189
|
+
userId: authInfo.extra?.userId ?? authInfo.clientId,
|
|
10190
|
+
scopes: authInfo.scopes,
|
|
10191
|
+
clientId: authInfo.clientId,
|
|
10192
|
+
token: authInfo.token
|
|
10193
|
+
};
|
|
10194
|
+
next();
|
|
10195
|
+
} catch (err) {
|
|
10196
|
+
const message = err instanceof Error ? err.message : "Token verification failed";
|
|
10197
|
+
res.status(401).json({
|
|
10198
|
+
error: { code: "invalid_token", message, status: 401 }
|
|
10199
|
+
});
|
|
10200
|
+
}
|
|
10201
|
+
}
|
|
10202
|
+
function rateLimit(req, res, next) {
|
|
10203
|
+
const rl = checkRateLimit("read", req.auth.userId);
|
|
10204
|
+
if (!rl.allowed) {
|
|
10205
|
+
res.setHeader("Retry-After", String(rl.retryAfter));
|
|
10206
|
+
res.status(429).json({
|
|
10207
|
+
error: {
|
|
10208
|
+
code: "rate_limited",
|
|
10209
|
+
message: "Too many requests. Please slow down.",
|
|
10210
|
+
retry_after: rl.retryAfter,
|
|
10211
|
+
status: 429
|
|
10212
|
+
}
|
|
10213
|
+
});
|
|
10214
|
+
return;
|
|
10215
|
+
}
|
|
10216
|
+
next();
|
|
10217
|
+
}
|
|
10218
|
+
router.get("/openapi.json", (_req, res) => {
|
|
10219
|
+
res.setHeader("Cache-Control", "public, max-age=3600");
|
|
10220
|
+
res.json(generateOpenApiSpec());
|
|
10221
|
+
});
|
|
10222
|
+
router.get("/", (_req, res) => {
|
|
10223
|
+
res.json({
|
|
10224
|
+
name: "Social Neuron API",
|
|
10225
|
+
version: MCP_VERSION,
|
|
10226
|
+
description: "AI content creation platform \u2014 REST API",
|
|
10227
|
+
tools: getRegisteredToolCount(),
|
|
10228
|
+
documentation: "https://socialneuron.com/docs/rest-api",
|
|
10229
|
+
endpoints: {
|
|
10230
|
+
tools: "/v1/tools",
|
|
10231
|
+
tool_proxy: "/v1/tools/:name",
|
|
10232
|
+
credits: "/v1/credits",
|
|
10233
|
+
brand: "/v1/brand",
|
|
10234
|
+
analytics: "/v1/analytics",
|
|
10235
|
+
posts: "/v1/posts",
|
|
10236
|
+
accounts: "/v1/accounts",
|
|
10237
|
+
content_generate: "/v1/content/generate",
|
|
10238
|
+
distribution_schedule: "/v1/distribution/schedule",
|
|
10239
|
+
openapi: "/v1/openapi.json"
|
|
10240
|
+
},
|
|
10241
|
+
auth: {
|
|
10242
|
+
type: "Bearer token",
|
|
10243
|
+
header: "Authorization: Bearer <your-api-key>",
|
|
10244
|
+
get_key: "https://socialneuron.com/settings/developer"
|
|
10245
|
+
}
|
|
10246
|
+
});
|
|
10247
|
+
});
|
|
10248
|
+
router.use(
|
|
10249
|
+
authenticate
|
|
10250
|
+
);
|
|
10251
|
+
router.use(
|
|
10252
|
+
rateLimit
|
|
10253
|
+
);
|
|
10254
|
+
async function executeInContext(req, res, toolName, args) {
|
|
10255
|
+
const scopeCheck = checkToolScope(toolName, req.auth.scopes);
|
|
10256
|
+
if (!scopeCheck.allowed) {
|
|
10257
|
+
res.status(403).json({
|
|
10258
|
+
error: {
|
|
10259
|
+
code: "insufficient_scope",
|
|
10260
|
+
message: scopeCheck.requiredScope ? `Tool '${toolName}' requires scope '${scopeCheck.requiredScope}'. Regenerate your API key with the required scope at https://socialneuron.com/settings/developer` : `Tool '${toolName}' has no scope defined. Contact support.`,
|
|
10261
|
+
required_scope: scopeCheck.requiredScope,
|
|
10262
|
+
status: 403
|
|
10263
|
+
}
|
|
10264
|
+
});
|
|
10265
|
+
return;
|
|
10266
|
+
}
|
|
10267
|
+
const rateLimitCategory = scopeCheck.requiredScope === "mcp:distribute" ? "posting" : scopeCheck.requiredScope === "mcp:write" ? "generation" : "read";
|
|
10268
|
+
const toolRl = checkRateLimit(rateLimitCategory, req.auth.userId);
|
|
10269
|
+
if (!toolRl.allowed) {
|
|
10270
|
+
res.setHeader("Retry-After", String(toolRl.retryAfter));
|
|
10271
|
+
res.status(429).json({
|
|
10272
|
+
error: {
|
|
10273
|
+
code: "rate_limited",
|
|
10274
|
+
message: `Rate limit exceeded for ${rateLimitCategory} operations. Wait ${toolRl.retryAfter}s.`,
|
|
10275
|
+
retry_after: toolRl.retryAfter,
|
|
10276
|
+
status: 429
|
|
10277
|
+
}
|
|
10278
|
+
});
|
|
10279
|
+
return;
|
|
10280
|
+
}
|
|
10281
|
+
const result = await requestContext.run(
|
|
10282
|
+
{
|
|
10283
|
+
userId: req.auth.userId,
|
|
10284
|
+
scopes: req.auth.scopes,
|
|
10285
|
+
creditsUsed: 0,
|
|
10286
|
+
assetsGenerated: 0
|
|
10287
|
+
},
|
|
10288
|
+
() => executeToolDirect(toolName, args)
|
|
10289
|
+
);
|
|
10290
|
+
if (result.isError) {
|
|
10291
|
+
const status = result.error?.includes("not found") ? 404 : result.error?.includes("rate limit") || result.error?.includes("Rate limit") ? 429 : result.error?.includes("Permission denied") ? 403 : 400;
|
|
10292
|
+
res.status(status).json({
|
|
10293
|
+
error: { code: "tool_error", message: result.error, status },
|
|
10294
|
+
_meta: result._meta
|
|
10295
|
+
});
|
|
10296
|
+
return;
|
|
10297
|
+
}
|
|
10298
|
+
res.json({ data: result.data, _meta: result._meta });
|
|
10299
|
+
}
|
|
10300
|
+
router.get("/tools", (req, res) => {
|
|
10301
|
+
const tools = getToolCatalogForApi();
|
|
10302
|
+
const module = req.query.module;
|
|
10303
|
+
const scope = req.query.scope;
|
|
10304
|
+
const search = req.query.q;
|
|
10305
|
+
let filtered = tools;
|
|
10306
|
+
if (module) filtered = filtered.filter((t) => t.module === module);
|
|
10307
|
+
if (scope) filtered = filtered.filter((t) => t.scope === scope);
|
|
10308
|
+
if (search) {
|
|
10309
|
+
const q = search.toLowerCase();
|
|
10310
|
+
filtered = filtered.filter(
|
|
10311
|
+
(t) => t.name.toLowerCase().includes(q) || t.description.toLowerCase().includes(q)
|
|
10312
|
+
);
|
|
10313
|
+
}
|
|
10314
|
+
res.json({
|
|
10315
|
+
data: {
|
|
10316
|
+
tools: filtered,
|
|
10317
|
+
total: filtered.length,
|
|
10318
|
+
modules: [...new Set(TOOL_CATALOG.map((t) => t.module))]
|
|
10319
|
+
},
|
|
10320
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10321
|
+
});
|
|
10322
|
+
});
|
|
10323
|
+
router.post(
|
|
10324
|
+
"/tools/:name",
|
|
10325
|
+
async (req, res) => {
|
|
10326
|
+
const toolName = req.params.name;
|
|
10327
|
+
if (!hasRegisteredTool(toolName)) {
|
|
10328
|
+
res.status(404).json({
|
|
10329
|
+
error: {
|
|
10330
|
+
code: "tool_not_found",
|
|
10331
|
+
message: `Tool '${toolName}' not found. Use GET /v1/tools to list available tools.`,
|
|
10332
|
+
available_tools: TOOL_CATALOG.length,
|
|
10333
|
+
status: 404
|
|
10334
|
+
}
|
|
10335
|
+
});
|
|
10336
|
+
return;
|
|
10337
|
+
}
|
|
10338
|
+
await executeInContext(req, res, toolName, req.body || {});
|
|
10339
|
+
}
|
|
10340
|
+
);
|
|
10341
|
+
router.get("/credits", async (req, res) => {
|
|
10342
|
+
await executeInContext(req, res, "get_credit_balance", {
|
|
10343
|
+
response_format: "json"
|
|
10344
|
+
});
|
|
10345
|
+
});
|
|
10346
|
+
router.get(
|
|
10347
|
+
"/credits/budget",
|
|
10348
|
+
async (req, res) => {
|
|
10349
|
+
await executeInContext(req, res, "get_budget_status", {
|
|
10350
|
+
response_format: "json"
|
|
10351
|
+
});
|
|
10352
|
+
}
|
|
10353
|
+
);
|
|
10354
|
+
router.get("/brand", async (req, res) => {
|
|
10355
|
+
await executeInContext(req, res, "get_brand_profile", {
|
|
10356
|
+
response_format: "json"
|
|
10357
|
+
});
|
|
10358
|
+
});
|
|
10359
|
+
router.get("/analytics", async (req, res) => {
|
|
10360
|
+
const {
|
|
10361
|
+
days,
|
|
10362
|
+
platform: platform2,
|
|
10363
|
+
limit: qLimit
|
|
10364
|
+
} = req.query;
|
|
10365
|
+
await executeInContext(req, res, "fetch_analytics", {
|
|
10366
|
+
response_format: "json",
|
|
10367
|
+
...days && { days: Number(days) },
|
|
10368
|
+
...platform2 && { platform: platform2 },
|
|
10369
|
+
...qLimit && { limit: Number(qLimit) }
|
|
10370
|
+
});
|
|
10371
|
+
});
|
|
10372
|
+
router.get(
|
|
10373
|
+
"/analytics/insights",
|
|
10374
|
+
async (req, res) => {
|
|
10375
|
+
await executeInContext(req, res, "get_performance_insights", {
|
|
10376
|
+
response_format: "json"
|
|
10377
|
+
});
|
|
10378
|
+
}
|
|
10379
|
+
);
|
|
10380
|
+
router.get(
|
|
10381
|
+
"/analytics/best-times",
|
|
10382
|
+
async (req, res) => {
|
|
10383
|
+
await executeInContext(req, res, "get_best_posting_times", {
|
|
10384
|
+
response_format: "json"
|
|
10385
|
+
});
|
|
10386
|
+
}
|
|
10387
|
+
);
|
|
10388
|
+
router.get("/posts", async (req, res) => {
|
|
10389
|
+
await executeInContext(req, res, "list_recent_posts", {
|
|
10390
|
+
response_format: "json",
|
|
10391
|
+
limit: req.query.limit ? Number(req.query.limit) : void 0
|
|
10392
|
+
});
|
|
10393
|
+
});
|
|
10394
|
+
router.get("/accounts", async (req, res) => {
|
|
10395
|
+
await executeInContext(req, res, "list_connected_accounts", {
|
|
10396
|
+
response_format: "json"
|
|
10397
|
+
});
|
|
10398
|
+
});
|
|
10399
|
+
router.post(
|
|
10400
|
+
"/content/generate",
|
|
10401
|
+
async (req, res) => {
|
|
10402
|
+
await executeInContext(req, res, "generate_content", {
|
|
10403
|
+
response_format: "json",
|
|
10404
|
+
...req.body
|
|
10405
|
+
});
|
|
10406
|
+
}
|
|
10407
|
+
);
|
|
10408
|
+
router.post(
|
|
10409
|
+
"/content/adapt",
|
|
10410
|
+
async (req, res) => {
|
|
10411
|
+
await executeInContext(req, res, "adapt_content", {
|
|
10412
|
+
response_format: "json",
|
|
10413
|
+
...req.body
|
|
10414
|
+
});
|
|
10415
|
+
}
|
|
10416
|
+
);
|
|
10417
|
+
router.post(
|
|
10418
|
+
"/content/video",
|
|
10419
|
+
async (req, res) => {
|
|
10420
|
+
await executeInContext(req, res, "generate_video", req.body || {});
|
|
10421
|
+
}
|
|
10422
|
+
);
|
|
10423
|
+
router.post(
|
|
10424
|
+
"/content/image",
|
|
10425
|
+
async (req, res) => {
|
|
10426
|
+
await executeInContext(req, res, "generate_image", req.body || {});
|
|
10427
|
+
}
|
|
10428
|
+
);
|
|
10429
|
+
router.get(
|
|
10430
|
+
"/content/status/:jobId",
|
|
10431
|
+
async (req, res) => {
|
|
10432
|
+
await executeInContext(req, res, "check_status", {
|
|
10433
|
+
job_id: req.params.jobId,
|
|
10434
|
+
response_format: "json"
|
|
10435
|
+
});
|
|
10436
|
+
}
|
|
10437
|
+
);
|
|
10438
|
+
router.post(
|
|
10439
|
+
"/distribution/schedule",
|
|
10440
|
+
async (req, res) => {
|
|
10441
|
+
await executeInContext(req, res, "schedule_post", req.body || {});
|
|
10442
|
+
}
|
|
10443
|
+
);
|
|
10444
|
+
router.get("/loop", async (req, res) => {
|
|
10445
|
+
await executeInContext(req, res, "get_loop_summary", {
|
|
10446
|
+
response_format: "json"
|
|
10447
|
+
});
|
|
10448
|
+
});
|
|
10449
|
+
return router;
|
|
10450
|
+
}
|
|
10451
|
+
|
|
10452
|
+
// src/http.ts
|
|
9465
10453
|
var PORT = parseInt(process.env.PORT ?? "8080", 10);
|
|
9466
10454
|
var SUPABASE_URL2 = process.env.SUPABASE_URL ?? "";
|
|
9467
10455
|
var SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY ?? "";
|
|
@@ -9541,7 +10529,7 @@ var cleanupInterval = setInterval(
|
|
|
9541
10529
|
);
|
|
9542
10530
|
var app = express();
|
|
9543
10531
|
app.disable("x-powered-by");
|
|
9544
|
-
app.use(express.json());
|
|
10532
|
+
app.use(express.json({ limit: "50kb" }));
|
|
9545
10533
|
app.set("trust proxy", 1);
|
|
9546
10534
|
var ipBuckets = /* @__PURE__ */ new Map();
|
|
9547
10535
|
var IP_RATE_MAX = 60;
|
|
@@ -9646,7 +10634,7 @@ async function authenticateRequest(req, res, next) {
|
|
|
9646
10634
|
};
|
|
9647
10635
|
next();
|
|
9648
10636
|
} catch (err) {
|
|
9649
|
-
const message = err
|
|
10637
|
+
const message = sanitizeError(err);
|
|
9650
10638
|
res.status(401).json({
|
|
9651
10639
|
error: "invalid_token",
|
|
9652
10640
|
error_description: message
|
|
@@ -9757,9 +10745,10 @@ app.post(
|
|
|
9757
10745
|
() => transport.handleRequest(req, res, req.body)
|
|
9758
10746
|
);
|
|
9759
10747
|
} catch (err) {
|
|
9760
|
-
const
|
|
9761
|
-
console.error(`[MCP HTTP] POST /mcp error: ${
|
|
10748
|
+
const rawMessage = err instanceof Error ? err.message : "Internal server error";
|
|
10749
|
+
console.error(`[MCP HTTP] POST /mcp error: ${rawMessage}`);
|
|
9762
10750
|
if (!res.headersSent) {
|
|
10751
|
+
const message = sanitizeError(err);
|
|
9763
10752
|
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message } });
|
|
9764
10753
|
}
|
|
9765
10754
|
}
|
|
@@ -9809,12 +10798,25 @@ app.delete(
|
|
|
9809
10798
|
res.status(200).json({ status: "session_closed" });
|
|
9810
10799
|
}
|
|
9811
10800
|
);
|
|
10801
|
+
var restCaptureServer = new McpServer({
|
|
10802
|
+
name: "socialneuron-rest",
|
|
10803
|
+
version: MCP_VERSION
|
|
10804
|
+
});
|
|
10805
|
+
captureToolHandlers(restCaptureServer);
|
|
10806
|
+
registerAllTools(restCaptureServer, { skipScreenshots: true });
|
|
10807
|
+
var restRouter = createRestApiRouter({
|
|
10808
|
+
supabaseUrl: SUPABASE_URL2,
|
|
10809
|
+
supabaseAnonKey: SUPABASE_ANON_KEY
|
|
10810
|
+
});
|
|
10811
|
+
app.use("/v1", restRouter);
|
|
10812
|
+
console.log("[MCP HTTP] REST API mounted at /v1");
|
|
9812
10813
|
var httpServer = app.listen(PORT, "0.0.0.0", () => {
|
|
9813
10814
|
console.log(
|
|
9814
10815
|
`[MCP HTTP] Social Neuron MCP Server listening on 0.0.0.0:${PORT}`
|
|
9815
10816
|
);
|
|
9816
10817
|
console.log(`[MCP HTTP] Health: http://localhost:${PORT}/health`);
|
|
9817
10818
|
console.log(`[MCP HTTP] MCP endpoint: ${MCP_SERVER_URL}`);
|
|
10819
|
+
console.log(`[MCP HTTP] REST API: http://localhost:${PORT}/v1`);
|
|
9818
10820
|
console.log(`[MCP HTTP] Environment: ${NODE_ENV}`);
|
|
9819
10821
|
});
|
|
9820
10822
|
async function shutdown(signal) {
|