@socialneuron/mcp-server 1.5.1 → 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/CHANGELOG.md +7 -0
- package/README.md +62 -13
- package/dist/http.js +1081 -53
- package/dist/index.js +88 -53
- package/package.json +3 -1
package/dist/http.js
CHANGED
|
@@ -853,6 +853,41 @@ function checkRateLimit(category, key) {
|
|
|
853
853
|
|
|
854
854
|
// src/tools/ideation.ts
|
|
855
855
|
init_supabase();
|
|
856
|
+
|
|
857
|
+
// src/lib/tool-errors.ts
|
|
858
|
+
function formatToolError(rawMessage) {
|
|
859
|
+
const msg = rawMessage.toLowerCase();
|
|
860
|
+
if (msg.includes("rate limit") || msg.includes("too many requests")) {
|
|
861
|
+
return `${rawMessage} Reduce request frequency or wait before retrying.`;
|
|
862
|
+
}
|
|
863
|
+
if (msg.includes("insufficient credit") || msg.includes("budget") || msg.includes("spending cap")) {
|
|
864
|
+
return `${rawMessage} Call get_credit_balance to check remaining credits. Consider a cheaper model or wait for monthly refresh.`;
|
|
865
|
+
}
|
|
866
|
+
if (msg.includes("oauth") || msg.includes("token expired") || msg.includes("not connected") || msg.includes("reconnect")) {
|
|
867
|
+
return `${rawMessage} Call list_connected_accounts to check status. User may need to reconnect at socialneuron.com/settings/connections.`;
|
|
868
|
+
}
|
|
869
|
+
if (msg.includes("generation failed") || msg.includes("failed to start") || msg.includes("no job id") || msg.includes("could not be parsed")) {
|
|
870
|
+
return `${rawMessage} Try simplifying the prompt, using a different model, or check credits with get_credit_balance.`;
|
|
871
|
+
}
|
|
872
|
+
if (msg.includes("not found") || msg.includes("no ") && msg.includes(" found")) {
|
|
873
|
+
return `${rawMessage} Verify the ID is correct \u2014 use the corresponding list tool to find valid IDs.`;
|
|
874
|
+
}
|
|
875
|
+
if (msg.includes("not accessible") || msg.includes("unauthorized") || msg.includes("permission")) {
|
|
876
|
+
return `${rawMessage} Check API key scopes with get_credit_balance. A higher-tier plan may be required.`;
|
|
877
|
+
}
|
|
878
|
+
if (msg.includes("ssrf") || msg.includes("url blocked")) {
|
|
879
|
+
return `${rawMessage} The URL was blocked for security. Use a publicly accessible HTTPS URL.`;
|
|
880
|
+
}
|
|
881
|
+
if (msg.includes("failed to schedule") || msg.includes("scheduling failed")) {
|
|
882
|
+
return `${rawMessage} Verify platform OAuth is active with list_connected_accounts, then retry.`;
|
|
883
|
+
}
|
|
884
|
+
if (msg.includes("no posts") || msg.includes("plan") && msg.includes("has no")) {
|
|
885
|
+
return `${rawMessage} Generate a plan with plan_content_week first, then save with save_content_plan.`;
|
|
886
|
+
}
|
|
887
|
+
return rawMessage;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/tools/ideation.ts
|
|
856
891
|
function registerIdeationTools(server) {
|
|
857
892
|
server.tool(
|
|
858
893
|
"generate_content",
|
|
@@ -1026,7 +1061,7 @@ Content Type: ${content_type}`;
|
|
|
1026
1061
|
content: [
|
|
1027
1062
|
{
|
|
1028
1063
|
type: "text",
|
|
1029
|
-
text: `Content generation failed: ${error}`
|
|
1064
|
+
text: formatToolError(`Content generation failed: ${error}`)
|
|
1030
1065
|
}
|
|
1031
1066
|
],
|
|
1032
1067
|
isError: true
|
|
@@ -1091,7 +1126,7 @@ Content Type: ${content_type}`;
|
|
|
1091
1126
|
content: [
|
|
1092
1127
|
{
|
|
1093
1128
|
type: "text",
|
|
1094
|
-
text: `Failed to fetch trends: ${error}`
|
|
1129
|
+
text: formatToolError(`Failed to fetch trends: ${error}`)
|
|
1095
1130
|
}
|
|
1096
1131
|
],
|
|
1097
1132
|
isError: true
|
|
@@ -1259,7 +1294,7 @@ ${content}`,
|
|
|
1259
1294
|
content: [
|
|
1260
1295
|
{
|
|
1261
1296
|
type: "text",
|
|
1262
|
-
text: `Content adaptation failed: ${error}`
|
|
1297
|
+
text: formatToolError(`Content adaptation failed: ${error}`)
|
|
1263
1298
|
}
|
|
1264
1299
|
],
|
|
1265
1300
|
isError: true
|
|
@@ -1333,7 +1368,7 @@ function sanitizeDbError(error) {
|
|
|
1333
1368
|
init_request_context();
|
|
1334
1369
|
|
|
1335
1370
|
// src/lib/version.ts
|
|
1336
|
-
var MCP_VERSION = "1.
|
|
1371
|
+
var MCP_VERSION = "1.6.0";
|
|
1337
1372
|
|
|
1338
1373
|
// src/tools/content.ts
|
|
1339
1374
|
var MAX_CREDITS_PER_RUN = Math.max(
|
|
@@ -1573,7 +1608,7 @@ function registerContentTools(server) {
|
|
|
1573
1608
|
content: [
|
|
1574
1609
|
{
|
|
1575
1610
|
type: "text",
|
|
1576
|
-
text: `Video generation failed to start: ${error}`
|
|
1611
|
+
text: formatToolError(`Video generation failed to start: ${error}`)
|
|
1577
1612
|
}
|
|
1578
1613
|
],
|
|
1579
1614
|
isError: true
|
|
@@ -1590,7 +1625,7 @@ function registerContentTools(server) {
|
|
|
1590
1625
|
content: [
|
|
1591
1626
|
{
|
|
1592
1627
|
type: "text",
|
|
1593
|
-
text: "Video generation failed: no job ID returned."
|
|
1628
|
+
text: formatToolError("Video generation failed: no job ID returned.")
|
|
1594
1629
|
}
|
|
1595
1630
|
],
|
|
1596
1631
|
isError: true
|
|
@@ -1766,7 +1801,7 @@ function registerContentTools(server) {
|
|
|
1766
1801
|
content: [
|
|
1767
1802
|
{
|
|
1768
1803
|
type: "text",
|
|
1769
|
-
text: `Image generation failed to start: ${error}`
|
|
1804
|
+
text: formatToolError(`Image generation failed to start: ${error}`)
|
|
1770
1805
|
}
|
|
1771
1806
|
],
|
|
1772
1807
|
isError: true
|
|
@@ -1783,7 +1818,7 @@ function registerContentTools(server) {
|
|
|
1783
1818
|
content: [
|
|
1784
1819
|
{
|
|
1785
1820
|
type: "text",
|
|
1786
|
-
text: "Image generation failed: no job ID returned."
|
|
1821
|
+
text: formatToolError("Image generation failed: no job ID returned.")
|
|
1787
1822
|
}
|
|
1788
1823
|
],
|
|
1789
1824
|
isError: true
|
|
@@ -1907,7 +1942,7 @@ function registerContentTools(server) {
|
|
|
1907
1942
|
content: [
|
|
1908
1943
|
{
|
|
1909
1944
|
type: "text",
|
|
1910
|
-
text: `Failed to look up job: ${sanitizeDbError(jobError)}`
|
|
1945
|
+
text: formatToolError(`Failed to look up job: ${sanitizeDbError(jobError)}`)
|
|
1911
1946
|
}
|
|
1912
1947
|
],
|
|
1913
1948
|
isError: true
|
|
@@ -1924,7 +1959,7 @@ function registerContentTools(server) {
|
|
|
1924
1959
|
content: [
|
|
1925
1960
|
{
|
|
1926
1961
|
type: "text",
|
|
1927
|
-
text: `No job found with ID "${job_id}". The ID may be incorrect or the job has expired.`
|
|
1962
|
+
text: formatToolError(`No job found with ID "${job_id}". The ID may be incorrect or the job has expired.`)
|
|
1928
1963
|
}
|
|
1929
1964
|
],
|
|
1930
1965
|
isError: true
|
|
@@ -2172,7 +2207,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2172
2207
|
content: [
|
|
2173
2208
|
{
|
|
2174
2209
|
type: "text",
|
|
2175
|
-
text: `Storyboard generation failed: ${error}`
|
|
2210
|
+
text: formatToolError(`Storyboard generation failed: ${error}`)
|
|
2176
2211
|
}
|
|
2177
2212
|
],
|
|
2178
2213
|
isError: true
|
|
@@ -2307,7 +2342,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2307
2342
|
content: [
|
|
2308
2343
|
{
|
|
2309
2344
|
type: "text",
|
|
2310
|
-
text: `Voiceover generation failed: ${error}`
|
|
2345
|
+
text: formatToolError(`Voiceover generation failed: ${error}`)
|
|
2311
2346
|
}
|
|
2312
2347
|
],
|
|
2313
2348
|
isError: true
|
|
@@ -2324,7 +2359,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2324
2359
|
content: [
|
|
2325
2360
|
{
|
|
2326
2361
|
type: "text",
|
|
2327
|
-
text: "Voiceover generation failed: no audio URL returned."
|
|
2362
|
+
text: formatToolError("Voiceover generation failed: no audio URL returned.")
|
|
2328
2363
|
}
|
|
2329
2364
|
],
|
|
2330
2365
|
isError: true
|
|
@@ -2487,7 +2522,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2487
2522
|
content: [
|
|
2488
2523
|
{
|
|
2489
2524
|
type: "text",
|
|
2490
|
-
text: `Carousel generation failed: ${error}`
|
|
2525
|
+
text: formatToolError(`Carousel generation failed: ${error}`)
|
|
2491
2526
|
}
|
|
2492
2527
|
],
|
|
2493
2528
|
isError: true
|
|
@@ -2834,7 +2869,7 @@ Created with Social Neuron`;
|
|
|
2834
2869
|
content: [
|
|
2835
2870
|
{
|
|
2836
2871
|
type: "text",
|
|
2837
|
-
text: `Failed to schedule post: ${error}`
|
|
2872
|
+
text: formatToolError(`Failed to schedule post: ${error}`)
|
|
2838
2873
|
}
|
|
2839
2874
|
],
|
|
2840
2875
|
isError: true
|
|
@@ -2922,7 +2957,7 @@ Created with Social Neuron`;
|
|
|
2922
2957
|
content: [
|
|
2923
2958
|
{
|
|
2924
2959
|
type: "text",
|
|
2925
|
-
text: `Failed to list connected accounts: ${sanitizeDbError(error)}`
|
|
2960
|
+
text: formatToolError(`Failed to list connected accounts: ${sanitizeDbError(error)}`)
|
|
2926
2961
|
}
|
|
2927
2962
|
],
|
|
2928
2963
|
isError: true
|
|
@@ -3021,7 +3056,7 @@ Created with Social Neuron`;
|
|
|
3021
3056
|
content: [
|
|
3022
3057
|
{
|
|
3023
3058
|
type: "text",
|
|
3024
|
-
text: `Failed to list posts: ${sanitizeDbError(error)}`
|
|
3059
|
+
text: formatToolError(`Failed to list posts: ${sanitizeDbError(error)}`)
|
|
3025
3060
|
}
|
|
3026
3061
|
],
|
|
3027
3062
|
isError: true
|
|
@@ -3226,7 +3261,7 @@ Created with Social Neuron`;
|
|
|
3226
3261
|
});
|
|
3227
3262
|
return {
|
|
3228
3263
|
content: [
|
|
3229
|
-
{ type: "text", text: `Failed to find slots: ${message}` }
|
|
3264
|
+
{ type: "text", text: formatToolError(`Failed to find slots: ${message}`) }
|
|
3230
3265
|
],
|
|
3231
3266
|
isError: true
|
|
3232
3267
|
};
|
|
@@ -3296,7 +3331,7 @@ Created with Social Neuron`;
|
|
|
3296
3331
|
content: [
|
|
3297
3332
|
{
|
|
3298
3333
|
type: "text",
|
|
3299
|
-
text: `Failed to load content plan: ${sanitizeDbError(storedError)}`
|
|
3334
|
+
text: formatToolError(`Failed to load content plan: ${sanitizeDbError(storedError)}`)
|
|
3300
3335
|
}
|
|
3301
3336
|
],
|
|
3302
3337
|
isError: true
|
|
@@ -3307,7 +3342,7 @@ Created with Social Neuron`;
|
|
|
3307
3342
|
content: [
|
|
3308
3343
|
{
|
|
3309
3344
|
type: "text",
|
|
3310
|
-
text: `No content plan found for plan_id=${plan_id}`
|
|
3345
|
+
text: formatToolError(`No content plan found for plan_id=${plan_id}`)
|
|
3311
3346
|
}
|
|
3312
3347
|
],
|
|
3313
3348
|
isError: true
|
|
@@ -3322,7 +3357,7 @@ Created with Social Neuron`;
|
|
|
3322
3357
|
content: [
|
|
3323
3358
|
{
|
|
3324
3359
|
type: "text",
|
|
3325
|
-
text: `Stored plan ${plan_id} has no posts array.`
|
|
3360
|
+
text: formatToolError(`Stored plan ${plan_id} has no posts array.`)
|
|
3326
3361
|
}
|
|
3327
3362
|
],
|
|
3328
3363
|
isError: true
|
|
@@ -3361,7 +3396,7 @@ Created with Social Neuron`;
|
|
|
3361
3396
|
content: [
|
|
3362
3397
|
{
|
|
3363
3398
|
type: "text",
|
|
3364
|
-
text: `Failed to load plan approvals: ${sanitizeDbError(approvalsError)}`
|
|
3399
|
+
text: formatToolError(`Failed to load plan approvals: ${sanitizeDbError(approvalsError)}`)
|
|
3365
3400
|
}
|
|
3366
3401
|
],
|
|
3367
3402
|
isError: true
|
|
@@ -3796,7 +3831,7 @@ Created with Social Neuron`;
|
|
|
3796
3831
|
content: [
|
|
3797
3832
|
{
|
|
3798
3833
|
type: "text",
|
|
3799
|
-
text: `Batch scheduling failed: ${message}`
|
|
3834
|
+
text: formatToolError(`Batch scheduling failed: ${message}`)
|
|
3800
3835
|
}
|
|
3801
3836
|
],
|
|
3802
3837
|
isError: true
|
|
@@ -3887,7 +3922,7 @@ function registerAnalyticsTools(server) {
|
|
|
3887
3922
|
content: [
|
|
3888
3923
|
{
|
|
3889
3924
|
type: "text",
|
|
3890
|
-
text: `Failed to fetch user-scoped posts: ${sanitizeDbError(postsError)}`
|
|
3925
|
+
text: formatToolError(`Failed to fetch user-scoped posts: ${sanitizeDbError(postsError)}`)
|
|
3891
3926
|
}
|
|
3892
3927
|
],
|
|
3893
3928
|
isError: true
|
|
@@ -3934,7 +3969,7 @@ function registerAnalyticsTools(server) {
|
|
|
3934
3969
|
content: [
|
|
3935
3970
|
{
|
|
3936
3971
|
type: "text",
|
|
3937
|
-
text: `Failed to fetch analytics: ${sanitizeDbError(simpleError)}`
|
|
3972
|
+
text: formatToolError(`Failed to fetch analytics: ${sanitizeDbError(simpleError)}`)
|
|
3938
3973
|
}
|
|
3939
3974
|
],
|
|
3940
3975
|
isError: true
|
|
@@ -4064,7 +4099,7 @@ function registerAnalyticsTools(server) {
|
|
|
4064
4099
|
content: [
|
|
4065
4100
|
{
|
|
4066
4101
|
type: "text",
|
|
4067
|
-
text: `Error refreshing analytics: ${error}`
|
|
4102
|
+
text: formatToolError(`Error refreshing analytics: ${error}`)
|
|
4068
4103
|
}
|
|
4069
4104
|
],
|
|
4070
4105
|
isError: true
|
|
@@ -4451,7 +4486,7 @@ function registerBrandTools(server) {
|
|
|
4451
4486
|
content: [
|
|
4452
4487
|
{
|
|
4453
4488
|
type: "text",
|
|
4454
|
-
text: `Brand extraction failed: ${error}`
|
|
4489
|
+
text: formatToolError(`Brand extraction failed: ${error}`)
|
|
4455
4490
|
}
|
|
4456
4491
|
],
|
|
4457
4492
|
isError: true
|
|
@@ -4561,7 +4596,7 @@ function registerBrandTools(server) {
|
|
|
4561
4596
|
content: [
|
|
4562
4597
|
{
|
|
4563
4598
|
type: "text",
|
|
4564
|
-
text: `Failed to load brand profile: ${sanitizeDbError(error)}`
|
|
4599
|
+
text: formatToolError(`Failed to load brand profile: ${sanitizeDbError(error)}`)
|
|
4565
4600
|
}
|
|
4566
4601
|
],
|
|
4567
4602
|
isError: true
|
|
@@ -4692,7 +4727,7 @@ function registerBrandTools(server) {
|
|
|
4692
4727
|
content: [
|
|
4693
4728
|
{
|
|
4694
4729
|
type: "text",
|
|
4695
|
-
text: `Failed to save brand profile: ${sanitizeDbError(error)}`
|
|
4730
|
+
text: formatToolError(`Failed to save brand profile: ${sanitizeDbError(error)}`)
|
|
4696
4731
|
}
|
|
4697
4732
|
],
|
|
4698
4733
|
isError: true
|
|
@@ -4868,7 +4903,7 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
4868
4903
|
content: [
|
|
4869
4904
|
{
|
|
4870
4905
|
type: "text",
|
|
4871
|
-
text: `Failed to update platform voice: ${saveError.message}`
|
|
4906
|
+
text: formatToolError(`Failed to update platform voice: ${saveError.message}`)
|
|
4872
4907
|
}
|
|
4873
4908
|
],
|
|
4874
4909
|
isError: true
|
|
@@ -6065,7 +6100,7 @@ function registerCommentsTools(server) {
|
|
|
6065
6100
|
if (error) {
|
|
6066
6101
|
return {
|
|
6067
6102
|
content: [
|
|
6068
|
-
{ type: "text", text: `Error listing comments: ${error}` }
|
|
6103
|
+
{ type: "text", text: formatToolError(`Error listing comments: ${error}`) }
|
|
6069
6104
|
],
|
|
6070
6105
|
isError: true
|
|
6071
6106
|
};
|
|
@@ -6169,7 +6204,7 @@ function registerCommentsTools(server) {
|
|
|
6169
6204
|
content: [
|
|
6170
6205
|
{
|
|
6171
6206
|
type: "text",
|
|
6172
|
-
text: `Error replying to comment: ${error}`
|
|
6207
|
+
text: formatToolError(`Error replying to comment: ${error}`)
|
|
6173
6208
|
}
|
|
6174
6209
|
],
|
|
6175
6210
|
isError: true
|
|
@@ -6255,7 +6290,7 @@ function registerCommentsTools(server) {
|
|
|
6255
6290
|
});
|
|
6256
6291
|
return {
|
|
6257
6292
|
content: [
|
|
6258
|
-
{ type: "text", text: `Error posting comment: ${error}` }
|
|
6293
|
+
{ type: "text", text: formatToolError(`Error posting comment: ${error}`) }
|
|
6259
6294
|
],
|
|
6260
6295
|
isError: true
|
|
6261
6296
|
};
|
|
@@ -6342,7 +6377,7 @@ function registerCommentsTools(server) {
|
|
|
6342
6377
|
content: [
|
|
6343
6378
|
{
|
|
6344
6379
|
type: "text",
|
|
6345
|
-
text: `Error moderating comment: ${error}`
|
|
6380
|
+
text: formatToolError(`Error moderating comment: ${error}`)
|
|
6346
6381
|
}
|
|
6347
6382
|
],
|
|
6348
6383
|
isError: true
|
|
@@ -6431,7 +6466,7 @@ function registerCommentsTools(server) {
|
|
|
6431
6466
|
});
|
|
6432
6467
|
return {
|
|
6433
6468
|
content: [
|
|
6434
|
-
{ type: "text", text: `Error deleting comment: ${error}` }
|
|
6469
|
+
{ type: "text", text: formatToolError(`Error deleting comment: ${error}`) }
|
|
6435
6470
|
],
|
|
6436
6471
|
isError: true
|
|
6437
6472
|
};
|
|
@@ -6705,7 +6740,7 @@ function registerCreditsTools(server) {
|
|
|
6705
6740
|
content: [
|
|
6706
6741
|
{
|
|
6707
6742
|
type: "text",
|
|
6708
|
-
text: `Failed to fetch credit balance: ${sanitizeDbError(profileResult.error)}`
|
|
6743
|
+
text: formatToolError(`Failed to fetch credit balance: ${sanitizeDbError(profileResult.error)}`)
|
|
6709
6744
|
}
|
|
6710
6745
|
],
|
|
6711
6746
|
isError: true
|
|
@@ -7346,7 +7381,7 @@ function registerExtractionTools(server) {
|
|
|
7346
7381
|
content: [
|
|
7347
7382
|
{
|
|
7348
7383
|
type: "text",
|
|
7349
|
-
text: `Failed to extract YouTube video: ${error ?? "No data returned"}`
|
|
7384
|
+
text: formatToolError(`Failed to extract YouTube video: ${error ?? "No data returned"}`)
|
|
7350
7385
|
}
|
|
7351
7386
|
],
|
|
7352
7387
|
isError: true
|
|
@@ -7386,7 +7421,7 @@ function registerExtractionTools(server) {
|
|
|
7386
7421
|
content: [
|
|
7387
7422
|
{
|
|
7388
7423
|
type: "text",
|
|
7389
|
-
text: `Failed to extract YouTube channel: ${error ?? "No data returned"}`
|
|
7424
|
+
text: formatToolError(`Failed to extract YouTube channel: ${error ?? "No data returned"}`)
|
|
7390
7425
|
}
|
|
7391
7426
|
],
|
|
7392
7427
|
isError: true
|
|
@@ -7417,7 +7452,7 @@ function registerExtractionTools(server) {
|
|
|
7417
7452
|
content: [
|
|
7418
7453
|
{
|
|
7419
7454
|
type: "text",
|
|
7420
|
-
text: `Failed to extract URL content: ${error ?? "No data returned"}`
|
|
7455
|
+
text: formatToolError(`Failed to extract URL content: ${error ?? "No data returned"}`)
|
|
7421
7456
|
}
|
|
7422
7457
|
],
|
|
7423
7458
|
isError: true
|
|
@@ -7474,7 +7509,7 @@ function registerExtractionTools(server) {
|
|
|
7474
7509
|
});
|
|
7475
7510
|
return {
|
|
7476
7511
|
content: [
|
|
7477
|
-
{ type: "text", text: `Extraction failed: ${message}` }
|
|
7512
|
+
{ type: "text", text: formatToolError(`Extraction failed: ${message}`) }
|
|
7478
7513
|
],
|
|
7479
7514
|
isError: true
|
|
7480
7515
|
};
|
|
@@ -7972,7 +8007,7 @@ ${ideationContext.promptInjection.slice(0, 1500)}` : "",
|
|
|
7972
8007
|
content: [
|
|
7973
8008
|
{
|
|
7974
8009
|
type: "text",
|
|
7975
|
-
text: `Plan generation failed: ${aiError ?? "No response from AI"}`
|
|
8010
|
+
text: formatToolError(`Plan generation failed: ${aiError ?? "No response from AI"}`)
|
|
7976
8011
|
}
|
|
7977
8012
|
],
|
|
7978
8013
|
isError: true
|
|
@@ -7992,10 +8027,10 @@ ${ideationContext.promptInjection.slice(0, 1500)}` : "",
|
|
|
7992
8027
|
content: [
|
|
7993
8028
|
{
|
|
7994
8029
|
type: "text",
|
|
7995
|
-
text: `AI response could not be parsed as JSON.
|
|
8030
|
+
text: formatToolError(`AI response could not be parsed as JSON.
|
|
7996
8031
|
|
|
7997
8032
|
Raw output (first 1000 chars):
|
|
7998
|
-
${rawText.slice(0, 1e3)}`
|
|
8033
|
+
${rawText.slice(0, 1e3)}`)
|
|
7999
8034
|
}
|
|
8000
8035
|
],
|
|
8001
8036
|
isError: true
|
|
@@ -8122,7 +8157,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8122
8157
|
content: [
|
|
8123
8158
|
{
|
|
8124
8159
|
type: "text",
|
|
8125
|
-
text: `Plan generation failed: ${message}`
|
|
8160
|
+
text: formatToolError(`Plan generation failed: ${message}`)
|
|
8126
8161
|
}
|
|
8127
8162
|
],
|
|
8128
8163
|
isError: true
|
|
@@ -8235,7 +8270,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8235
8270
|
content: [
|
|
8236
8271
|
{
|
|
8237
8272
|
type: "text",
|
|
8238
|
-
text: `Failed to save content plan: ${message}`
|
|
8273
|
+
text: formatToolError(`Failed to save content plan: ${message}`)
|
|
8239
8274
|
}
|
|
8240
8275
|
],
|
|
8241
8276
|
isError: true
|
|
@@ -8268,7 +8303,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8268
8303
|
content: [
|
|
8269
8304
|
{
|
|
8270
8305
|
type: "text",
|
|
8271
|
-
text: `Failed to load content plan: ${sanitizeDbError(error)}`
|
|
8306
|
+
text: formatToolError(`Failed to load content plan: ${sanitizeDbError(error)}`)
|
|
8272
8307
|
}
|
|
8273
8308
|
],
|
|
8274
8309
|
isError: true
|
|
@@ -8279,7 +8314,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8279
8314
|
content: [
|
|
8280
8315
|
{
|
|
8281
8316
|
type: "text",
|
|
8282
|
-
text: `No content plan found for plan_id=${plan_id}`
|
|
8317
|
+
text: formatToolError(`No content plan found for plan_id=${plan_id}`)
|
|
8283
8318
|
}
|
|
8284
8319
|
],
|
|
8285
8320
|
isError: true
|
|
@@ -8367,7 +8402,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8367
8402
|
content: [
|
|
8368
8403
|
{
|
|
8369
8404
|
type: "text",
|
|
8370
|
-
text: `No content plan found for plan_id=${plan_id}`
|
|
8405
|
+
text: formatToolError(`No content plan found for plan_id=${plan_id}`)
|
|
8371
8406
|
}
|
|
8372
8407
|
],
|
|
8373
8408
|
isError: true
|
|
@@ -8411,7 +8446,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8411
8446
|
content: [
|
|
8412
8447
|
{
|
|
8413
8448
|
type: "text",
|
|
8414
|
-
text: `Failed to update content plan: ${sanitizeDbError(saveError)}`
|
|
8449
|
+
text: formatToolError(`Failed to update content plan: ${sanitizeDbError(saveError)}`)
|
|
8415
8450
|
}
|
|
8416
8451
|
],
|
|
8417
8452
|
isError: true
|
|
@@ -8478,7 +8513,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8478
8513
|
content: [
|
|
8479
8514
|
{
|
|
8480
8515
|
type: "text",
|
|
8481
|
-
text: `No content plan found for plan_id=${plan_id}`
|
|
8516
|
+
text: formatToolError(`No content plan found for plan_id=${plan_id}`)
|
|
8482
8517
|
}
|
|
8483
8518
|
],
|
|
8484
8519
|
isError: true
|
|
@@ -8491,7 +8526,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8491
8526
|
content: [
|
|
8492
8527
|
{
|
|
8493
8528
|
type: "text",
|
|
8494
|
-
text: `Plan ${plan_id} has no posts to submit.`
|
|
8529
|
+
text: formatToolError(`Plan ${plan_id} has no posts to submit.`)
|
|
8495
8530
|
}
|
|
8496
8531
|
],
|
|
8497
8532
|
isError: true
|
|
@@ -8511,7 +8546,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8511
8546
|
content: [
|
|
8512
8547
|
{
|
|
8513
8548
|
type: "text",
|
|
8514
|
-
text: `Failed to create approvals: ${sanitizeDbError(approvalError)}`
|
|
8549
|
+
text: formatToolError(`Failed to create approvals: ${sanitizeDbError(approvalError)}`)
|
|
8515
8550
|
}
|
|
8516
8551
|
],
|
|
8517
8552
|
isError: true
|
|
@@ -8523,7 +8558,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8523
8558
|
content: [
|
|
8524
8559
|
{
|
|
8525
8560
|
type: "text",
|
|
8526
|
-
text: `Failed to update plan status: ${sanitizeDbError(statusError)}`
|
|
8561
|
+
text: formatToolError(`Failed to update plan status: ${sanitizeDbError(statusError)}`)
|
|
8527
8562
|
}
|
|
8528
8563
|
],
|
|
8529
8564
|
isError: true
|
|
@@ -9427,6 +9462,986 @@ async function verifyApiKey(apiKey, supabaseUrl, supabaseAnonKey) {
|
|
|
9427
9462
|
|
|
9428
9463
|
// src/http.ts
|
|
9429
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
|
|
9430
10445
|
var PORT = parseInt(process.env.PORT ?? "8080", 10);
|
|
9431
10446
|
var SUPABASE_URL2 = process.env.SUPABASE_URL ?? "";
|
|
9432
10447
|
var SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY ?? "";
|
|
@@ -9774,12 +10789,25 @@ app.delete(
|
|
|
9774
10789
|
res.status(200).json({ status: "session_closed" });
|
|
9775
10790
|
}
|
|
9776
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");
|
|
9777
10804
|
var httpServer = app.listen(PORT, "0.0.0.0", () => {
|
|
9778
10805
|
console.log(
|
|
9779
10806
|
`[MCP HTTP] Social Neuron MCP Server listening on 0.0.0.0:${PORT}`
|
|
9780
10807
|
);
|
|
9781
10808
|
console.log(`[MCP HTTP] Health: http://localhost:${PORT}/health`);
|
|
9782
10809
|
console.log(`[MCP HTTP] MCP endpoint: ${MCP_SERVER_URL}`);
|
|
10810
|
+
console.log(`[MCP HTTP] REST API: http://localhost:${PORT}/v1`);
|
|
9783
10811
|
console.log(`[MCP HTTP] Environment: ${NODE_ENV}`);
|
|
9784
10812
|
});
|
|
9785
10813
|
async function shutdown(signal) {
|