@socketsecurity/mcp 0.0.11 → 0.0.12
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 +11 -2
- package/index.js +21 -154
- package/mock-client/http-client.js +10 -28
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ The easiest way to get started is to use our public Socket MCP server. **No API
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
[](https://vscode.dev/redirect/mcp/install?name=socket-mcp&config={"url":"https://mcp.socket.dev/","type":"http"})
|
|
28
|
-
[](https://cursor.com/install-mcp?name=socket-mcp&config=
|
|
28
|
+
[](https://cursor.com/en/install-mcp?name=socket-mcp&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vbWNwLnNvY2tldC5kZXYvIn0%3D)
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
<details><summary><b>Manual Installation Instructions & more MCP Clients</b></summary>
|
|
@@ -149,7 +149,7 @@ For local deployment, you have two options:
|
|
|
149
149
|
Click a button below to install the self-hosted stdio server in your favorite AI assistant.
|
|
150
150
|
|
|
151
151
|
[](https://vscode.dev/redirect/mcp/install?name=socket-mcp&config={"command":"npx","args":["@socketsecurity/mcp@latest"],"type":"stdio"})
|
|
152
|
-
[](https://cursor.com/install-mcp?name=socket-mcp
|
|
152
|
+
[](https://cursor.com/en/install-mcp?name=socket-mcp&config=eyJjb21tYW5kIjoibnB4IEBzb2NrZXRzZWN1cml0eS9tY3BAbGF0ZXN0IiwiZW52Ijp7IlNPQ0tFVF9BUElfS0VZIjoieW91ci1hcGkta2V5LWhlcmUifX0%3D)
|
|
153
153
|
|
|
154
154
|
Claude Code (stdio mode) can be set up with the following command:
|
|
155
155
|
|
|
@@ -374,3 +374,12 @@ MCP_HTTP_MODE=true SOCKET_API_KEY=your_api_key_here node --experimental-strip-ty
|
|
|
374
374
|
- 📖 [Socket Documentation](https://docs.socket.dev)
|
|
375
375
|
- 🐛 [Report Issues](https://github.com/SocketDev/socket-mcp/issues)
|
|
376
376
|
- 💬 [Community Support](https://github.com/SocketDev/socket-mcp/discussions)
|
|
377
|
+
|
|
378
|
+
<br/>
|
|
379
|
+
<div align="center">
|
|
380
|
+
<picture>
|
|
381
|
+
<source media="(prefers-color-scheme: dark)" srcset="logo-white.png">
|
|
382
|
+
<source media="(prefers-color-scheme: light)" srcset="logo-black.png">
|
|
383
|
+
<img width="324" height="108" alt="Socket Logo" src="logo-black.png">
|
|
384
|
+
</picture>
|
|
385
|
+
</div>
|
package/index.js
CHANGED
|
@@ -9,8 +9,6 @@ import { join } from 'path';
|
|
|
9
9
|
import { readFileSync } from 'fs';
|
|
10
10
|
import { tmpdir } from 'os';
|
|
11
11
|
import { createServer } from 'http';
|
|
12
|
-
import { randomUUID } from 'crypto';
|
|
13
|
-
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
14
12
|
const __dirname = import.meta.dirname;
|
|
15
13
|
const packageJson = JSON.parse(readFileSync(join(__dirname, './package.json'), 'utf8'));
|
|
16
14
|
const VERSION = packageJson.version || '0.0.1';
|
|
@@ -52,7 +50,6 @@ async function getApiKeyInteractively() {
|
|
|
52
50
|
return apiKey;
|
|
53
51
|
}
|
|
54
52
|
let SOCKET_API_KEY = process.env['SOCKET_API_KEY'] || '';
|
|
55
|
-
const transports = {};
|
|
56
53
|
const server = new McpServer({
|
|
57
54
|
name: 'socket',
|
|
58
55
|
version: VERSION,
|
|
@@ -204,6 +201,7 @@ if (!SOCKET_API_KEY) {
|
|
|
204
201
|
}
|
|
205
202
|
if (useHttp) {
|
|
206
203
|
logger.info(`Starting HTTP server on port ${port}`);
|
|
204
|
+
let httpTransport = null;
|
|
207
205
|
const httpServer = createServer(async (req, res) => {
|
|
208
206
|
const origin = req.headers.origin;
|
|
209
207
|
const allowedOrigins = [
|
|
@@ -229,9 +227,8 @@ if (useHttp) {
|
|
|
229
227
|
else {
|
|
230
228
|
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
|
|
231
229
|
}
|
|
232
|
-
res.setHeader('Access-Control-Allow-Methods', '
|
|
233
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,
|
|
234
|
-
res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id');
|
|
230
|
+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
231
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
|
|
235
232
|
if (req.method === 'OPTIONS') {
|
|
236
233
|
res.writeHead(200);
|
|
237
234
|
res.end();
|
|
@@ -263,79 +260,34 @@ if (useHttp) {
|
|
|
263
260
|
}
|
|
264
261
|
if (url.pathname === '/') {
|
|
265
262
|
if (req.method === 'POST') {
|
|
266
|
-
const acceptHeader = req.headers.accept;
|
|
267
|
-
if (!acceptHeader || (!acceptHeader.includes('application/json') && !acceptHeader.includes('text/event-stream'))) {
|
|
268
|
-
logger.warn(`Invalid Accept header: ${acceptHeader}`);
|
|
269
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
270
|
-
res.end(JSON.stringify({
|
|
271
|
-
jsonrpc: '2.0',
|
|
272
|
-
error: { code: -32000, message: 'Bad Request: Accept header must include application/json or text/event-stream' },
|
|
273
|
-
id: null
|
|
274
|
-
}));
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
263
|
let body = '';
|
|
278
264
|
req.on('data', chunk => (body += chunk));
|
|
279
265
|
req.on('end', async () => {
|
|
280
266
|
try {
|
|
281
267
|
const jsonData = JSON.parse(body);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
res.end(JSON.stringify({
|
|
287
|
-
jsonrpc: '2.0',
|
|
288
|
-
error: { code: -32000, message: 'Bad Request: Session ID must contain only visible ASCII characters' },
|
|
289
|
-
id: jsonData.id || null
|
|
290
|
-
}));
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
let transport;
|
|
294
|
-
if (sessionId && transports[sessionId]) {
|
|
295
|
-
transport = transports[sessionId];
|
|
296
|
-
}
|
|
297
|
-
else if (!sessionId) {
|
|
298
|
-
const newSessionId = randomUUID();
|
|
299
|
-
const isInit = isInitializeRequest(jsonData);
|
|
300
|
-
if (isInit) {
|
|
301
|
-
logger.info(`Creating new session for initialize request: ${newSessionId}`);
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
logger.warn(`Creating fallback session for non-initialize request: ${newSessionId}`);
|
|
305
|
-
}
|
|
306
|
-
transport = new StreamableHTTPServerTransport({
|
|
307
|
-
sessionIdGenerator: () => newSessionId,
|
|
308
|
-
onsessioninitialized: (id) => {
|
|
309
|
-
transports[id] = transport;
|
|
310
|
-
logger.info(`Session initialized: ${id}`);
|
|
311
|
-
res.setHeader('mcp-session-id', id);
|
|
268
|
+
if (jsonData && jsonData.method === 'initialize') {
|
|
269
|
+
if (httpTransport) {
|
|
270
|
+
try {
|
|
271
|
+
httpTransport.close();
|
|
312
272
|
}
|
|
273
|
+
catch { }
|
|
274
|
+
}
|
|
275
|
+
httpTransport = new StreamableHTTPServerTransport({
|
|
276
|
+
sessionIdGenerator: undefined,
|
|
277
|
+
enableJsonResponse: true
|
|
313
278
|
});
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (sid && transports[sid]) {
|
|
317
|
-
delete transports[sid];
|
|
318
|
-
logger.info(`Session closed: ${sid}`);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
await server.connect(transport);
|
|
322
|
-
await transport.handleRequest(req, res, jsonData);
|
|
279
|
+
await server.connect(httpTransport);
|
|
280
|
+
await httpTransport.handleRequest(req, res, jsonData);
|
|
323
281
|
return;
|
|
324
282
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
code: -32000,
|
|
332
|
-
message: 'Bad Request: Invalid session ID. Please initialize a new session first.'
|
|
333
|
-
},
|
|
334
|
-
id: jsonData.id || null
|
|
335
|
-
}));
|
|
336
|
-
return;
|
|
283
|
+
if (!httpTransport) {
|
|
284
|
+
httpTransport = new StreamableHTTPServerTransport({
|
|
285
|
+
sessionIdGenerator: undefined,
|
|
286
|
+
enableJsonResponse: true
|
|
287
|
+
});
|
|
288
|
+
await server.connect(httpTransport);
|
|
337
289
|
}
|
|
338
|
-
await
|
|
290
|
+
await httpTransport.handleRequest(req, res, jsonData);
|
|
339
291
|
}
|
|
340
292
|
catch (error) {
|
|
341
293
|
logger.error(`Error processing POST request: ${error}`);
|
|
@@ -350,91 +302,6 @@ if (useHttp) {
|
|
|
350
302
|
}
|
|
351
303
|
});
|
|
352
304
|
}
|
|
353
|
-
else if (req.method === 'GET') {
|
|
354
|
-
const acceptHeader = req.headers.accept;
|
|
355
|
-
if (!acceptHeader || !acceptHeader.includes('text/event-stream')) {
|
|
356
|
-
logger.warn(`GET request without text/event-stream Accept header: ${acceptHeader}`);
|
|
357
|
-
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
358
|
-
res.end(JSON.stringify({
|
|
359
|
-
jsonrpc: '2.0',
|
|
360
|
-
error: { code: -32000, message: 'Method Not Allowed: GET requires Accept: text/event-stream' },
|
|
361
|
-
id: null
|
|
362
|
-
}));
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
const sessionId = req.headers['mcp-session-id'];
|
|
366
|
-
if (sessionId && !/^[\x21-\x7E]+$/.test(sessionId)) {
|
|
367
|
-
logger.warn(`Invalid session ID format in GET request: ${sessionId}`);
|
|
368
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
369
|
-
res.end(JSON.stringify({
|
|
370
|
-
jsonrpc: '2.0',
|
|
371
|
-
error: { code: -32000, message: 'Bad Request: Session ID must contain only visible ASCII characters' },
|
|
372
|
-
id: null
|
|
373
|
-
}));
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
if (!sessionId || !transports[sessionId]) {
|
|
377
|
-
logger.warn(`SSE request with invalid session ID: ${sessionId}`);
|
|
378
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
379
|
-
res.end(JSON.stringify({
|
|
380
|
-
jsonrpc: '2.0',
|
|
381
|
-
error: { code: -32000, message: 'Bad Request: Invalid or missing session ID for SSE stream' },
|
|
382
|
-
id: null
|
|
383
|
-
}));
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
const lastEventId = req.headers['last-event-id'];
|
|
387
|
-
if (lastEventId) {
|
|
388
|
-
logger.info(`SSE resumability requested with Last-Event-ID: ${lastEventId}`);
|
|
389
|
-
}
|
|
390
|
-
logger.info(`Opening SSE stream for session: ${sessionId}`);
|
|
391
|
-
req.socket?.setTimeout(0);
|
|
392
|
-
req.socket?.setKeepAlive(true, 30000);
|
|
393
|
-
let streamClosed = false;
|
|
394
|
-
req.on('close', () => {
|
|
395
|
-
streamClosed = true;
|
|
396
|
-
logger.info(`Client disconnected SSE stream for session: ${sessionId}`);
|
|
397
|
-
});
|
|
398
|
-
req.on('aborted', () => {
|
|
399
|
-
streamClosed = true;
|
|
400
|
-
logger.info(`Client aborted SSE stream for session: ${sessionId}`);
|
|
401
|
-
});
|
|
402
|
-
const transport = transports[sessionId];
|
|
403
|
-
try {
|
|
404
|
-
await transport.handleRequest(req, res);
|
|
405
|
-
if (!streamClosed && !res.destroyed) {
|
|
406
|
-
logger.info(`Transport completed, maintaining SSE stream for session: ${sessionId}`);
|
|
407
|
-
const heartbeat = setInterval(() => {
|
|
408
|
-
if (streamClosed || res.destroyed) {
|
|
409
|
-
clearInterval(heartbeat);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
try {
|
|
413
|
-
res.write(': heartbeat\n\n');
|
|
414
|
-
}
|
|
415
|
-
catch (error) {
|
|
416
|
-
logger.error(error, `Heartbeat error for session ${sessionId}:`);
|
|
417
|
-
clearInterval(heartbeat);
|
|
418
|
-
}
|
|
419
|
-
}, 30000);
|
|
420
|
-
req.on('close', () => clearInterval(heartbeat));
|
|
421
|
-
res.on('close', () => clearInterval(heartbeat));
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
catch (error) {
|
|
425
|
-
logger.error(error, `SSE transport error for session ${sessionId}:`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
else if (req.method === 'DELETE') {
|
|
429
|
-
const sessionId = req.headers['mcp-session-id'];
|
|
430
|
-
if (!sessionId || !transports[sessionId]) {
|
|
431
|
-
res.writeHead(400);
|
|
432
|
-
res.end('Invalid or missing session ID');
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
const transport = transports[sessionId];
|
|
436
|
-
await transport.handleRequest(req, res);
|
|
437
|
-
}
|
|
438
305
|
else {
|
|
439
306
|
res.writeHead(405);
|
|
440
307
|
res.end('Method not allowed');
|
|
@@ -16,10 +16,8 @@ async function parseResponse(response) {
|
|
|
16
16
|
}
|
|
17
17
|
async function testHTTPMode() {
|
|
18
18
|
const baseUrl = (process.env['MCP_URL'] || 'http://localhost:3000').replace(/\/$/, '');
|
|
19
|
-
const sessionId = `test-session-${Date.now()}`;
|
|
20
19
|
console.log('Testing Socket MCP in HTTP mode...');
|
|
21
20
|
console.log(`Server URL: ${baseUrl}`);
|
|
22
|
-
console.log(`Session ID: ${sessionId}`);
|
|
23
21
|
try {
|
|
24
22
|
console.log('\n1. Initializing connection...');
|
|
25
23
|
const initRequest = {
|
|
@@ -46,9 +44,7 @@ async function testHTTPMode() {
|
|
|
46
44
|
});
|
|
47
45
|
const initResult = await parseResponse(initResponse);
|
|
48
46
|
console.log('Initialize response:', JSON.stringify(initResult, null, 2));
|
|
49
|
-
|
|
50
|
-
const actualSessionId = serverSessionId || sessionId;
|
|
51
|
-
console.log('Session ID:', actualSessionId);
|
|
47
|
+
console.log('Initialized (stateless)');
|
|
52
48
|
console.log('\n2. Listing available tools...');
|
|
53
49
|
const toolsRequest = {
|
|
54
50
|
jsonrpc: '2.0',
|
|
@@ -60,13 +56,18 @@ async function testHTTPMode() {
|
|
|
60
56
|
method: 'POST',
|
|
61
57
|
headers: {
|
|
62
58
|
'Content-Type': 'application/json',
|
|
63
|
-
Accept: 'application/json, text/event-stream'
|
|
64
|
-
'mcp-session-id': actualSessionId
|
|
59
|
+
Accept: 'application/json, text/event-stream'
|
|
65
60
|
},
|
|
66
61
|
body: JSON.stringify(toolsRequest)
|
|
67
62
|
});
|
|
68
63
|
const toolsResult = await parseResponse(toolsResponse);
|
|
69
64
|
console.log('Available tools:', JSON.stringify(toolsResult, null, 2));
|
|
65
|
+
if (!toolsResult ||
|
|
66
|
+
!toolsResult.result ||
|
|
67
|
+
!Array.isArray(toolsResult.result.tools) ||
|
|
68
|
+
!toolsResult.result.tools.some((tool) => tool.name === 'depscore')) {
|
|
69
|
+
throw new Error('depscore tool not found in available tools');
|
|
70
|
+
}
|
|
70
71
|
console.log('\n3. Calling depscore tool...');
|
|
71
72
|
const depscoreRequest = {
|
|
72
73
|
jsonrpc: '2.0',
|
|
@@ -87,32 +88,13 @@ async function testHTTPMode() {
|
|
|
87
88
|
method: 'POST',
|
|
88
89
|
headers: {
|
|
89
90
|
'Content-Type': 'application/json',
|
|
90
|
-
Accept: 'application/json, text/event-stream'
|
|
91
|
-
'mcp-session-id': actualSessionId
|
|
91
|
+
Accept: 'application/json, text/event-stream'
|
|
92
92
|
},
|
|
93
93
|
body: JSON.stringify(depscoreRequest)
|
|
94
94
|
});
|
|
95
95
|
const depscoreResult = await parseResponse(depscoreResponse);
|
|
96
96
|
console.log('Depscore result:', JSON.stringify(depscoreResult, null, 2));
|
|
97
|
-
console.log('\n4.
|
|
98
|
-
const sseResponse = await fetch(`${baseUrl}/`, {
|
|
99
|
-
method: 'GET',
|
|
100
|
-
headers: {
|
|
101
|
-
'mcp-session-id': actualSessionId,
|
|
102
|
-
Accept: 'text/event-stream'
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
if (sseResponse.ok) {
|
|
106
|
-
console.log('SSE stream connected successfully');
|
|
107
|
-
}
|
|
108
|
-
console.log('\n5. Cleaning up session...');
|
|
109
|
-
const cleanupResponse = await fetch(`${baseUrl}/`, {
|
|
110
|
-
method: 'DELETE',
|
|
111
|
-
headers: {
|
|
112
|
-
'mcp-session-id': actualSessionId
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
console.log('Session cleanup:', cleanupResponse.status === 200 ? 'Success' : 'Failed');
|
|
97
|
+
console.log('\n4. HTTP mode test complete (no sessions)');
|
|
116
98
|
}
|
|
117
99
|
catch (error) {
|
|
118
100
|
console.error('Error:', error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketsecurity/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"bin": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"debug-stdio": "node --experimental-strip-types ./mock-client/debug-client.ts",
|
|
29
29
|
"debug-sdk": "node --experimental-strip-types ./mock-client/stdio-client.ts",
|
|
30
30
|
"debug-http": "node --experimental-strip-types ./mock-client/http-client.ts",
|
|
31
|
-
"server-stdio": "SOCKET_API_KEY=${SOCKET_API_KEY} --experimental-strip-types ./index.ts",
|
|
32
|
-
"server-http": "MCP_HTTP_MODE=true SOCKET_API_KEY=${SOCKET_API_KEY} ./
|
|
31
|
+
"server-stdio": "SOCKET_API_KEY=${SOCKET_API_KEY} node --experimental-strip-types ./index.ts",
|
|
32
|
+
"server-http": "MCP_HTTP_MODE=true SOCKET_API_KEY=${SOCKET_API_KEY} node --experimental-strip-types ./index.ts"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [],
|
|
35
35
|
"files": [
|
|
@@ -46,19 +46,19 @@
|
|
|
46
46
|
"url": "https://github.com/SocketDev/socket-mcp"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.18.0",
|
|
50
50
|
"pino": "^9.7.0",
|
|
51
51
|
"pino-pretty": "^13.0.0",
|
|
52
52
|
"semver": "^7.7.2",
|
|
53
53
|
"zod": "^3.24.4"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"neostandard": "^0.12.0",
|
|
57
56
|
"@anthropic-ai/dxt": "^0.2.0",
|
|
58
57
|
"@types/node": "^24.0.7",
|
|
59
58
|
"@types/semver": "^7.7.0",
|
|
60
59
|
"@types/triple-beam": "^1.3.5",
|
|
61
60
|
"c8": "^10.0.0",
|
|
61
|
+
"neostandard": "^0.12.0",
|
|
62
62
|
"npm-run-all2": "^8.0.1",
|
|
63
63
|
"typescript": "~5.9.2"
|
|
64
64
|
}
|