@socketsecurity/mcp
Advanced tools
+21
-154
@@ -12,4 +12,2 @@ #!/usr/bin/env -S node --experimental-strip-types | ||
| import { createServer } from 'http'; | ||
| import { randomUUID } from 'crypto'; | ||
| import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; | ||
| const __dirname = import.meta.dirname; | ||
@@ -56,3 +54,2 @@ const packageJson = JSON.parse(readFileSync(join(__dirname, './package.json'), 'utf8')); | ||
| let SOCKET_API_KEY = process.env['SOCKET_API_KEY'] || ''; | ||
| const transports = {}; | ||
| const server = new McpServer({ | ||
@@ -209,2 +206,3 @@ name: 'socket', | ||
| logger.info(`Starting HTTP server on port ${port}`); | ||
| let httpTransport = null; | ||
| const httpServer = createServer(async (req, res) => { | ||
@@ -235,5 +233,4 @@ const origin = req.headers.origin; | ||
| } | ||
| res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS'); | ||
| res.setHeader('Access-Control-Allow-Headers', 'Content-Type, mcp-session-id, Accept, Last-Event-ID'); | ||
| res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id'); | ||
| res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); | ||
| res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept'); | ||
| if (req.method === 'OPTIONS') { | ||
@@ -270,13 +267,2 @@ res.writeHead(200); | ||
| if (req.method === 'POST') { | ||
| const acceptHeader = req.headers.accept; | ||
| if (!acceptHeader || (!acceptHeader.includes('application/json') && !acceptHeader.includes('text/event-stream'))) { | ||
| logger.warn(`Invalid Accept header: ${acceptHeader}`); | ||
| res.writeHead(400, { 'Content-Type': 'application/json' }); | ||
| res.end(JSON.stringify({ | ||
| jsonrpc: '2.0', | ||
| error: { code: -32000, message: 'Bad Request: Accept header must include application/json or text/event-stream' }, | ||
| id: null | ||
| })); | ||
| return; | ||
| } | ||
| let body = ''; | ||
@@ -287,59 +273,25 @@ req.on('data', chunk => (body += chunk)); | ||
| const jsonData = JSON.parse(body); | ||
| const sessionId = req.headers['mcp-session-id']; | ||
| if (sessionId && !/^[\x21-\x7E]+$/.test(sessionId)) { | ||
| logger.warn(`Invalid session ID format: ${sessionId}`); | ||
| res.writeHead(400, { 'Content-Type': 'application/json' }); | ||
| res.end(JSON.stringify({ | ||
| jsonrpc: '2.0', | ||
| error: { code: -32000, message: 'Bad Request: Session ID must contain only visible ASCII characters' }, | ||
| id: jsonData.id || null | ||
| })); | ||
| return; | ||
| } | ||
| let transport; | ||
| if (sessionId && transports[sessionId]) { | ||
| transport = transports[sessionId]; | ||
| } | ||
| else if (!sessionId) { | ||
| const newSessionId = randomUUID(); | ||
| const isInit = isInitializeRequest(jsonData); | ||
| if (isInit) { | ||
| logger.info(`Creating new session for initialize request: ${newSessionId}`); | ||
| if (jsonData && jsonData.method === 'initialize') { | ||
| if (httpTransport) { | ||
| try { | ||
| httpTransport.close(); | ||
| } | ||
| catch { } | ||
| } | ||
| else { | ||
| logger.warn(`Creating fallback session for non-initialize request: ${newSessionId}`); | ||
| } | ||
| transport = new StreamableHTTPServerTransport({ | ||
| sessionIdGenerator: () => newSessionId, | ||
| onsessioninitialized: (id) => { | ||
| transports[id] = transport; | ||
| logger.info(`Session initialized: ${id}`); | ||
| res.setHeader('mcp-session-id', id); | ||
| } | ||
| httpTransport = new StreamableHTTPServerTransport({ | ||
| sessionIdGenerator: undefined, | ||
| enableJsonResponse: true | ||
| }); | ||
| transport.onclose = () => { | ||
| const sid = transport.sessionId; | ||
| if (sid && transports[sid]) { | ||
| delete transports[sid]; | ||
| logger.info(`Session closed: ${sid}`); | ||
| } | ||
| }; | ||
| await server.connect(transport); | ||
| await transport.handleRequest(req, res, jsonData); | ||
| await server.connect(httpTransport); | ||
| await httpTransport.handleRequest(req, res, jsonData); | ||
| return; | ||
| } | ||
| else { | ||
| logger.error(`Invalid session ID: ${sessionId}. Active sessions count: ${Object.keys(transports).length}`); | ||
| res.writeHead(400); | ||
| res.end(JSON.stringify({ | ||
| jsonrpc: '2.0', | ||
| error: { | ||
| code: -32000, | ||
| message: 'Bad Request: Invalid session ID. Please initialize a new session first.' | ||
| }, | ||
| id: jsonData.id || null | ||
| })); | ||
| return; | ||
| if (!httpTransport) { | ||
| httpTransport = new StreamableHTTPServerTransport({ | ||
| sessionIdGenerator: undefined, | ||
| enableJsonResponse: true | ||
| }); | ||
| await server.connect(httpTransport); | ||
| } | ||
| await transport.handleRequest(req, res, jsonData); | ||
| await httpTransport.handleRequest(req, res, jsonData); | ||
| } | ||
@@ -359,87 +311,2 @@ catch (error) { | ||
| } | ||
| else if (req.method === 'GET') { | ||
| const acceptHeader = req.headers.accept; | ||
| if (!acceptHeader || !acceptHeader.includes('text/event-stream')) { | ||
| logger.warn(`GET request without text/event-stream Accept header: ${acceptHeader}`); | ||
| res.writeHead(405, { 'Content-Type': 'application/json' }); | ||
| res.end(JSON.stringify({ | ||
| jsonrpc: '2.0', | ||
| error: { code: -32000, message: 'Method Not Allowed: GET requires Accept: text/event-stream' }, | ||
| id: null | ||
| })); | ||
| return; | ||
| } | ||
| const sessionId = req.headers['mcp-session-id']; | ||
| if (sessionId && !/^[\x21-\x7E]+$/.test(sessionId)) { | ||
| logger.warn(`Invalid session ID format in GET request: ${sessionId}`); | ||
| res.writeHead(400, { 'Content-Type': 'application/json' }); | ||
| res.end(JSON.stringify({ | ||
| jsonrpc: '2.0', | ||
| error: { code: -32000, message: 'Bad Request: Session ID must contain only visible ASCII characters' }, | ||
| id: null | ||
| })); | ||
| return; | ||
| } | ||
| if (!sessionId || !transports[sessionId]) { | ||
| logger.warn(`SSE request with invalid session ID: ${sessionId}`); | ||
| res.writeHead(400, { 'Content-Type': 'application/json' }); | ||
| res.end(JSON.stringify({ | ||
| jsonrpc: '2.0', | ||
| error: { code: -32000, message: 'Bad Request: Invalid or missing session ID for SSE stream' }, | ||
| id: null | ||
| })); | ||
| return; | ||
| } | ||
| const lastEventId = req.headers['last-event-id']; | ||
| if (lastEventId) { | ||
| logger.info(`SSE resumability requested with Last-Event-ID: ${lastEventId}`); | ||
| } | ||
| logger.info(`Opening SSE stream for session: ${sessionId}`); | ||
| req.socket?.setTimeout(0); | ||
| req.socket?.setKeepAlive(true, 30000); | ||
| let streamClosed = false; | ||
| req.on('close', () => { | ||
| streamClosed = true; | ||
| logger.info(`Client disconnected SSE stream for session: ${sessionId}`); | ||
| }); | ||
| req.on('aborted', () => { | ||
| streamClosed = true; | ||
| logger.info(`Client aborted SSE stream for session: ${sessionId}`); | ||
| }); | ||
| const transport = transports[sessionId]; | ||
| try { | ||
| await transport.handleRequest(req, res); | ||
| if (!streamClosed && !res.destroyed) { | ||
| logger.info(`Transport completed, maintaining SSE stream for session: ${sessionId}`); | ||
| const heartbeat = setInterval(() => { | ||
| if (streamClosed || res.destroyed) { | ||
| clearInterval(heartbeat); | ||
| return; | ||
| } | ||
| try { | ||
| res.write(': heartbeat\n\n'); | ||
| } | ||
| catch (error) { | ||
| logger.error(error, `Heartbeat error for session ${sessionId}:`); | ||
| clearInterval(heartbeat); | ||
| } | ||
| }, 30000); | ||
| req.on('close', () => clearInterval(heartbeat)); | ||
| res.on('close', () => clearInterval(heartbeat)); | ||
| } | ||
| } | ||
| catch (error) { | ||
| logger.error(error, `SSE transport error for session ${sessionId}:`); | ||
| } | ||
| } | ||
| else if (req.method === 'DELETE') { | ||
| const sessionId = req.headers['mcp-session-id']; | ||
| if (!sessionId || !transports[sessionId]) { | ||
| res.writeHead(400); | ||
| res.end('Invalid or missing session ID'); | ||
| return; | ||
| } | ||
| const transport = transports[sessionId]; | ||
| await transport.handleRequest(req, res); | ||
| } | ||
| else { | ||
@@ -446,0 +313,0 @@ res.writeHead(405); |
@@ -19,6 +19,4 @@ #!/usr/bin/env node --experimental-strip-types | ||
| const baseUrl = (process.env['MCP_URL'] || 'http://localhost:3000').replace(/\/$/, ''); | ||
| const sessionId = `test-session-${Date.now()}`; | ||
| console.log('Testing Socket MCP in HTTP mode...'); | ||
| console.log(`Server URL: ${baseUrl}`); | ||
| console.log(`Session ID: ${sessionId}`); | ||
| try { | ||
@@ -50,5 +48,3 @@ console.log('\n1. Initializing connection...'); | ||
| console.log('Initialize response:', JSON.stringify(initResult, null, 2)); | ||
| const serverSessionId = initResponse.headers.get('mcp-session-id'); | ||
| const actualSessionId = serverSessionId || sessionId; | ||
| console.log('Session ID:', actualSessionId); | ||
| console.log('Initialized (stateless)'); | ||
| console.log('\n2. Listing available tools...'); | ||
@@ -65,4 +61,3 @@ const toolsRequest = { | ||
| 'Content-Type': 'application/json', | ||
| Accept: 'application/json, text/event-stream', | ||
| 'mcp-session-id': actualSessionId | ||
| Accept: 'application/json, text/event-stream' | ||
| }, | ||
@@ -73,2 +68,8 @@ body: JSON.stringify(toolsRequest) | ||
| console.log('Available tools:', JSON.stringify(toolsResult, null, 2)); | ||
| if (!toolsResult || | ||
| !toolsResult.result || | ||
| !Array.isArray(toolsResult.result.tools) || | ||
| !toolsResult.result.tools.some((tool) => tool.name === 'depscore')) { | ||
| throw new Error('depscore tool not found in available tools'); | ||
| } | ||
| console.log('\n3. Calling depscore tool...'); | ||
@@ -94,4 +95,3 @@ const depscoreRequest = { | ||
| 'Content-Type': 'application/json', | ||
| Accept: 'application/json, text/event-stream', | ||
| 'mcp-session-id': actualSessionId | ||
| Accept: 'application/json, text/event-stream' | ||
| }, | ||
@@ -102,21 +102,3 @@ body: JSON.stringify(depscoreRequest) | ||
| console.log('Depscore result:', JSON.stringify(depscoreResult, null, 2)); | ||
| console.log('\n4. Testing SSE stream connection...'); | ||
| const sseResponse = await fetch(`${baseUrl}/`, { | ||
| method: 'GET', | ||
| headers: { | ||
| 'mcp-session-id': actualSessionId, | ||
| Accept: 'text/event-stream' | ||
| } | ||
| }); | ||
| if (sseResponse.ok) { | ||
| console.log('SSE stream connected successfully'); | ||
| } | ||
| console.log('\n5. Cleaning up session...'); | ||
| const cleanupResponse = await fetch(`${baseUrl}/`, { | ||
| method: 'DELETE', | ||
| headers: { | ||
| 'mcp-session-id': actualSessionId | ||
| } | ||
| }); | ||
| console.log('Session cleanup:', cleanupResponse.status === 200 ? 'Success' : 'Failed'); | ||
| console.log('\n4. HTTP mode test complete (no sessions)'); | ||
| } | ||
@@ -123,0 +105,0 @@ catch (error) { |
+5
-5
| { | ||
| "name": "@socketsecurity/mcp", | ||
| "version": "0.0.11", | ||
| "version": "0.0.12", | ||
| "type": "module", | ||
@@ -31,4 +31,4 @@ "main": "./index.js", | ||
| "debug-http": "node --experimental-strip-types ./mock-client/http-client.ts", | ||
| "server-stdio": "SOCKET_API_KEY=${SOCKET_API_KEY} --experimental-strip-types ./index.ts", | ||
| "server-http": "MCP_HTTP_MODE=true SOCKET_API_KEY=${SOCKET_API_KEY} ./build/index.js" | ||
| "server-stdio": "SOCKET_API_KEY=${SOCKET_API_KEY} node --experimental-strip-types ./index.ts", | ||
| "server-http": "MCP_HTTP_MODE=true SOCKET_API_KEY=${SOCKET_API_KEY} node --experimental-strip-types ./index.ts" | ||
| }, | ||
@@ -50,3 +50,3 @@ "keywords": [], | ||
| "dependencies": { | ||
| "@modelcontextprotocol/sdk": "^1.11.3", | ||
| "@modelcontextprotocol/sdk": "^1.18.0", | ||
| "pino": "^9.7.0", | ||
@@ -58,3 +58,2 @@ "pino-pretty": "^13.0.0", | ||
| "devDependencies": { | ||
| "neostandard": "^0.12.0", | ||
| "@anthropic-ai/dxt": "^0.2.0", | ||
@@ -65,2 +64,3 @@ "@types/node": "^24.0.7", | ||
| "c8": "^10.0.0", | ||
| "neostandard": "^0.12.0", | ||
| "npm-run-all2": "^8.0.1", | ||
@@ -67,0 +67,0 @@ "typescript": "~5.9.2" |
+11
-2
@@ -28,3 +28,3 @@ # Socket MCP Server | ||
| [](https://vscode.dev/redirect/mcp/install?name=socket-mcp&config={"url":"https://mcp.socket.dev/","type":"http"}) | ||
| [](https://cursor.com/install-mcp?name=socket-mcp&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vbWNwLnNvY2tldC5kZXYifQ%3D%3D) | ||
| [](https://cursor.com/en/install-mcp?name=socket-mcp&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vbWNwLnNvY2tldC5kZXYvIn0%3D) | ||
@@ -153,3 +153,3 @@ | ||
| [](https://vscode.dev/redirect/mcp/install?name=socket-mcp&config={"command":"npx","args":["@socketsecurity/mcp@latest"],"type":"stdio"}) | ||
| [](https://cursor.com/install-mcp?name=socket-mcp-stdio&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyJAc29ja2V0c2VjdXJpdHkvbWNwQGxhdGVzdCJdLCJlbnYiOnsiU09DS0VUX0FQSV9LRVkiOiJ5b3VyLWFwaS1rZXktaGVyZSJ9fQ==) | ||
| [](https://cursor.com/en/install-mcp?name=socket-mcp&config=eyJjb21tYW5kIjoibnB4IEBzb2NrZXRzZWN1cml0eS9tY3BAbGF0ZXN0IiwiZW52Ijp7IlNPQ0tFVF9BUElfS0VZIjoieW91ci1hcGkta2V5LWhlcmUifX0%3D) | ||
@@ -379,1 +379,10 @@ Claude Code (stdio mode) can be set up with the following command: | ||
| - 💬 [Community Support](https://github.com/SocketDev/socket-mcp/discussions) | ||
| <br/> | ||
| <div align="center"> | ||
| <picture> | ||
| <source media="(prefers-color-scheme: dark)" srcset="logo-white.png"> | ||
| <source media="(prefers-color-scheme: light)" srcset="logo-black.png"> | ||
| <img width="324" height="108" alt="Socket Logo" src="logo-black.png"> | ||
| </picture> | ||
| </div> |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 6 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 6 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
386
2.39%6
-25%41952
-15.7%645
-18.97%