🚨 Latest Research:Tanstack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack.Learn More
Socket
Book a DemoSign in
Socket

@socketsecurity/mcp

Package Overview
Dependencies
Maintainers
2
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@socketsecurity/mcp - npm Package Compare versions

Comparing version
0.0.16
to
0.0.17
+236
-129
index.js

@@ -5,2 +5,4 @@ #!/usr/bin/env -S node --experimental-strip-types

import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
import { randomUUID } from 'node:crypto';
import { z } from 'zod';

@@ -62,71 +64,88 @@ import pino from 'pino';

}
const server = new McpServer({
name: 'socket',
version: VERSION,
});
server.registerTool('depscore', {
title: 'Dependency Score Tool',
description: "Get the dependency score of packages with the `depscore` tool from Socket. Use 'unknown' for version if not known. Use this tool to scan dependencies for their quality and security on existing code or when code is generated. Stop generating code and ask the user how to proceed when any of the scores are low. When checking dependencies, make sure to also check the imports in the code, not just the manifest files (pyproject.toml, package.json, etc).",
inputSchema: z.object({
packages: z.array(z.object({
ecosystem: z.string().describe('The package ecosystem (e.g., npm, pypi)').default('npm'),
depname: z.string().describe('The name of the dependency'),
version: z.string().describe("The version of the dependency, use 'unknown' if not known").default('unknown'),
})).describe('Array of packages to check'),
}),
annotations: {
readOnlyHint: true,
},
}, async ({ packages }) => {
logger.info(`Received request for ${packages.length} packages`);
const components = packages.map(pkg => {
const cleanedVersion = (pkg.version ?? 'unknown').replace(/[\^~]/g, '');
const ecosystem = pkg.ecosystem ?? 'npm';
let purl;
if (cleanedVersion === '1.0.0' || cleanedVersion === 'unknown' || !cleanedVersion) {
purl = `pkg:${ecosystem}/${pkg.depname}`;
}
else {
logger.info(`Using version ${cleanedVersion} for ${pkg.depname}`);
purl = `pkg:${ecosystem}/${pkg.depname}@${cleanedVersion}`;
}
return { purl };
});
try {
const response = await fetch(SOCKET_API_URL, {
method: 'POST',
headers: buildSocketHeaders(),
body: JSON.stringify({ components })
function createConfiguredServer() {
const srv = new McpServer({ name: 'socket', version: VERSION });
srv.registerTool('depscore', {
title: 'Dependency Score Tool',
description: "Get the dependency score of packages with the `depscore` tool from Socket. Use 'unknown' for version if not known. Use this tool to scan dependencies for their quality and security on existing code or when code is generated. Stop generating code and ask the user how to proceed when any of the scores are low. When checking dependencies, make sure to also check the imports in the code, not just the manifest files (pyproject.toml, package.json, etc).",
inputSchema: {
packages: z.array(z.object({
ecosystem: z.string().describe('The package ecosystem (e.g., npm, pypi)').default('npm'),
depname: z.string().describe('The name of the dependency'),
version: z.string().describe("The version of the dependency, use 'unknown' if not known").default('unknown'),
})).describe('Array of packages to check'),
},
annotations: {
readOnlyHint: true,
},
}, async ({ packages }) => {
logger.info(`Received request for ${packages.length} packages`);
const components = packages.map((pkg) => {
const cleanedVersion = (pkg.version ?? 'unknown').replace(/[\^~]/g, '');
const ecosystem = pkg.ecosystem ?? 'npm';
let purl;
if (cleanedVersion === '1.0.0' || cleanedVersion === 'unknown' || !cleanedVersion) {
purl = `pkg:${ecosystem}/${pkg.depname}`;
}
else {
logger.info(`Using version ${cleanedVersion} for ${pkg.depname}`);
purl = `pkg:${ecosystem}/${pkg.depname}@${cleanedVersion}`;
}
return { purl };
});
const responseText = await response.text();
if (response.status !== 200) {
const errorMsg = `Error processing packages: [${response.status}] ${responseText}`;
logger.error(errorMsg);
return {
content: [{ type: 'text', text: errorMsg }],
isError: true
};
}
else if (!responseText.trim()) {
const errorMsg = 'No packages were found.';
logger.error(errorMsg);
return {
content: [{ type: 'text', text: errorMsg }],
isError: true
};
}
try {
const results = [];
if ((response.headers.get('content-type') || '').includes('x-ndjson')) {
const jsonLines = responseText.split('\n')
.filter(line => line.trim())
.map(line => JSON.parse(line));
if (!jsonLines.length) {
const errorMsg = 'No valid JSON objects found in NDJSON response';
return {
content: [{ type: 'text', text: errorMsg }],
isError: true
};
const response = await fetch(SOCKET_API_URL, {
method: 'POST',
headers: buildSocketHeaders(),
body: JSON.stringify({ components })
});
const responseText = await response.text();
if (response.status !== 200) {
const errorMsg = `Error processing packages: [${response.status}] ${responseText}`;
logger.error(errorMsg);
return {
content: [{ type: 'text', text: errorMsg }],
isError: true
};
}
else if (!responseText.trim()) {
const errorMsg = 'No packages were found.';
logger.error(errorMsg);
return {
content: [{ type: 'text', text: errorMsg }],
isError: true
};
}
try {
const results = [];
if ((response.headers.get('content-type') || '').includes('x-ndjson')) {
const jsonLines = responseText.split('\n')
.filter(line => line.trim())
.map(line => JSON.parse(line));
if (!jsonLines.length) {
const errorMsg = 'No valid JSON objects found in NDJSON response';
return {
content: [{ type: 'text', text: errorMsg }],
isError: true
};
}
for (const jsonData of jsonLines) {
const purl = `pkg:${jsonData.type || 'unknown'}/${jsonData.name || 'unknown'}@${jsonData.version || 'unknown'}`;
if (jsonData.score && jsonData.score.overall !== undefined) {
const scoreEntries = Object.entries(jsonData.score)
.filter(([key]) => key !== 'overall' && key !== 'uuid')
.map(([key, value]) => {
const numValue = Number(value);
const displayValue = numValue <= 1 ? Math.round(numValue * 100) : numValue;
return `${key}: ${displayValue}`;
})
.join(', ');
results.push(`${purl}: ${scoreEntries}`);
}
else {
results.push(`${purl}: No score found`);
}
}
}
for (const jsonData of jsonLines) {
else {
const jsonData = JSON.parse(responseText);
const purl = `pkg:${jsonData.type || 'unknown'}/${jsonData.name || 'unknown'}@${jsonData.version || 'unknown'}`;

@@ -148,52 +167,35 @@ if (jsonData.score && jsonData.score.overall !== undefined) {

}
return {
content: [
{
type: 'text',
text: results.length > 0
? `Dependency scores:\n${results.join('\n')}`
: 'No scores found for the provided packages'
}
]
};
}
else {
const jsonData = JSON.parse(responseText);
const purl = `pkg:${jsonData.type || 'unknown'}/${jsonData.name || 'unknown'}@${jsonData.version || 'unknown'}`;
if (jsonData.score && jsonData.score.overall !== undefined) {
const scoreEntries = Object.entries(jsonData.score)
.filter(([key]) => key !== 'overall' && key !== 'uuid')
.map(([key, value]) => {
const numValue = Number(value);
const displayValue = numValue <= 1 ? Math.round(numValue * 100) : numValue;
return `${key}: ${displayValue}`;
})
.join(', ');
results.push(`${purl}: ${scoreEntries}`);
}
else {
results.push(`${purl}: No score found`);
}
catch (e) {
const error = e;
const errorMsg = `JSON parsing error: ${error.message} -- Response: ${responseText}`;
logger.error(errorMsg);
return {
content: [{ type: 'text', text: 'Error parsing response from Socket API' }],
isError: true
};
}
return {
content: [
{
type: 'text',
text: results.length > 0
? `Dependency scores:\n${results.join('\n')}`
: 'No scores found for the provided packages'
}
]
};
}
catch (e) {
const error = e;
const errorMsg = `JSON parsing error: ${error.message} -- Response: ${responseText}`;
const errorMsg = `Error processing packages: ${error.message}`;
logger.error(errorMsg);
return {
content: [{ type: 'text', text: 'Error parsing response from Socket API' }],
content: [{ type: 'text', text: 'Error connecting to Socket API' }],
isError: true
};
}
}
catch (e) {
const error = e;
const errorMsg = `Error processing packages: ${error.message}`;
logger.error(errorMsg);
return {
content: [{ type: 'text', text: 'Error connecting to Socket API' }],
isError: true
};
}
});
});
return srv;
}
const useHttp = process.env['MCP_HTTP_MODE'] === 'true' || process.argv.includes('--http');

@@ -214,3 +216,26 @@ const port = parseInt(process.env['MCP_PORT'] || '3000', 10);

logger.info(`Starting HTTP server on port ${port}`);
let httpTransport = null;
const sessions = new Map();
function destroySession(id) {
const s = sessions.get(id);
if (!s)
return;
sessions.delete(id);
try {
s.transport.close();
}
catch { }
s.server.close().catch(() => { });
logger.info(`Session ${id} destroyed`);
}
const SESSION_TTL_MS = 30 * 60 * 1000;
const reapInterval = setInterval(() => {
const now = Date.now();
for (const [id, session] of sessions.entries()) {
if (now - session.lastActivity > SESSION_TTL_MS) {
logger.info(`Reaping idle session ${id}`);
destroySession(id);
}
}
}, 60_000);
reapInterval.unref();
const httpServer = createServer(async (req, res) => {

@@ -277,4 +302,5 @@ let url;

res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Mcp-Session-Id');
res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
}

@@ -287,33 +313,58 @@ if (req.method === 'OPTIONS') {

if (url.pathname === '/') {
const accept = req.headers.accept || '';
if (!accept.includes('application/json') || !accept.includes('text/event-stream')) {
const requiredAccept = 'application/json, text/event-stream';
req.headers.accept = requiredAccept;
const idx = req.rawHeaders.findIndex(h => h.toLowerCase() === 'accept');
if (idx !== -1) {
req.rawHeaders[idx + 1] = requiredAccept;
}
else {
req.rawHeaders.push('Accept', requiredAccept);
}
}
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => (body += chunk));
req.on('data', (chunk) => { body += chunk; });
req.on('end', async () => {
try {
const jsonData = JSON.parse(body);
if (jsonData && jsonData.method === 'initialize') {
const sessionId = req.headers['mcp-session-id'] || undefined;
const session = sessionId ? sessions.get(sessionId) : undefined;
let transport = session?.transport;
if (!transport && isInitializeRequest(jsonData)) {
const clientInfo = jsonData.params?.clientInfo;
logger.info(`Client connected: ${clientInfo?.name || 'unknown'} v${clientInfo?.version || 'unknown'} from ${origin || host}`);
if (httpTransport) {
try {
httpTransport.close();
}
catch { }
}
httpTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
const server = createConfiguredServer();
const newTransport = new StreamableHTTPServerTransport({
enableJsonResponse: true,
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (id) => {
sessions.set(id, { transport: newTransport, server, lastActivity: Date.now() });
},
onsessionclosed: (id) => { destroySession(id); }
});
await server.connect(httpTransport);
await httpTransport.handleRequest(req, res, jsonData);
newTransport.onclose = () => {
const id = newTransport.sessionId;
if (id)
destroySession(id);
};
transport = newTransport;
await server.connect(transport);
}
if (!transport) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
jsonrpc: '2.0',
error: { code: -32000, message: 'Bad Request: No valid session. Send initialize first.' },
id: null
}));
return;
}
if (!httpTransport) {
httpTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
await server.connect(httpTransport);
if (sessionId) {
const activeSession = sessions.get(sessionId);
if (activeSession)
activeSession.lastActivity = Date.now();
}
await httpTransport.handleRequest(req, res, jsonData);
await transport.handleRequest(req, res, jsonData);
}

@@ -333,2 +384,57 @@ catch (error) {

}
else if (req.method === 'GET') {
const sessionId = req.headers['mcp-session-id'] || undefined;
const session = sessionId ? sessions.get(sessionId) : undefined;
if (!session) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
jsonrpc: '2.0',
error: { code: -32000, message: 'Not Found: Invalid or expired session. Re-initialize.' },
id: null
}));
return;
}
try {
session.lastActivity = Date.now();
await session.transport.handleRequest(req, res);
}
catch (error) {
logger.error(`Error processing GET request: ${error}`);
if (!res.headersSent) {
res.writeHead(500);
res.end(JSON.stringify({
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal server error' },
id: null
}));
}
}
}
else if (req.method === 'DELETE') {
const sessionId = req.headers['mcp-session-id'] || undefined;
const transport = sessionId ? sessions.get(sessionId)?.transport : undefined;
if (!transport) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
jsonrpc: '2.0',
error: { code: -32000, message: 'Not Found: Invalid or expired session.' },
id: null
}));
return;
}
try {
await transport.handleRequest(req, res);
}
catch (error) {
logger.error(`Error processing DELETE request: ${error}`);
if (!res.headersSent) {
res.writeHead(500);
res.end(JSON.stringify({
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal server error' },
id: null
}));
}
}
}
else {

@@ -351,2 +457,3 @@ res.writeHead(405);

logger.info('Starting in stdio mode');
const server = createConfiguredServer();
const transport = new StdioServerTransport();

@@ -353,0 +460,0 @@ server.connect(transport)

{
"name": "@socketsecurity/mcp",
"version": "0.0.16",
"version": "0.0.17",
"type": "module",

@@ -53,8 +53,18 @@ "main": "./index.js",

"@anthropic-ai/mcpb": "^1.1.0",
"@modelcontextprotocol/sdk": "^1.18.0",
"@modelcontextprotocol/sdk": "1.26.0",
"pino": "^10.0.0",
"pino-pretty": "^13.0.0",
"semver": "^7.7.2",
"zod": "^3.24.4"
"zod": "3.25.76"
},
"overrides": {
"zod": "3.25.76",
"zod-to-json-schema": "3.25.1"
},
"pnpm": {
"overrides": {
"zod": "3.25.76",
"zod-to-json-schema": "3.25.1"
}
},
"devDependencies": {

@@ -61,0 +71,0 @@ "@types/node": "^24.0.7",