@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 CHANGED
@@ -1,13 +1,26 @@
1
1
  # @socialneuron/mcp-server
2
2
 
3
- > 52 MCP tools for AI-powered social media management. Create content, schedule posts, track analytics, and optimize performance — all from Claude Code or any MCP client.
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
  [![npm version](https://img.shields.io/npm/v/@socialneuron/mcp-server)](https://www.npmjs.com/package/@socialneuron/mcp-server)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- ### 1. Authenticate
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
- ### 2. Add to Claude Code
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
- ### 3. Start using
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
- These tools are available to AI agents (Claude, Cursor, etc.) via the MCP protocol.
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 repo](https://github.com/socialneuron/examples) for prompt-driven workflow templates:
321
+ See the [`examples/`](examples/) directory:
277
322
 
278
- - Weekly content batch planning
279
- - Cross-platform content repurposing
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
- - [Social Neuron](https://socialneuron.com)
287
- - [For Developers](https://socialneuron.com/for-developers)
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.5.2";
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
@@ -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.5.2";
17
+ MCP_VERSION = "1.6.0";
18
18
  }
19
19
  });
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socialneuron/mcp-server",
3
- "version": "1.5.2",
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
  },