@socialneuron/mcp-server 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/http.js +21 -12
- package/dist/index.js +10 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@socialneuron/mcp-server` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.6.1] - 2026-03-22
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
- **Explicit body size limit**: `express.json({ limit: '50kb' })` prevents DoS via oversized payloads.
|
|
9
|
+
- **Error message sanitization**: MCP POST catch block now uses `sanitizeError()` — no more internal paths or table names in error responses.
|
|
10
|
+
- **PII removal**: Removed `email` from API key validation chain (7 files). Key validation no longer exposes user email addresses.
|
|
11
|
+
- **Generation rate limiting**: Added explicit `generation` category at 20 req/min (previously fell back to `read` at 60/min).
|
|
12
|
+
- **npm provenance**: Added `--provenance` flag and `id-token: write` permission to release workflow for supply chain verification.
|
|
13
|
+
- **Security comment**: Documented that Edge Functions must not trust `x-internal-worker-call` header without Bearer token verification.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **hono prototype pollution**: Updated transitive dependency to fix GHSA-v8w9-8mx6-g223.
|
|
17
|
+
- `npm audit` now reports 0 vulnerabilities.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- 18 examples (8 REST curl, 5 TypeScript SDK, 4 CLI, 1 MCP prompts).
|
|
21
|
+
- TypeScript SDK package (`packages/sdk/`) with 9 resource classes.
|
|
22
|
+
- CLI tab completion and content generation commands.
|
|
23
|
+
- SDK documentation and release workflow.
|
|
24
|
+
|
|
25
|
+
## [1.6.0] - 2026-03-21
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **REST API layer**: Universal tool proxy at `POST /v1/tools/:name` — call any of the 52 MCP tools via standard HTTP REST. No MCP client required.
|
|
29
|
+
- **OpenAPI 3.1 spec**: Auto-generated from TOOL_CATALOG at `/openapi.json` — always in sync with tools.
|
|
30
|
+
- **15 convenience endpoints**: Resource-oriented routes for common operations (`/v1/credits`, `/v1/content/generate`, `/v1/posts`, etc.).
|
|
31
|
+
- **Express HTTP transport**: New `dist/http.js` entry point for running as a standalone REST API server.
|
|
32
|
+
- **MCP Registry metadata**: `server.json` with mcpName, endpoints, env, and auth configuration for registry discovery.
|
|
33
|
+
- **Cursor Directory manifest**: Plugin manifest for Cursor IDE integration.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- **TS2345**: Cast Express route param to string for strict TypeScript compatibility.
|
|
37
|
+
- **npm publish 404**: Removed `--provenance` flag from release workflow (incompatible with scoped packages on granular tokens).
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- Dual transport support: MCP (stdio) and HTTP (Express) from a single codebase.
|
|
41
|
+
- SECURITY.md updated with v1.6.x in supported versions.
|
|
42
|
+
- `docs/auth.md` domain reference corrected (`www.socialneuron.com` → `socialneuron.com`).
|
|
43
|
+
|
|
5
44
|
## [1.5.2] - 2026-03-20
|
|
6
45
|
|
|
7
46
|
### Added
|
package/dist/http.js
CHANGED
|
@@ -343,7 +343,6 @@ __export(supabase_exports, {
|
|
|
343
343
|
CLOUD_SUPABASE_URL: () => CLOUD_SUPABASE_URL,
|
|
344
344
|
getAuthMode: () => getAuthMode,
|
|
345
345
|
getAuthenticatedApiKey: () => getAuthenticatedApiKey,
|
|
346
|
-
getAuthenticatedEmail: () => getAuthenticatedEmail,
|
|
347
346
|
getAuthenticatedExpiresAt: () => getAuthenticatedExpiresAt,
|
|
348
347
|
getAuthenticatedScopes: () => getAuthenticatedScopes,
|
|
349
348
|
getDefaultProjectId: () => getDefaultProjectId,
|
|
@@ -431,7 +430,6 @@ async function initializeAuth() {
|
|
|
431
430
|
_authMode = "api-key";
|
|
432
431
|
authenticatedUserId = result.userId;
|
|
433
432
|
authenticatedScopes = result.scopes && result.scopes.length > 0 ? result.scopes : ["mcp:read"];
|
|
434
|
-
authenticatedEmail = result.email || null;
|
|
435
433
|
authenticatedExpiresAt = result.expiresAt || null;
|
|
436
434
|
console.error(
|
|
437
435
|
"[MCP] Authenticated via API key (prefix: " + apiKey.substring(0, 6) + "..." + apiKey.slice(-4) + ")"
|
|
@@ -487,9 +485,6 @@ function getMcpRunId() {
|
|
|
487
485
|
function getAuthenticatedScopes() {
|
|
488
486
|
return authenticatedScopes;
|
|
489
487
|
}
|
|
490
|
-
function getAuthenticatedEmail() {
|
|
491
|
-
return authenticatedEmail;
|
|
492
|
-
}
|
|
493
488
|
function getAuthenticatedExpiresAt() {
|
|
494
489
|
return authenticatedExpiresAt;
|
|
495
490
|
}
|
|
@@ -528,7 +523,7 @@ async function logMcpToolInvocation(args) {
|
|
|
528
523
|
captureToolEvent(args).catch(() => {
|
|
529
524
|
});
|
|
530
525
|
}
|
|
531
|
-
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes,
|
|
526
|
+
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes, authenticatedExpiresAt, authenticatedApiKey, MCP_RUN_ID, CLOUD_SUPABASE_URL, CLOUD_SUPABASE_ANON_KEY, projectIdCache;
|
|
532
527
|
var init_supabase = __esm({
|
|
533
528
|
"src/lib/supabase.ts"() {
|
|
534
529
|
"use strict";
|
|
@@ -540,7 +535,6 @@ var init_supabase = __esm({
|
|
|
540
535
|
_authMode = "service-role";
|
|
541
536
|
authenticatedUserId = null;
|
|
542
537
|
authenticatedScopes = [];
|
|
543
|
-
authenticatedEmail = null;
|
|
544
538
|
authenticatedExpiresAt = null;
|
|
545
539
|
authenticatedApiKey = null;
|
|
546
540
|
MCP_RUN_ID = randomUUID();
|
|
@@ -787,6 +781,8 @@ async function callEdgeFunction(functionName, body, options) {
|
|
|
787
781
|
var CATEGORY_CONFIGS = {
|
|
788
782
|
posting: { maxTokens: 30, refillRate: 30 / 60 },
|
|
789
783
|
// 30 req/min
|
|
784
|
+
generation: { maxTokens: 20, refillRate: 20 / 60 },
|
|
785
|
+
// 20 req/min — AI content generation (mcp:write)
|
|
790
786
|
screenshot: { maxTokens: 10, refillRate: 10 / 60 },
|
|
791
787
|
// 10 req/min
|
|
792
788
|
read: { maxTokens: 60, refillRate: 60 / 60 }
|
|
@@ -1363,6 +1359,18 @@ function sanitizeDbError(error) {
|
|
|
1363
1359
|
}
|
|
1364
1360
|
return "Database operation failed. Please try again.";
|
|
1365
1361
|
}
|
|
1362
|
+
function sanitizeError(error) {
|
|
1363
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
|
|
1364
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1365
|
+
console.error("[Error]", msg);
|
|
1366
|
+
}
|
|
1367
|
+
for (const [pattern, userMessage] of ERROR_PATTERNS) {
|
|
1368
|
+
if (pattern.test(msg)) {
|
|
1369
|
+
return userMessage;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return "An unexpected error occurred. Please try again.";
|
|
1373
|
+
}
|
|
1366
1374
|
|
|
1367
1375
|
// src/tools/content.ts
|
|
1368
1376
|
init_request_context();
|
|
@@ -9449,7 +9457,7 @@ async function verifyApiKey(apiKey, supabaseUrl, supabaseAnonKey) {
|
|
|
9449
9457
|
clientId: "api-key",
|
|
9450
9458
|
scopes: data.scopes ?? ["mcp:read"],
|
|
9451
9459
|
expiresAt,
|
|
9452
|
-
extra: { userId: data.userId
|
|
9460
|
+
extra: { userId: data.userId }
|
|
9453
9461
|
};
|
|
9454
9462
|
} catch (err) {
|
|
9455
9463
|
clearTimeout(timer);
|
|
@@ -10521,7 +10529,7 @@ var cleanupInterval = setInterval(
|
|
|
10521
10529
|
);
|
|
10522
10530
|
var app = express();
|
|
10523
10531
|
app.disable("x-powered-by");
|
|
10524
|
-
app.use(express.json());
|
|
10532
|
+
app.use(express.json({ limit: "50kb" }));
|
|
10525
10533
|
app.set("trust proxy", 1);
|
|
10526
10534
|
var ipBuckets = /* @__PURE__ */ new Map();
|
|
10527
10535
|
var IP_RATE_MAX = 60;
|
|
@@ -10626,7 +10634,7 @@ async function authenticateRequest(req, res, next) {
|
|
|
10626
10634
|
};
|
|
10627
10635
|
next();
|
|
10628
10636
|
} catch (err) {
|
|
10629
|
-
const message = err
|
|
10637
|
+
const message = sanitizeError(err);
|
|
10630
10638
|
res.status(401).json({
|
|
10631
10639
|
error: "invalid_token",
|
|
10632
10640
|
error_description: message
|
|
@@ -10737,9 +10745,10 @@ app.post(
|
|
|
10737
10745
|
() => transport.handleRequest(req, res, req.body)
|
|
10738
10746
|
);
|
|
10739
10747
|
} catch (err) {
|
|
10740
|
-
const
|
|
10741
|
-
console.error(`[MCP HTTP] POST /mcp error: ${
|
|
10748
|
+
const rawMessage = err instanceof Error ? err.message : "Internal server error";
|
|
10749
|
+
console.error(`[MCP HTTP] POST /mcp error: ${rawMessage}`);
|
|
10742
10750
|
if (!res.headersSent) {
|
|
10751
|
+
const message = sanitizeError(err);
|
|
10743
10752
|
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message } });
|
|
10744
10753
|
}
|
|
10745
10754
|
}
|
package/dist/index.js
CHANGED
|
@@ -350,7 +350,6 @@ __export(supabase_exports, {
|
|
|
350
350
|
CLOUD_SUPABASE_URL: () => CLOUD_SUPABASE_URL,
|
|
351
351
|
getAuthMode: () => getAuthMode,
|
|
352
352
|
getAuthenticatedApiKey: () => getAuthenticatedApiKey,
|
|
353
|
-
getAuthenticatedEmail: () => getAuthenticatedEmail,
|
|
354
353
|
getAuthenticatedExpiresAt: () => getAuthenticatedExpiresAt,
|
|
355
354
|
getAuthenticatedScopes: () => getAuthenticatedScopes,
|
|
356
355
|
getDefaultProjectId: () => getDefaultProjectId,
|
|
@@ -438,7 +437,6 @@ async function initializeAuth() {
|
|
|
438
437
|
_authMode = "api-key";
|
|
439
438
|
authenticatedUserId = result.userId;
|
|
440
439
|
authenticatedScopes = result.scopes && result.scopes.length > 0 ? result.scopes : ["mcp:read"];
|
|
441
|
-
authenticatedEmail = result.email || null;
|
|
442
440
|
authenticatedExpiresAt = result.expiresAt || null;
|
|
443
441
|
console.error(
|
|
444
442
|
"[MCP] Authenticated via API key (prefix: " + apiKey.substring(0, 6) + "..." + apiKey.slice(-4) + ")"
|
|
@@ -494,9 +492,6 @@ function getMcpRunId() {
|
|
|
494
492
|
function getAuthenticatedScopes() {
|
|
495
493
|
return authenticatedScopes;
|
|
496
494
|
}
|
|
497
|
-
function getAuthenticatedEmail() {
|
|
498
|
-
return authenticatedEmail;
|
|
499
|
-
}
|
|
500
495
|
function getAuthenticatedExpiresAt() {
|
|
501
496
|
return authenticatedExpiresAt;
|
|
502
497
|
}
|
|
@@ -535,7 +530,7 @@ async function logMcpToolInvocation(args) {
|
|
|
535
530
|
captureToolEvent(args).catch(() => {
|
|
536
531
|
});
|
|
537
532
|
}
|
|
538
|
-
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes,
|
|
533
|
+
var SUPABASE_URL, SUPABASE_SERVICE_KEY, client2, _authMode, authenticatedUserId, authenticatedScopes, authenticatedExpiresAt, authenticatedApiKey, MCP_RUN_ID, CLOUD_SUPABASE_URL, CLOUD_SUPABASE_ANON_KEY, projectIdCache;
|
|
539
534
|
var init_supabase = __esm({
|
|
540
535
|
"src/lib/supabase.ts"() {
|
|
541
536
|
"use strict";
|
|
@@ -547,7 +542,6 @@ var init_supabase = __esm({
|
|
|
547
542
|
_authMode = "service-role";
|
|
548
543
|
authenticatedUserId = null;
|
|
549
544
|
authenticatedScopes = [];
|
|
550
|
-
authenticatedEmail = null;
|
|
551
545
|
authenticatedExpiresAt = null;
|
|
552
546
|
authenticatedApiKey = null;
|
|
553
547
|
MCP_RUN_ID = randomUUID();
|
|
@@ -2369,7 +2363,6 @@ async function handleInfo(args, asJson) {
|
|
|
2369
2363
|
const result = await validateApiKey2(apiKey);
|
|
2370
2364
|
if (result.valid) {
|
|
2371
2365
|
info.auth = {
|
|
2372
|
-
email: result.email || null,
|
|
2373
2366
|
scopes: result.scopes || [],
|
|
2374
2367
|
expiresAt: result.expiresAt || null
|
|
2375
2368
|
};
|
|
@@ -2401,7 +2394,7 @@ async function handleInfo(args, asJson) {
|
|
|
2401
2394
|
console.error("Auth: not configured");
|
|
2402
2395
|
} else if (info.auth) {
|
|
2403
2396
|
const auth = info.auth;
|
|
2404
|
-
console.error(
|
|
2397
|
+
console.error("Auth: authenticated");
|
|
2405
2398
|
console.error(`Scopes: ${auth.scopes.length > 0 ? auth.scopes.join(", ") : "none"}`);
|
|
2406
2399
|
if (auth.expiresAt) {
|
|
2407
2400
|
console.error(`Expires: ${auth.expiresAt}`);
|
|
@@ -3280,7 +3273,7 @@ async function runLoginPaste() {
|
|
|
3280
3273
|
await saveSupabaseUrl(getDefaultSupabaseUrl2());
|
|
3281
3274
|
console.error("");
|
|
3282
3275
|
console.error(" API key saved securely.");
|
|
3283
|
-
console.error(` User: ${result.
|
|
3276
|
+
console.error(` User: ${result.userId || "unknown"}`);
|
|
3284
3277
|
console.error(` Scopes: ${result.scopes?.join(", ") || "mcp:full"}`);
|
|
3285
3278
|
if (result.expiresAt) {
|
|
3286
3279
|
const daysLeft = Math.ceil(
|
|
@@ -3429,7 +3422,6 @@ async function runWhoami(options) {
|
|
|
3429
3422
|
if (asJson) {
|
|
3430
3423
|
const payload = {
|
|
3431
3424
|
ok: true,
|
|
3432
|
-
email: result.email || null,
|
|
3433
3425
|
userId: result.userId,
|
|
3434
3426
|
keyPrefix: apiKey.substring(0, 12) + "...",
|
|
3435
3427
|
scopes: result.scopes || ["mcp:full"],
|
|
@@ -3439,7 +3431,6 @@ async function runWhoami(options) {
|
|
|
3439
3431
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
3440
3432
|
} else {
|
|
3441
3433
|
console.error("");
|
|
3442
|
-
console.error(` Email: ${result.email || "(not available)"}`);
|
|
3443
3434
|
console.error(` User ID: ${result.userId}`);
|
|
3444
3435
|
console.error(` Key: ${apiKey.substring(0, 12)}...`);
|
|
3445
3436
|
console.error(` Scopes: ${result.scopes?.join(", ") || "mcp:full"}`);
|
|
@@ -3482,7 +3473,7 @@ async function runHealthCheck(options) {
|
|
|
3482
3473
|
checks.push({
|
|
3483
3474
|
name: "Key Valid",
|
|
3484
3475
|
ok: true,
|
|
3485
|
-
detail: `User: ${result.
|
|
3476
|
+
detail: `User: ${result.userId}`
|
|
3486
3477
|
});
|
|
3487
3478
|
checks.push({
|
|
3488
3479
|
name: "Scopes",
|
|
@@ -3595,7 +3586,7 @@ async function runRepl() {
|
|
|
3595
3586
|
Social Neuron CLI v${MCP_VERSION} \u2014 Interactive Mode
|
|
3596
3587
|
`);
|
|
3597
3588
|
process.stderr.write("Type a command, .help for help, or .exit to quit.\n\n");
|
|
3598
|
-
let
|
|
3589
|
+
let authUserId = null;
|
|
3599
3590
|
try {
|
|
3600
3591
|
const { loadApiKey: loadApiKey2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
|
|
3601
3592
|
const { validateApiKey: validateApiKey2 } = await Promise.resolve().then(() => (init_api_keys(), api_keys_exports));
|
|
@@ -3603,8 +3594,8 @@ Social Neuron CLI v${MCP_VERSION} \u2014 Interactive Mode
|
|
|
3603
3594
|
if (key) {
|
|
3604
3595
|
const result = await validateApiKey2(key);
|
|
3605
3596
|
if (result.valid) {
|
|
3606
|
-
|
|
3607
|
-
process.stderr.write(` Authenticated
|
|
3597
|
+
authUserId = result.userId || null;
|
|
3598
|
+
process.stderr.write(` Authenticated (user: ${authUserId || "unknown"})
|
|
3608
3599
|
|
|
3609
3600
|
`);
|
|
3610
3601
|
}
|
|
@@ -3638,7 +3629,7 @@ Social Neuron CLI v${MCP_VERSION} \u2014 Interactive Mode
|
|
|
3638
3629
|
".exit",
|
|
3639
3630
|
".clear"
|
|
3640
3631
|
];
|
|
3641
|
-
const promptStr =
|
|
3632
|
+
const promptStr = authUserId ? `sn[${authUserId.substring(0, 8)}]> ` : "sn> ";
|
|
3642
3633
|
const rl = createInterface2({
|
|
3643
3634
|
input: process.stdin,
|
|
3644
3635
|
output: process.stderr,
|
|
@@ -3829,6 +3820,8 @@ import { z } from "zod";
|
|
|
3829
3820
|
var CATEGORY_CONFIGS = {
|
|
3830
3821
|
posting: { maxTokens: 30, refillRate: 30 / 60 },
|
|
3831
3822
|
// 30 req/min
|
|
3823
|
+
generation: { maxTokens: 20, refillRate: 20 / 60 },
|
|
3824
|
+
// 20 req/min — AI content generation (mcp:write)
|
|
3832
3825
|
screenshot: { maxTokens: 10, refillRate: 10 / 60 },
|
|
3833
3826
|
// 10 req/min
|
|
3834
3827
|
read: { maxTokens: 60, refillRate: 60 / 60 }
|