@socialneuron/mcp-server 1.5.2 → 1.6.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/README.md +62 -13
- package/dist/http.js +994 -1
- package/dist/index.js +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
# @socialneuron/mcp-server
|
|
2
2
|
|
|
3
|
-
> 52
|
|
3
|
+
> 52 tools for AI-powered social media management. MCP, REST API, CLI — create content, schedule posts, track analytics, and optimize performance.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@socialneuron/mcp-server)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
+
## Integration Methods
|
|
9
|
+
|
|
10
|
+
| Method | Best For | Docs |
|
|
11
|
+
|--------|----------|------|
|
|
12
|
+
| **MCP** | AI agents (Claude, Cursor, VS Code) | [Setup](#quick-start) |
|
|
13
|
+
| **REST API** | Any HTTP client, webhooks, Zapier | [Guide](docs/rest-api.md) |
|
|
14
|
+
| **CLI** | Terminal, CI/CD pipelines | [Guide](docs/cli-guide.md) |
|
|
15
|
+
| **SDK** | TypeScript/Node.js apps | Coming Q2 2026 |
|
|
16
|
+
|
|
17
|
+
All methods share the same 52 tools, auth, scopes, and credit system. [Compare methods](docs/integration-methods.md).
|
|
18
|
+
|
|
8
19
|
## Quick Start
|
|
9
20
|
|
|
10
|
-
###
|
|
21
|
+
### MCP (AI Agents)
|
|
22
|
+
|
|
23
|
+
#### 1. Authenticate
|
|
11
24
|
|
|
12
25
|
```bash
|
|
13
26
|
npx -y @socialneuron/mcp-server login --device
|
|
@@ -15,7 +28,7 @@ npx -y @socialneuron/mcp-server login --device
|
|
|
15
28
|
|
|
16
29
|
This opens your browser to authorize access. Requires a paid Social Neuron plan (Starter or above). See [pricing](https://socialneuron.com/pricing).
|
|
17
30
|
|
|
18
|
-
|
|
31
|
+
#### 2. Add to Claude Code
|
|
19
32
|
|
|
20
33
|
```bash
|
|
21
34
|
claude mcp add socialneuron -- npx -y @socialneuron/mcp-server
|
|
@@ -76,10 +89,42 @@ Add to `.cursor/mcp.json` in your workspace:
|
|
|
76
89
|
```
|
|
77
90
|
</details>
|
|
78
91
|
|
|
79
|
-
|
|
92
|
+
#### 3. Start using
|
|
80
93
|
|
|
81
94
|
Ask Claude: "What content should I post this week?" or "Schedule my latest video to YouTube and TikTok"
|
|
82
95
|
|
|
96
|
+
### REST API (Any Language)
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Check credits
|
|
100
|
+
curl -H "Authorization: Bearer snk_live_..." \
|
|
101
|
+
https://mcp.socialneuron.com/v1/credits
|
|
102
|
+
|
|
103
|
+
# Generate content
|
|
104
|
+
curl -X POST -H "Authorization: Bearer snk_live_..." \
|
|
105
|
+
-H "Content-Type: application/json" \
|
|
106
|
+
-d '{"topic": "AI trends", "platforms": ["linkedin"]}' \
|
|
107
|
+
https://mcp.socialneuron.com/v1/content/generate
|
|
108
|
+
|
|
109
|
+
# Execute any tool via proxy
|
|
110
|
+
curl -X POST -H "Authorization: Bearer snk_live_..." \
|
|
111
|
+
-H "Content-Type: application/json" \
|
|
112
|
+
-d '{"response_format": "json"}' \
|
|
113
|
+
https://mcp.socialneuron.com/v1/tools/get_brand_profile
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
See [REST API docs](docs/rest-api.md) | [OpenAPI spec](https://mcp.socialneuron.com/v1/openapi.json) | [Examples](examples/rest/)
|
|
117
|
+
|
|
118
|
+
### CLI (Terminal & CI/CD)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npx @socialneuron/mcp-server sn system credits --json
|
|
122
|
+
npx @socialneuron/mcp-server sn analytics loop --json
|
|
123
|
+
npx @socialneuron/mcp-server sn discovery tools --module content
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
See [CLI guide](docs/cli-guide.md) | [Examples](examples/cli/)
|
|
127
|
+
|
|
83
128
|
## What You Can Do
|
|
84
129
|
|
|
85
130
|
Ask Claude things like:
|
|
@@ -94,7 +139,7 @@ Ask Claude things like:
|
|
|
94
139
|
|
|
95
140
|
## Tool Categories (52 tools)
|
|
96
141
|
|
|
97
|
-
|
|
142
|
+
All tools are accessible via MCP, REST API (`POST /v1/tools/{name}`), and CLI.
|
|
98
143
|
|
|
99
144
|
### Content Lifecycle
|
|
100
145
|
|
|
@@ -273,23 +318,27 @@ No personal content, API keys, or request payloads are ever collected. Your user
|
|
|
273
318
|
|
|
274
319
|
## Examples
|
|
275
320
|
|
|
276
|
-
See the [examples
|
|
321
|
+
See the [`examples/`](examples/) directory:
|
|
277
322
|
|
|
278
|
-
-
|
|
279
|
-
-
|
|
323
|
+
- [REST API examples](examples/rest/) — curl scripts for every endpoint
|
|
324
|
+
- [CLI examples](examples/cli/) — automation workflows
|
|
325
|
+
- [MCP prompts](examples/mcp/claude-prompts.md) — natural language examples
|
|
326
|
+
- [External examples repo](https://github.com/socialneuron/examples) — prompt-driven workflow templates
|
|
280
327
|
- Performance review and optimization loops
|
|
281
328
|
- Brand-aligned content generation
|
|
282
329
|
- Comment engagement automation
|
|
283
330
|
|
|
284
331
|
## Links
|
|
285
332
|
|
|
286
|
-
- [
|
|
287
|
-
- [
|
|
333
|
+
- [For Developers](https://socialneuron.com/for-developers) — Integration methods, tools, pricing
|
|
334
|
+
- [REST API Docs](docs/rest-api.md) — Endpoint reference
|
|
335
|
+
- [CLI Guide](docs/cli-guide.md) — Terminal commands
|
|
336
|
+
- [Integration Methods](docs/integration-methods.md) — Compare MCP vs REST vs CLI
|
|
337
|
+
- [OpenAPI Spec](https://mcp.socialneuron.com/v1/openapi.json) — Machine-readable API spec
|
|
338
|
+
- [Developer Settings](https://socialneuron.com/settings/developer) — Generate API keys
|
|
288
339
|
- [Documentation](https://socialneuron.com/docs)
|
|
289
|
-
- [Examples](https://github.com/socialneuron/examples)
|
|
290
|
-
- [Agent Protocol](https://socialneuron.com/system-prompt.txt)
|
|
291
|
-
- [Developer Settings](https://socialneuron.com/settings/developer)
|
|
292
340
|
- [Pricing](https://socialneuron.com/pricing)
|
|
341
|
+
- [Agent Protocol](https://socialneuron.com/system-prompt.txt)
|
|
293
342
|
|
|
294
343
|
## License
|
|
295
344
|
|
package/dist/http.js
CHANGED
|
@@ -1368,7 +1368,7 @@ function sanitizeDbError(error) {
|
|
|
1368
1368
|
init_request_context();
|
|
1369
1369
|
|
|
1370
1370
|
// src/lib/version.ts
|
|
1371
|
-
var MCP_VERSION = "1.
|
|
1371
|
+
var MCP_VERSION = "1.6.0";
|
|
1372
1372
|
|
|
1373
1373
|
// src/tools/content.ts
|
|
1374
1374
|
var MAX_CREDITS_PER_RUN = Math.max(
|
|
@@ -9462,6 +9462,986 @@ async function verifyApiKey(apiKey, supabaseUrl, supabaseAnonKey) {
|
|
|
9462
9462
|
|
|
9463
9463
|
// src/http.ts
|
|
9464
9464
|
init_posthog();
|
|
9465
|
+
|
|
9466
|
+
// src/api/tool-executor.ts
|
|
9467
|
+
var toolHandlers = /* @__PURE__ */ new Map();
|
|
9468
|
+
function captureToolHandlers(server) {
|
|
9469
|
+
const original = server.tool.bind(server);
|
|
9470
|
+
server.tool = function capturedTool(...args) {
|
|
9471
|
+
const name = args[0];
|
|
9472
|
+
const handlerIndex = args.findIndex(
|
|
9473
|
+
(a, i) => i > 0 && typeof a === "function"
|
|
9474
|
+
);
|
|
9475
|
+
if (handlerIndex !== -1) {
|
|
9476
|
+
toolHandlers.set(name, args[handlerIndex]);
|
|
9477
|
+
}
|
|
9478
|
+
return original(...args);
|
|
9479
|
+
};
|
|
9480
|
+
}
|
|
9481
|
+
async function executeToolDirect(name, args) {
|
|
9482
|
+
const meta = {
|
|
9483
|
+
tool: name,
|
|
9484
|
+
version: MCP_VERSION,
|
|
9485
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9486
|
+
};
|
|
9487
|
+
const handler = toolHandlers.get(name);
|
|
9488
|
+
if (!handler) {
|
|
9489
|
+
return {
|
|
9490
|
+
data: null,
|
|
9491
|
+
error: `Tool '${name}' not found. Use GET /v1/tools to list available tools.`,
|
|
9492
|
+
isError: true,
|
|
9493
|
+
_meta: meta
|
|
9494
|
+
};
|
|
9495
|
+
}
|
|
9496
|
+
try {
|
|
9497
|
+
const result = await handler(args);
|
|
9498
|
+
const textContent = result.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
9499
|
+
if (result.isError) {
|
|
9500
|
+
return { data: null, error: textContent, isError: true, _meta: meta };
|
|
9501
|
+
}
|
|
9502
|
+
let data;
|
|
9503
|
+
try {
|
|
9504
|
+
data = JSON.parse(textContent);
|
|
9505
|
+
} catch {
|
|
9506
|
+
data = { text: textContent };
|
|
9507
|
+
}
|
|
9508
|
+
return { data, error: null, isError: false, _meta: meta };
|
|
9509
|
+
} catch (err) {
|
|
9510
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9511
|
+
return { data: null, error: message, isError: true, _meta: meta };
|
|
9512
|
+
}
|
|
9513
|
+
}
|
|
9514
|
+
function hasRegisteredTool(name) {
|
|
9515
|
+
return toolHandlers.has(name);
|
|
9516
|
+
}
|
|
9517
|
+
function getRegisteredToolCount() {
|
|
9518
|
+
return toolHandlers.size;
|
|
9519
|
+
}
|
|
9520
|
+
function checkToolScope(toolName, userScopes) {
|
|
9521
|
+
const requiredScope = TOOL_SCOPES[toolName];
|
|
9522
|
+
if (!requiredScope) {
|
|
9523
|
+
return { allowed: false, requiredScope: null };
|
|
9524
|
+
}
|
|
9525
|
+
return { allowed: hasScope(userScopes, requiredScope), requiredScope };
|
|
9526
|
+
}
|
|
9527
|
+
function getToolCatalogForApi() {
|
|
9528
|
+
return TOOL_CATALOG.filter((tool) => toolHandlers.has(tool.name)).map(
|
|
9529
|
+
(tool) => ({
|
|
9530
|
+
...tool,
|
|
9531
|
+
endpoint: `/v1/tools/${tool.name}`,
|
|
9532
|
+
method: "POST"
|
|
9533
|
+
})
|
|
9534
|
+
);
|
|
9535
|
+
}
|
|
9536
|
+
|
|
9537
|
+
// src/api/router.ts
|
|
9538
|
+
import { Router } from "express";
|
|
9539
|
+
init_request_context();
|
|
9540
|
+
|
|
9541
|
+
// src/api/openapi.ts
|
|
9542
|
+
function generateOpenApiSpec() {
|
|
9543
|
+
const modules = [...new Set(TOOL_CATALOG.map((t) => t.module))];
|
|
9544
|
+
const toolPaths = {};
|
|
9545
|
+
for (const tool of TOOL_CATALOG) {
|
|
9546
|
+
toolPaths[`/v1/tools/${tool.name}`] = {
|
|
9547
|
+
post: {
|
|
9548
|
+
operationId: tool.name,
|
|
9549
|
+
summary: tool.description,
|
|
9550
|
+
tags: [tool.module],
|
|
9551
|
+
"x-required-scope": tool.scope,
|
|
9552
|
+
security: [{ bearerAuth: [] }],
|
|
9553
|
+
requestBody: {
|
|
9554
|
+
required: false,
|
|
9555
|
+
content: {
|
|
9556
|
+
"application/json": {
|
|
9557
|
+
schema: {
|
|
9558
|
+
type: "object",
|
|
9559
|
+
description: `Input parameters for ${tool.name}. Pass tool-specific arguments as JSON.`
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
}
|
|
9563
|
+
},
|
|
9564
|
+
responses: {
|
|
9565
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9566
|
+
"400": { $ref: "#/components/responses/ToolError" },
|
|
9567
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9568
|
+
"403": { $ref: "#/components/responses/InsufficientScope" },
|
|
9569
|
+
"404": { $ref: "#/components/responses/NotFound" },
|
|
9570
|
+
"429": { $ref: "#/components/responses/RateLimited" }
|
|
9571
|
+
}
|
|
9572
|
+
}
|
|
9573
|
+
};
|
|
9574
|
+
}
|
|
9575
|
+
return {
|
|
9576
|
+
openapi: "3.1.0",
|
|
9577
|
+
info: {
|
|
9578
|
+
title: "Social Neuron API",
|
|
9579
|
+
version: MCP_VERSION,
|
|
9580
|
+
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.",
|
|
9581
|
+
contact: {
|
|
9582
|
+
name: "Social Neuron",
|
|
9583
|
+
email: "socialneuronteam@gmail.com",
|
|
9584
|
+
url: "https://socialneuron.com/for-developers"
|
|
9585
|
+
},
|
|
9586
|
+
license: { name: "MIT", url: "https://opensource.org/licenses/MIT" },
|
|
9587
|
+
termsOfService: "https://socialneuron.com/terms"
|
|
9588
|
+
},
|
|
9589
|
+
servers: [
|
|
9590
|
+
{
|
|
9591
|
+
url: "https://mcp.socialneuron.com",
|
|
9592
|
+
description: "Production"
|
|
9593
|
+
}
|
|
9594
|
+
],
|
|
9595
|
+
tags: [
|
|
9596
|
+
{
|
|
9597
|
+
name: "tools",
|
|
9598
|
+
description: "Tool discovery and universal tool proxy"
|
|
9599
|
+
},
|
|
9600
|
+
{
|
|
9601
|
+
name: "credits",
|
|
9602
|
+
description: "Credit balance and budget tracking"
|
|
9603
|
+
},
|
|
9604
|
+
{
|
|
9605
|
+
name: "brand",
|
|
9606
|
+
description: "Brand profile management"
|
|
9607
|
+
},
|
|
9608
|
+
{
|
|
9609
|
+
name: "analytics",
|
|
9610
|
+
description: "Performance analytics and insights"
|
|
9611
|
+
},
|
|
9612
|
+
{
|
|
9613
|
+
name: "content",
|
|
9614
|
+
description: "Content generation (text, image, video)"
|
|
9615
|
+
},
|
|
9616
|
+
{
|
|
9617
|
+
name: "distribution",
|
|
9618
|
+
description: "Post scheduling and publishing"
|
|
9619
|
+
},
|
|
9620
|
+
{
|
|
9621
|
+
name: "posts",
|
|
9622
|
+
description: "Post listing and status"
|
|
9623
|
+
},
|
|
9624
|
+
...modules.map((m) => ({
|
|
9625
|
+
name: m,
|
|
9626
|
+
description: `${m} tools (via tool proxy)`
|
|
9627
|
+
}))
|
|
9628
|
+
],
|
|
9629
|
+
paths: {
|
|
9630
|
+
"/v1/": {
|
|
9631
|
+
get: {
|
|
9632
|
+
operationId: "getApiInfo",
|
|
9633
|
+
summary: "API info and discovery",
|
|
9634
|
+
tags: ["tools"],
|
|
9635
|
+
security: [{ bearerAuth: [] }],
|
|
9636
|
+
responses: {
|
|
9637
|
+
"200": {
|
|
9638
|
+
description: "API metadata",
|
|
9639
|
+
content: {
|
|
9640
|
+
"application/json": {
|
|
9641
|
+
schema: { type: "object" }
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
}
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
},
|
|
9648
|
+
"/v1/tools": {
|
|
9649
|
+
get: {
|
|
9650
|
+
operationId: "listTools",
|
|
9651
|
+
summary: "List available tools with optional filtering",
|
|
9652
|
+
tags: ["tools"],
|
|
9653
|
+
security: [{ bearerAuth: [] }],
|
|
9654
|
+
parameters: [
|
|
9655
|
+
{
|
|
9656
|
+
name: "module",
|
|
9657
|
+
in: "query",
|
|
9658
|
+
schema: { type: "string" },
|
|
9659
|
+
description: "Filter by module name"
|
|
9660
|
+
},
|
|
9661
|
+
{
|
|
9662
|
+
name: "scope",
|
|
9663
|
+
in: "query",
|
|
9664
|
+
schema: { type: "string" },
|
|
9665
|
+
description: "Filter by required scope"
|
|
9666
|
+
},
|
|
9667
|
+
{
|
|
9668
|
+
name: "q",
|
|
9669
|
+
in: "query",
|
|
9670
|
+
schema: { type: "string" },
|
|
9671
|
+
description: "Search tools by keyword"
|
|
9672
|
+
}
|
|
9673
|
+
],
|
|
9674
|
+
responses: {
|
|
9675
|
+
"200": {
|
|
9676
|
+
description: "Tool catalog",
|
|
9677
|
+
content: {
|
|
9678
|
+
"application/json": {
|
|
9679
|
+
schema: {
|
|
9680
|
+
type: "object",
|
|
9681
|
+
properties: {
|
|
9682
|
+
data: {
|
|
9683
|
+
type: "object",
|
|
9684
|
+
properties: {
|
|
9685
|
+
tools: {
|
|
9686
|
+
type: "array",
|
|
9687
|
+
items: { $ref: "#/components/schemas/ToolEntry" }
|
|
9688
|
+
},
|
|
9689
|
+
total: { type: "integer" },
|
|
9690
|
+
modules: {
|
|
9691
|
+
type: "array",
|
|
9692
|
+
items: { type: "string" }
|
|
9693
|
+
}
|
|
9694
|
+
}
|
|
9695
|
+
},
|
|
9696
|
+
_meta: { $ref: "#/components/schemas/Meta" }
|
|
9697
|
+
}
|
|
9698
|
+
}
|
|
9699
|
+
}
|
|
9700
|
+
}
|
|
9701
|
+
}
|
|
9702
|
+
}
|
|
9703
|
+
}
|
|
9704
|
+
},
|
|
9705
|
+
"/v1/credits": {
|
|
9706
|
+
get: {
|
|
9707
|
+
operationId: "getCreditBalance",
|
|
9708
|
+
summary: "Get credit balance, plan, and monthly usage",
|
|
9709
|
+
tags: ["credits"],
|
|
9710
|
+
"x-tool-name": "get_credit_balance",
|
|
9711
|
+
"x-required-scope": "mcp:read",
|
|
9712
|
+
security: [{ bearerAuth: [] }],
|
|
9713
|
+
responses: {
|
|
9714
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9715
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9716
|
+
}
|
|
9717
|
+
}
|
|
9718
|
+
},
|
|
9719
|
+
"/v1/credits/budget": {
|
|
9720
|
+
get: {
|
|
9721
|
+
operationId: "getBudgetStatus",
|
|
9722
|
+
summary: "Get per-session budget and spending status",
|
|
9723
|
+
tags: ["credits"],
|
|
9724
|
+
"x-tool-name": "get_budget_status",
|
|
9725
|
+
"x-required-scope": "mcp:read",
|
|
9726
|
+
security: [{ bearerAuth: [] }],
|
|
9727
|
+
responses: {
|
|
9728
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9729
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9730
|
+
}
|
|
9731
|
+
}
|
|
9732
|
+
},
|
|
9733
|
+
"/v1/brand": {
|
|
9734
|
+
get: {
|
|
9735
|
+
operationId: "getBrandProfile",
|
|
9736
|
+
summary: "Get current brand profile",
|
|
9737
|
+
tags: ["brand"],
|
|
9738
|
+
"x-tool-name": "get_brand_profile",
|
|
9739
|
+
"x-required-scope": "mcp:read",
|
|
9740
|
+
security: [{ bearerAuth: [] }],
|
|
9741
|
+
responses: {
|
|
9742
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9743
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9744
|
+
}
|
|
9745
|
+
}
|
|
9746
|
+
},
|
|
9747
|
+
"/v1/analytics": {
|
|
9748
|
+
get: {
|
|
9749
|
+
operationId: "fetchAnalytics",
|
|
9750
|
+
summary: "Fetch post performance analytics",
|
|
9751
|
+
tags: ["analytics"],
|
|
9752
|
+
"x-tool-name": "fetch_analytics",
|
|
9753
|
+
"x-required-scope": "mcp:read",
|
|
9754
|
+
security: [{ bearerAuth: [] }],
|
|
9755
|
+
responses: {
|
|
9756
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9757
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9758
|
+
}
|
|
9759
|
+
}
|
|
9760
|
+
},
|
|
9761
|
+
"/v1/analytics/insights": {
|
|
9762
|
+
get: {
|
|
9763
|
+
operationId: "getPerformanceInsights",
|
|
9764
|
+
summary: "Get AI-generated performance insights",
|
|
9765
|
+
tags: ["analytics"],
|
|
9766
|
+
"x-tool-name": "get_performance_insights",
|
|
9767
|
+
"x-required-scope": "mcp:read",
|
|
9768
|
+
security: [{ bearerAuth: [] }],
|
|
9769
|
+
responses: {
|
|
9770
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9771
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9772
|
+
}
|
|
9773
|
+
}
|
|
9774
|
+
},
|
|
9775
|
+
"/v1/analytics/best-times": {
|
|
9776
|
+
get: {
|
|
9777
|
+
operationId: "getBestPostingTimes",
|
|
9778
|
+
summary: "Get recommended posting times based on audience data",
|
|
9779
|
+
tags: ["analytics"],
|
|
9780
|
+
"x-tool-name": "get_best_posting_times",
|
|
9781
|
+
"x-required-scope": "mcp:read",
|
|
9782
|
+
security: [{ bearerAuth: [] }],
|
|
9783
|
+
responses: {
|
|
9784
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9785
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9786
|
+
}
|
|
9787
|
+
}
|
|
9788
|
+
},
|
|
9789
|
+
"/v1/posts": {
|
|
9790
|
+
get: {
|
|
9791
|
+
operationId: "listRecentPosts",
|
|
9792
|
+
summary: "List recently published or scheduled posts",
|
|
9793
|
+
tags: ["posts"],
|
|
9794
|
+
"x-tool-name": "list_recent_posts",
|
|
9795
|
+
"x-required-scope": "mcp:read",
|
|
9796
|
+
security: [{ bearerAuth: [] }],
|
|
9797
|
+
parameters: [
|
|
9798
|
+
{
|
|
9799
|
+
name: "limit",
|
|
9800
|
+
in: "query",
|
|
9801
|
+
schema: { type: "integer", default: 20 },
|
|
9802
|
+
description: "Max number of posts to return"
|
|
9803
|
+
}
|
|
9804
|
+
],
|
|
9805
|
+
responses: {
|
|
9806
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9807
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9808
|
+
}
|
|
9809
|
+
}
|
|
9810
|
+
},
|
|
9811
|
+
"/v1/accounts": {
|
|
9812
|
+
get: {
|
|
9813
|
+
operationId: "listConnectedAccounts",
|
|
9814
|
+
summary: "List connected social media accounts",
|
|
9815
|
+
tags: ["posts"],
|
|
9816
|
+
"x-tool-name": "list_connected_accounts",
|
|
9817
|
+
"x-required-scope": "mcp:read",
|
|
9818
|
+
security: [{ bearerAuth: [] }],
|
|
9819
|
+
responses: {
|
|
9820
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9821
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
9822
|
+
}
|
|
9823
|
+
}
|
|
9824
|
+
},
|
|
9825
|
+
"/v1/content/generate": {
|
|
9826
|
+
post: {
|
|
9827
|
+
operationId: "generateContent",
|
|
9828
|
+
summary: "Generate social media content with AI",
|
|
9829
|
+
tags: ["content"],
|
|
9830
|
+
"x-tool-name": "generate_content",
|
|
9831
|
+
"x-required-scope": "mcp:write",
|
|
9832
|
+
security: [{ bearerAuth: [] }],
|
|
9833
|
+
requestBody: {
|
|
9834
|
+
required: true,
|
|
9835
|
+
content: {
|
|
9836
|
+
"application/json": {
|
|
9837
|
+
schema: {
|
|
9838
|
+
type: "object",
|
|
9839
|
+
properties: {
|
|
9840
|
+
topic: {
|
|
9841
|
+
type: "string",
|
|
9842
|
+
description: "Content topic or prompt"
|
|
9843
|
+
},
|
|
9844
|
+
platforms: {
|
|
9845
|
+
type: "array",
|
|
9846
|
+
items: { type: "string" },
|
|
9847
|
+
description: "Target platforms"
|
|
9848
|
+
},
|
|
9849
|
+
tone: { type: "string", description: "Content tone" },
|
|
9850
|
+
content_type: {
|
|
9851
|
+
type: "string",
|
|
9852
|
+
description: "Type of content to generate"
|
|
9853
|
+
}
|
|
9854
|
+
}
|
|
9855
|
+
}
|
|
9856
|
+
}
|
|
9857
|
+
}
|
|
9858
|
+
},
|
|
9859
|
+
responses: {
|
|
9860
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9861
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9862
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
9863
|
+
}
|
|
9864
|
+
}
|
|
9865
|
+
},
|
|
9866
|
+
"/v1/content/adapt": {
|
|
9867
|
+
post: {
|
|
9868
|
+
operationId: "adaptContent",
|
|
9869
|
+
summary: "Adapt existing content for different platforms",
|
|
9870
|
+
tags: ["content"],
|
|
9871
|
+
"x-tool-name": "adapt_content",
|
|
9872
|
+
"x-required-scope": "mcp:write",
|
|
9873
|
+
security: [{ bearerAuth: [] }],
|
|
9874
|
+
requestBody: {
|
|
9875
|
+
required: true,
|
|
9876
|
+
content: {
|
|
9877
|
+
"application/json": {
|
|
9878
|
+
schema: {
|
|
9879
|
+
type: "object",
|
|
9880
|
+
properties: {
|
|
9881
|
+
content: {
|
|
9882
|
+
type: "string",
|
|
9883
|
+
description: "Content to adapt"
|
|
9884
|
+
},
|
|
9885
|
+
target_platforms: {
|
|
9886
|
+
type: "array",
|
|
9887
|
+
items: { type: "string" },
|
|
9888
|
+
description: "Target platforms for adaptation"
|
|
9889
|
+
}
|
|
9890
|
+
}
|
|
9891
|
+
}
|
|
9892
|
+
}
|
|
9893
|
+
}
|
|
9894
|
+
},
|
|
9895
|
+
responses: {
|
|
9896
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9897
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9898
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
9899
|
+
}
|
|
9900
|
+
}
|
|
9901
|
+
},
|
|
9902
|
+
"/v1/content/video": {
|
|
9903
|
+
post: {
|
|
9904
|
+
operationId: "generateVideo",
|
|
9905
|
+
summary: "Generate video content using AI models",
|
|
9906
|
+
tags: ["content"],
|
|
9907
|
+
"x-tool-name": "generate_video",
|
|
9908
|
+
"x-required-scope": "mcp:write",
|
|
9909
|
+
security: [{ bearerAuth: [] }],
|
|
9910
|
+
requestBody: {
|
|
9911
|
+
required: true,
|
|
9912
|
+
content: {
|
|
9913
|
+
"application/json": {
|
|
9914
|
+
schema: {
|
|
9915
|
+
type: "object",
|
|
9916
|
+
properties: {
|
|
9917
|
+
prompt: { type: "string" },
|
|
9918
|
+
aspect_ratio: { type: "string" },
|
|
9919
|
+
duration: { type: "integer" }
|
|
9920
|
+
}
|
|
9921
|
+
}
|
|
9922
|
+
}
|
|
9923
|
+
}
|
|
9924
|
+
},
|
|
9925
|
+
responses: {
|
|
9926
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9927
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9928
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
9929
|
+
}
|
|
9930
|
+
}
|
|
9931
|
+
},
|
|
9932
|
+
"/v1/content/image": {
|
|
9933
|
+
post: {
|
|
9934
|
+
operationId: "generateImage",
|
|
9935
|
+
summary: "Generate images using AI models",
|
|
9936
|
+
tags: ["content"],
|
|
9937
|
+
"x-tool-name": "generate_image",
|
|
9938
|
+
"x-required-scope": "mcp:write",
|
|
9939
|
+
security: [{ bearerAuth: [] }],
|
|
9940
|
+
requestBody: {
|
|
9941
|
+
required: true,
|
|
9942
|
+
content: {
|
|
9943
|
+
"application/json": {
|
|
9944
|
+
schema: {
|
|
9945
|
+
type: "object",
|
|
9946
|
+
properties: {
|
|
9947
|
+
prompt: { type: "string" },
|
|
9948
|
+
aspect_ratio: { type: "string" },
|
|
9949
|
+
style: { type: "string" }
|
|
9950
|
+
}
|
|
9951
|
+
}
|
|
9952
|
+
}
|
|
9953
|
+
}
|
|
9954
|
+
},
|
|
9955
|
+
responses: {
|
|
9956
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9957
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9958
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
9959
|
+
}
|
|
9960
|
+
}
|
|
9961
|
+
},
|
|
9962
|
+
"/v1/content/status/{jobId}": {
|
|
9963
|
+
get: {
|
|
9964
|
+
operationId: "checkJobStatus",
|
|
9965
|
+
summary: "Check status of async content generation job",
|
|
9966
|
+
tags: ["content"],
|
|
9967
|
+
"x-tool-name": "check_status",
|
|
9968
|
+
"x-required-scope": "mcp:read",
|
|
9969
|
+
security: [{ bearerAuth: [] }],
|
|
9970
|
+
parameters: [
|
|
9971
|
+
{
|
|
9972
|
+
name: "jobId",
|
|
9973
|
+
in: "path",
|
|
9974
|
+
required: true,
|
|
9975
|
+
schema: { type: "string" },
|
|
9976
|
+
description: "Job ID to check"
|
|
9977
|
+
}
|
|
9978
|
+
],
|
|
9979
|
+
responses: {
|
|
9980
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
9981
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
9982
|
+
"404": { $ref: "#/components/responses/NotFound" }
|
|
9983
|
+
}
|
|
9984
|
+
}
|
|
9985
|
+
},
|
|
9986
|
+
"/v1/distribution/schedule": {
|
|
9987
|
+
post: {
|
|
9988
|
+
operationId: "schedulePost",
|
|
9989
|
+
summary: "Schedule or publish content to social platforms",
|
|
9990
|
+
tags: ["distribution"],
|
|
9991
|
+
"x-tool-name": "schedule_post",
|
|
9992
|
+
"x-required-scope": "mcp:distribute",
|
|
9993
|
+
security: [{ bearerAuth: [] }],
|
|
9994
|
+
requestBody: {
|
|
9995
|
+
required: true,
|
|
9996
|
+
content: {
|
|
9997
|
+
"application/json": {
|
|
9998
|
+
schema: {
|
|
9999
|
+
type: "object",
|
|
10000
|
+
properties: {
|
|
10001
|
+
media_url: {
|
|
10002
|
+
type: "string",
|
|
10003
|
+
description: "URL of media to post"
|
|
10004
|
+
},
|
|
10005
|
+
caption: {
|
|
10006
|
+
type: "string",
|
|
10007
|
+
description: "Post caption text"
|
|
10008
|
+
},
|
|
10009
|
+
platforms: {
|
|
10010
|
+
type: "array",
|
|
10011
|
+
items: { type: "string" },
|
|
10012
|
+
description: "Target platforms"
|
|
10013
|
+
},
|
|
10014
|
+
schedule_at: {
|
|
10015
|
+
type: "string",
|
|
10016
|
+
format: "date-time",
|
|
10017
|
+
description: "ISO 8601 schedule time (omit for immediate)"
|
|
10018
|
+
}
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
}
|
|
10022
|
+
}
|
|
10023
|
+
},
|
|
10024
|
+
responses: {
|
|
10025
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
10026
|
+
"401": { $ref: "#/components/responses/Unauthorized" },
|
|
10027
|
+
"403": { $ref: "#/components/responses/InsufficientScope" }
|
|
10028
|
+
}
|
|
10029
|
+
}
|
|
10030
|
+
},
|
|
10031
|
+
"/v1/loop": {
|
|
10032
|
+
get: {
|
|
10033
|
+
operationId: "getLoopSummary",
|
|
10034
|
+
summary: "Get growth loop summary and optimization recommendations",
|
|
10035
|
+
tags: ["analytics"],
|
|
10036
|
+
"x-tool-name": "get_loop_summary",
|
|
10037
|
+
"x-required-scope": "mcp:read",
|
|
10038
|
+
security: [{ bearerAuth: [] }],
|
|
10039
|
+
responses: {
|
|
10040
|
+
"200": { $ref: "#/components/responses/ToolSuccess" },
|
|
10041
|
+
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
10042
|
+
}
|
|
10043
|
+
}
|
|
10044
|
+
},
|
|
10045
|
+
// Spread all tool proxy paths
|
|
10046
|
+
...toolPaths
|
|
10047
|
+
},
|
|
10048
|
+
components: {
|
|
10049
|
+
securitySchemes: {
|
|
10050
|
+
bearerAuth: {
|
|
10051
|
+
type: "http",
|
|
10052
|
+
scheme: "bearer",
|
|
10053
|
+
description: "API key from Settings > Developer. Format: snk_live_..."
|
|
10054
|
+
}
|
|
10055
|
+
},
|
|
10056
|
+
schemas: {
|
|
10057
|
+
Meta: {
|
|
10058
|
+
type: "object",
|
|
10059
|
+
properties: {
|
|
10060
|
+
tool: { type: "string" },
|
|
10061
|
+
version: { type: "string" },
|
|
10062
|
+
timestamp: { type: "string", format: "date-time" }
|
|
10063
|
+
}
|
|
10064
|
+
},
|
|
10065
|
+
ToolEntry: {
|
|
10066
|
+
type: "object",
|
|
10067
|
+
properties: {
|
|
10068
|
+
name: { type: "string" },
|
|
10069
|
+
description: { type: "string" },
|
|
10070
|
+
module: { type: "string" },
|
|
10071
|
+
scope: { type: "string" },
|
|
10072
|
+
endpoint: { type: "string" },
|
|
10073
|
+
method: { type: "string" }
|
|
10074
|
+
}
|
|
10075
|
+
},
|
|
10076
|
+
ApiError: {
|
|
10077
|
+
type: "object",
|
|
10078
|
+
properties: {
|
|
10079
|
+
error: {
|
|
10080
|
+
type: "object",
|
|
10081
|
+
properties: {
|
|
10082
|
+
code: { type: "string" },
|
|
10083
|
+
message: { type: "string" },
|
|
10084
|
+
status: { type: "integer" }
|
|
10085
|
+
},
|
|
10086
|
+
required: ["code", "message", "status"]
|
|
10087
|
+
}
|
|
10088
|
+
}
|
|
10089
|
+
}
|
|
10090
|
+
},
|
|
10091
|
+
responses: {
|
|
10092
|
+
ToolSuccess: {
|
|
10093
|
+
description: "Successful tool execution",
|
|
10094
|
+
content: {
|
|
10095
|
+
"application/json": {
|
|
10096
|
+
schema: {
|
|
10097
|
+
type: "object",
|
|
10098
|
+
properties: {
|
|
10099
|
+
data: { type: "object" },
|
|
10100
|
+
_meta: { $ref: "#/components/schemas/Meta" }
|
|
10101
|
+
}
|
|
10102
|
+
}
|
|
10103
|
+
}
|
|
10104
|
+
}
|
|
10105
|
+
},
|
|
10106
|
+
ToolError: {
|
|
10107
|
+
description: "Tool execution error",
|
|
10108
|
+
content: {
|
|
10109
|
+
"application/json": {
|
|
10110
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10111
|
+
}
|
|
10112
|
+
}
|
|
10113
|
+
},
|
|
10114
|
+
Unauthorized: {
|
|
10115
|
+
description: "Missing or invalid Bearer token",
|
|
10116
|
+
content: {
|
|
10117
|
+
"application/json": {
|
|
10118
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10119
|
+
}
|
|
10120
|
+
}
|
|
10121
|
+
},
|
|
10122
|
+
InsufficientScope: {
|
|
10123
|
+
description: "API key lacks required scope",
|
|
10124
|
+
content: {
|
|
10125
|
+
"application/json": {
|
|
10126
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10127
|
+
}
|
|
10128
|
+
}
|
|
10129
|
+
},
|
|
10130
|
+
NotFound: {
|
|
10131
|
+
description: "Tool or resource not found",
|
|
10132
|
+
content: {
|
|
10133
|
+
"application/json": {
|
|
10134
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10135
|
+
}
|
|
10136
|
+
}
|
|
10137
|
+
},
|
|
10138
|
+
RateLimited: {
|
|
10139
|
+
description: "Rate limit exceeded",
|
|
10140
|
+
headers: {
|
|
10141
|
+
"Retry-After": {
|
|
10142
|
+
schema: { type: "integer" },
|
|
10143
|
+
description: "Seconds to wait before retrying"
|
|
10144
|
+
}
|
|
10145
|
+
},
|
|
10146
|
+
content: {
|
|
10147
|
+
"application/json": {
|
|
10148
|
+
schema: { $ref: "#/components/schemas/ApiError" }
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10151
|
+
}
|
|
10152
|
+
}
|
|
10153
|
+
},
|
|
10154
|
+
security: [{ bearerAuth: [] }]
|
|
10155
|
+
};
|
|
10156
|
+
}
|
|
10157
|
+
|
|
10158
|
+
// src/api/router.ts
|
|
10159
|
+
function createRestApiRouter(options) {
|
|
10160
|
+
const router = Router();
|
|
10161
|
+
const tokenVerifier2 = createTokenVerifier({
|
|
10162
|
+
supabaseUrl: options.supabaseUrl,
|
|
10163
|
+
supabaseAnonKey: options.supabaseAnonKey
|
|
10164
|
+
});
|
|
10165
|
+
async function authenticate(req, res, next) {
|
|
10166
|
+
const authHeader = req.headers.authorization;
|
|
10167
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
10168
|
+
res.status(401).json({
|
|
10169
|
+
error: {
|
|
10170
|
+
code: "unauthorized",
|
|
10171
|
+
message: "Bearer token required. Get your API key at https://socialneuron.com/settings/developer",
|
|
10172
|
+
status: 401
|
|
10173
|
+
}
|
|
10174
|
+
});
|
|
10175
|
+
return;
|
|
10176
|
+
}
|
|
10177
|
+
const token = authHeader.slice(7);
|
|
10178
|
+
try {
|
|
10179
|
+
const authInfo = await tokenVerifier2.verifyAccessToken(token);
|
|
10180
|
+
req.auth = {
|
|
10181
|
+
userId: authInfo.extra?.userId ?? authInfo.clientId,
|
|
10182
|
+
scopes: authInfo.scopes,
|
|
10183
|
+
clientId: authInfo.clientId,
|
|
10184
|
+
token: authInfo.token
|
|
10185
|
+
};
|
|
10186
|
+
next();
|
|
10187
|
+
} catch (err) {
|
|
10188
|
+
const message = err instanceof Error ? err.message : "Token verification failed";
|
|
10189
|
+
res.status(401).json({
|
|
10190
|
+
error: { code: "invalid_token", message, status: 401 }
|
|
10191
|
+
});
|
|
10192
|
+
}
|
|
10193
|
+
}
|
|
10194
|
+
function rateLimit(req, res, next) {
|
|
10195
|
+
const rl = checkRateLimit("read", req.auth.userId);
|
|
10196
|
+
if (!rl.allowed) {
|
|
10197
|
+
res.setHeader("Retry-After", String(rl.retryAfter));
|
|
10198
|
+
res.status(429).json({
|
|
10199
|
+
error: {
|
|
10200
|
+
code: "rate_limited",
|
|
10201
|
+
message: "Too many requests. Please slow down.",
|
|
10202
|
+
retry_after: rl.retryAfter,
|
|
10203
|
+
status: 429
|
|
10204
|
+
}
|
|
10205
|
+
});
|
|
10206
|
+
return;
|
|
10207
|
+
}
|
|
10208
|
+
next();
|
|
10209
|
+
}
|
|
10210
|
+
router.get("/openapi.json", (_req, res) => {
|
|
10211
|
+
res.setHeader("Cache-Control", "public, max-age=3600");
|
|
10212
|
+
res.json(generateOpenApiSpec());
|
|
10213
|
+
});
|
|
10214
|
+
router.get("/", (_req, res) => {
|
|
10215
|
+
res.json({
|
|
10216
|
+
name: "Social Neuron API",
|
|
10217
|
+
version: MCP_VERSION,
|
|
10218
|
+
description: "AI content creation platform \u2014 REST API",
|
|
10219
|
+
tools: getRegisteredToolCount(),
|
|
10220
|
+
documentation: "https://socialneuron.com/docs/rest-api",
|
|
10221
|
+
endpoints: {
|
|
10222
|
+
tools: "/v1/tools",
|
|
10223
|
+
tool_proxy: "/v1/tools/:name",
|
|
10224
|
+
credits: "/v1/credits",
|
|
10225
|
+
brand: "/v1/brand",
|
|
10226
|
+
analytics: "/v1/analytics",
|
|
10227
|
+
posts: "/v1/posts",
|
|
10228
|
+
accounts: "/v1/accounts",
|
|
10229
|
+
content_generate: "/v1/content/generate",
|
|
10230
|
+
distribution_schedule: "/v1/distribution/schedule",
|
|
10231
|
+
openapi: "/v1/openapi.json"
|
|
10232
|
+
},
|
|
10233
|
+
auth: {
|
|
10234
|
+
type: "Bearer token",
|
|
10235
|
+
header: "Authorization: Bearer <your-api-key>",
|
|
10236
|
+
get_key: "https://socialneuron.com/settings/developer"
|
|
10237
|
+
}
|
|
10238
|
+
});
|
|
10239
|
+
});
|
|
10240
|
+
router.use(
|
|
10241
|
+
authenticate
|
|
10242
|
+
);
|
|
10243
|
+
router.use(
|
|
10244
|
+
rateLimit
|
|
10245
|
+
);
|
|
10246
|
+
async function executeInContext(req, res, toolName, args) {
|
|
10247
|
+
const scopeCheck = checkToolScope(toolName, req.auth.scopes);
|
|
10248
|
+
if (!scopeCheck.allowed) {
|
|
10249
|
+
res.status(403).json({
|
|
10250
|
+
error: {
|
|
10251
|
+
code: "insufficient_scope",
|
|
10252
|
+
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.`,
|
|
10253
|
+
required_scope: scopeCheck.requiredScope,
|
|
10254
|
+
status: 403
|
|
10255
|
+
}
|
|
10256
|
+
});
|
|
10257
|
+
return;
|
|
10258
|
+
}
|
|
10259
|
+
const rateLimitCategory = scopeCheck.requiredScope === "mcp:distribute" ? "posting" : scopeCheck.requiredScope === "mcp:write" ? "generation" : "read";
|
|
10260
|
+
const toolRl = checkRateLimit(rateLimitCategory, req.auth.userId);
|
|
10261
|
+
if (!toolRl.allowed) {
|
|
10262
|
+
res.setHeader("Retry-After", String(toolRl.retryAfter));
|
|
10263
|
+
res.status(429).json({
|
|
10264
|
+
error: {
|
|
10265
|
+
code: "rate_limited",
|
|
10266
|
+
message: `Rate limit exceeded for ${rateLimitCategory} operations. Wait ${toolRl.retryAfter}s.`,
|
|
10267
|
+
retry_after: toolRl.retryAfter,
|
|
10268
|
+
status: 429
|
|
10269
|
+
}
|
|
10270
|
+
});
|
|
10271
|
+
return;
|
|
10272
|
+
}
|
|
10273
|
+
const result = await requestContext.run(
|
|
10274
|
+
{
|
|
10275
|
+
userId: req.auth.userId,
|
|
10276
|
+
scopes: req.auth.scopes,
|
|
10277
|
+
creditsUsed: 0,
|
|
10278
|
+
assetsGenerated: 0
|
|
10279
|
+
},
|
|
10280
|
+
() => executeToolDirect(toolName, args)
|
|
10281
|
+
);
|
|
10282
|
+
if (result.isError) {
|
|
10283
|
+
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;
|
|
10284
|
+
res.status(status).json({
|
|
10285
|
+
error: { code: "tool_error", message: result.error, status },
|
|
10286
|
+
_meta: result._meta
|
|
10287
|
+
});
|
|
10288
|
+
return;
|
|
10289
|
+
}
|
|
10290
|
+
res.json({ data: result.data, _meta: result._meta });
|
|
10291
|
+
}
|
|
10292
|
+
router.get("/tools", (req, res) => {
|
|
10293
|
+
const tools = getToolCatalogForApi();
|
|
10294
|
+
const module = req.query.module;
|
|
10295
|
+
const scope = req.query.scope;
|
|
10296
|
+
const search = req.query.q;
|
|
10297
|
+
let filtered = tools;
|
|
10298
|
+
if (module) filtered = filtered.filter((t) => t.module === module);
|
|
10299
|
+
if (scope) filtered = filtered.filter((t) => t.scope === scope);
|
|
10300
|
+
if (search) {
|
|
10301
|
+
const q = search.toLowerCase();
|
|
10302
|
+
filtered = filtered.filter(
|
|
10303
|
+
(t) => t.name.toLowerCase().includes(q) || t.description.toLowerCase().includes(q)
|
|
10304
|
+
);
|
|
10305
|
+
}
|
|
10306
|
+
res.json({
|
|
10307
|
+
data: {
|
|
10308
|
+
tools: filtered,
|
|
10309
|
+
total: filtered.length,
|
|
10310
|
+
modules: [...new Set(TOOL_CATALOG.map((t) => t.module))]
|
|
10311
|
+
},
|
|
10312
|
+
_meta: { version: MCP_VERSION, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10313
|
+
});
|
|
10314
|
+
});
|
|
10315
|
+
router.post(
|
|
10316
|
+
"/tools/:name",
|
|
10317
|
+
async (req, res) => {
|
|
10318
|
+
const toolName = req.params.name;
|
|
10319
|
+
if (!hasRegisteredTool(toolName)) {
|
|
10320
|
+
res.status(404).json({
|
|
10321
|
+
error: {
|
|
10322
|
+
code: "tool_not_found",
|
|
10323
|
+
message: `Tool '${toolName}' not found. Use GET /v1/tools to list available tools.`,
|
|
10324
|
+
available_tools: TOOL_CATALOG.length,
|
|
10325
|
+
status: 404
|
|
10326
|
+
}
|
|
10327
|
+
});
|
|
10328
|
+
return;
|
|
10329
|
+
}
|
|
10330
|
+
await executeInContext(req, res, toolName, req.body || {});
|
|
10331
|
+
}
|
|
10332
|
+
);
|
|
10333
|
+
router.get("/credits", async (req, res) => {
|
|
10334
|
+
await executeInContext(req, res, "get_credit_balance", {
|
|
10335
|
+
response_format: "json"
|
|
10336
|
+
});
|
|
10337
|
+
});
|
|
10338
|
+
router.get(
|
|
10339
|
+
"/credits/budget",
|
|
10340
|
+
async (req, res) => {
|
|
10341
|
+
await executeInContext(req, res, "get_budget_status", {
|
|
10342
|
+
response_format: "json"
|
|
10343
|
+
});
|
|
10344
|
+
}
|
|
10345
|
+
);
|
|
10346
|
+
router.get("/brand", async (req, res) => {
|
|
10347
|
+
await executeInContext(req, res, "get_brand_profile", {
|
|
10348
|
+
response_format: "json"
|
|
10349
|
+
});
|
|
10350
|
+
});
|
|
10351
|
+
router.get("/analytics", async (req, res) => {
|
|
10352
|
+
const {
|
|
10353
|
+
days,
|
|
10354
|
+
platform: platform2,
|
|
10355
|
+
limit: qLimit
|
|
10356
|
+
} = req.query;
|
|
10357
|
+
await executeInContext(req, res, "fetch_analytics", {
|
|
10358
|
+
response_format: "json",
|
|
10359
|
+
...days && { days: Number(days) },
|
|
10360
|
+
...platform2 && { platform: platform2 },
|
|
10361
|
+
...qLimit && { limit: Number(qLimit) }
|
|
10362
|
+
});
|
|
10363
|
+
});
|
|
10364
|
+
router.get(
|
|
10365
|
+
"/analytics/insights",
|
|
10366
|
+
async (req, res) => {
|
|
10367
|
+
await executeInContext(req, res, "get_performance_insights", {
|
|
10368
|
+
response_format: "json"
|
|
10369
|
+
});
|
|
10370
|
+
}
|
|
10371
|
+
);
|
|
10372
|
+
router.get(
|
|
10373
|
+
"/analytics/best-times",
|
|
10374
|
+
async (req, res) => {
|
|
10375
|
+
await executeInContext(req, res, "get_best_posting_times", {
|
|
10376
|
+
response_format: "json"
|
|
10377
|
+
});
|
|
10378
|
+
}
|
|
10379
|
+
);
|
|
10380
|
+
router.get("/posts", async (req, res) => {
|
|
10381
|
+
await executeInContext(req, res, "list_recent_posts", {
|
|
10382
|
+
response_format: "json",
|
|
10383
|
+
limit: req.query.limit ? Number(req.query.limit) : void 0
|
|
10384
|
+
});
|
|
10385
|
+
});
|
|
10386
|
+
router.get("/accounts", async (req, res) => {
|
|
10387
|
+
await executeInContext(req, res, "list_connected_accounts", {
|
|
10388
|
+
response_format: "json"
|
|
10389
|
+
});
|
|
10390
|
+
});
|
|
10391
|
+
router.post(
|
|
10392
|
+
"/content/generate",
|
|
10393
|
+
async (req, res) => {
|
|
10394
|
+
await executeInContext(req, res, "generate_content", {
|
|
10395
|
+
response_format: "json",
|
|
10396
|
+
...req.body
|
|
10397
|
+
});
|
|
10398
|
+
}
|
|
10399
|
+
);
|
|
10400
|
+
router.post(
|
|
10401
|
+
"/content/adapt",
|
|
10402
|
+
async (req, res) => {
|
|
10403
|
+
await executeInContext(req, res, "adapt_content", {
|
|
10404
|
+
response_format: "json",
|
|
10405
|
+
...req.body
|
|
10406
|
+
});
|
|
10407
|
+
}
|
|
10408
|
+
);
|
|
10409
|
+
router.post(
|
|
10410
|
+
"/content/video",
|
|
10411
|
+
async (req, res) => {
|
|
10412
|
+
await executeInContext(req, res, "generate_video", req.body || {});
|
|
10413
|
+
}
|
|
10414
|
+
);
|
|
10415
|
+
router.post(
|
|
10416
|
+
"/content/image",
|
|
10417
|
+
async (req, res) => {
|
|
10418
|
+
await executeInContext(req, res, "generate_image", req.body || {});
|
|
10419
|
+
}
|
|
10420
|
+
);
|
|
10421
|
+
router.get(
|
|
10422
|
+
"/content/status/:jobId",
|
|
10423
|
+
async (req, res) => {
|
|
10424
|
+
await executeInContext(req, res, "check_status", {
|
|
10425
|
+
job_id: req.params.jobId,
|
|
10426
|
+
response_format: "json"
|
|
10427
|
+
});
|
|
10428
|
+
}
|
|
10429
|
+
);
|
|
10430
|
+
router.post(
|
|
10431
|
+
"/distribution/schedule",
|
|
10432
|
+
async (req, res) => {
|
|
10433
|
+
await executeInContext(req, res, "schedule_post", req.body || {});
|
|
10434
|
+
}
|
|
10435
|
+
);
|
|
10436
|
+
router.get("/loop", async (req, res) => {
|
|
10437
|
+
await executeInContext(req, res, "get_loop_summary", {
|
|
10438
|
+
response_format: "json"
|
|
10439
|
+
});
|
|
10440
|
+
});
|
|
10441
|
+
return router;
|
|
10442
|
+
}
|
|
10443
|
+
|
|
10444
|
+
// src/http.ts
|
|
9465
10445
|
var PORT = parseInt(process.env.PORT ?? "8080", 10);
|
|
9466
10446
|
var SUPABASE_URL2 = process.env.SUPABASE_URL ?? "";
|
|
9467
10447
|
var SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY ?? "";
|
|
@@ -9809,12 +10789,25 @@ app.delete(
|
|
|
9809
10789
|
res.status(200).json({ status: "session_closed" });
|
|
9810
10790
|
}
|
|
9811
10791
|
);
|
|
10792
|
+
var restCaptureServer = new McpServer({
|
|
10793
|
+
name: "socialneuron-rest",
|
|
10794
|
+
version: MCP_VERSION
|
|
10795
|
+
});
|
|
10796
|
+
captureToolHandlers(restCaptureServer);
|
|
10797
|
+
registerAllTools(restCaptureServer, { skipScreenshots: true });
|
|
10798
|
+
var restRouter = createRestApiRouter({
|
|
10799
|
+
supabaseUrl: SUPABASE_URL2,
|
|
10800
|
+
supabaseAnonKey: SUPABASE_ANON_KEY
|
|
10801
|
+
});
|
|
10802
|
+
app.use("/v1", restRouter);
|
|
10803
|
+
console.log("[MCP HTTP] REST API mounted at /v1");
|
|
9812
10804
|
var httpServer = app.listen(PORT, "0.0.0.0", () => {
|
|
9813
10805
|
console.log(
|
|
9814
10806
|
`[MCP HTTP] Social Neuron MCP Server listening on 0.0.0.0:${PORT}`
|
|
9815
10807
|
);
|
|
9816
10808
|
console.log(`[MCP HTTP] Health: http://localhost:${PORT}/health`);
|
|
9817
10809
|
console.log(`[MCP HTTP] MCP endpoint: ${MCP_SERVER_URL}`);
|
|
10810
|
+
console.log(`[MCP HTTP] REST API: http://localhost:${PORT}/v1`);
|
|
9818
10811
|
console.log(`[MCP HTTP] Environment: ${NODE_ENV}`);
|
|
9819
10812
|
});
|
|
9820
10813
|
async function shutdown(signal) {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socialneuron/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "MCP server for Social Neuron - AI content creation platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"type": "git",
|
|
23
23
|
"url": "git+https://github.com/socialneuron/mcp-server.git"
|
|
24
24
|
},
|
|
25
|
+
"source": "./src/index.ts",
|
|
25
26
|
"exports": {
|
|
26
27
|
".": "./dist/index.js",
|
|
27
28
|
"./http": "./dist/http.js"
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"tiktok",
|
|
44
45
|
"instagram"
|
|
45
46
|
],
|
|
47
|
+
"mcpName": "com.socialneuron/mcp-server",
|
|
46
48
|
"publishConfig": {
|
|
47
49
|
"access": "public"
|
|
48
50
|
},
|