Skip to content

Commit 1c0ce39

Browse files
committed
Adding support for batching requests
1 parent ce681fa commit 1c0ce39

File tree

2 files changed

+168
-114
lines changed

2 files changed

+168
-114
lines changed

build/index.js

Lines changed: 82 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
44
import { z } from "zod";
55
import fetch from 'node-fetch';
66
import winston from 'winston';
7+
import readline from 'readline';
78
const VERSION = "0.0.1"; // Version of the MCP server
89
// Configure winston logger
910
const logger = winston.createLogger({
@@ -15,10 +16,24 @@ const logger = winston.createLogger({
1516
]
1617
});
1718
const SOCKET_API_URL = "https://api.socket.dev/v0/purl?alerts=false&compact=false&fixable=false&licenseattrib=false&licensedetails=false";
18-
const SOCKET_API_KEY = process.env.SOCKET_API_KEY || "";
19+
let SOCKET_API_KEY = process.env.SOCKET_API_KEY || "";
1920
if (!SOCKET_API_KEY) {
2021
logger.error("SOCKET_API_KEY environment variable is not set");
21-
process.exit(1);
22+
const rl = readline.createInterface({
23+
input: process.stdin,
24+
output: process.stderr
25+
});
26+
const apiKey = await new Promise((resolve) => {
27+
rl.question('Please enter your Socket API key: ', (answer) => {
28+
rl.close();
29+
resolve(answer);
30+
});
31+
});
32+
if (!apiKey) {
33+
logger.error("No API key provided");
34+
process.exit(1);
35+
}
36+
SOCKET_API_KEY = apiKey;
2237
}
2338
const SOCKET_HEADERS = {
2439
"user-agent": `socket-mcp/${VERSION}`,
@@ -36,40 +51,45 @@ const server = new McpServer({
3651
tools: {},
3752
},
3853
});
39-
server.tool("depscore", "Get the dependency score of a package 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).", {
40-
ecosystem: z.string().describe("The package ecosystem (e.g., npm, pypi)").default("npm"),
41-
depname: z.string().describe("The name of the dependency"),
42-
version: z.string().describe("The version of the dependency, use 'unknown' if not known").default("unknown"),
43-
}, async ({ ecosystem, depname, version }) => {
44-
logger.info(`Received request for ${depname} (${version}) in ${ecosystem} ecosystem`);
45-
// cleanup version
46-
let purl;
47-
const cleanedVersion = version.replace(/[\^~]/g, ''); // Remove ^ and ~ from version
48-
if (cleanedVersion === "1.0.0" || cleanedVersion === "unknown" || !cleanedVersion) {
49-
purl = `pkg:${ecosystem}/${depname}`;
50-
}
51-
else {
52-
logger.info(`Using version ${cleanedVersion} for ${depname}`);
53-
purl = `pkg:${ecosystem}/${depname}@${cleanedVersion}`;
54-
}
54+
server.tool("depscore", "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).", {
55+
packages: z.array(z.object({
56+
ecosystem: z.string().describe("The package ecosystem (e.g., npm, pypi)").default("npm"),
57+
depname: z.string().describe("The name of the dependency"),
58+
version: z.string().describe("The version of the dependency, use 'unknown' if not known").default("unknown"),
59+
})).describe("Array of packages to check"),
60+
}, async ({ packages }) => {
61+
logger.info(`Received request for ${packages.length} packages`);
62+
// Build components array for the API request
63+
const components = packages.map(pkg => {
64+
const cleanedVersion = pkg.version.replace(/[\^~]/g, ''); // Remove ^ and ~ from version
65+
let purl;
66+
if (cleanedVersion === "1.0.0" || cleanedVersion === "unknown" || !cleanedVersion) {
67+
purl = `pkg:${pkg.ecosystem}/${pkg.depname}`;
68+
}
69+
else {
70+
logger.info(`Using version ${cleanedVersion} for ${pkg.depname}`);
71+
purl = `pkg:${pkg.ecosystem}/${pkg.depname}@${cleanedVersion}`;
72+
}
73+
return { purl };
74+
});
5575
try {
56-
// Make a POST request to the Socket API
76+
// Make a POST request to the Socket API with all packages
5777
const response = await fetch(SOCKET_API_URL, {
5878
method: 'POST',
5979
headers: SOCKET_HEADERS,
60-
body: JSON.stringify({ components: [{ purl }] })
80+
body: JSON.stringify({ components })
6181
});
6282
const responseText = await response.text();
6383
if (response.status !== 200) {
64-
const errorMsg = `Error processing ${purl}: [${response.status}] ${responseText}`;
84+
const errorMsg = `Error processing packages: [${response.status}] ${responseText}`;
6585
logger.error(errorMsg);
6686
return {
6787
content: [{ type: "text", text: errorMsg }],
6888
isError: false
6989
};
7090
}
7191
else if (!responseText.trim()) {
72-
const errorMsg = `${purl} was not found.`;
92+
const errorMsg = `No packages were found.`;
7393
logger.error(errorMsg);
7494
return {
7595
content: [{ type: "text", text: errorMsg }],
@@ -78,67 +98,72 @@ server.tool("depscore", "Get the dependency score of a package with the `depscor
7898
}
7999
try {
80100
// Handle NDJSON (multiple JSON objects, one per line)
81-
let jsonData;
101+
let results = [];
82102
if ((response.headers.get('content-type') || '').includes('x-ndjson')) {
83103
const jsonLines = responseText.split('\n')
84104
.filter(line => line.trim())
85105
.map(line => JSON.parse(line));
86106
if (!jsonLines.length) {
87-
const errorMsg = `No valid JSON objects found in NDJSON response for ${purl}`;
107+
const errorMsg = `No valid JSON objects found in NDJSON response`;
88108
return {
89109
content: [{ type: "text", text: errorMsg }],
90110
isError: true
91111
};
92112
}
93-
jsonData = jsonLines[0];
94-
}
95-
else {
96-
jsonData = JSON.parse(responseText);
97-
}
98-
if (jsonData.score && jsonData.score.overall !== undefined) {
99-
// Unroll the jsonData.score object into key-value pairs
100-
const scoreEntries = Object.entries(jsonData.score)
101-
.filter(([key]) => key !== "overall" && key !== "uuid")
102-
.map(([key, value]) => `${key}: ${value}`)
103-
.join(', ');
104-
return {
105-
content: [
106-
{
107-
type: "text",
108-
text: `Dependency scores for ${purl}: ${scoreEntries}`
109-
}
110-
]
111-
};
113+
// Process each result
114+
for (const jsonData of jsonLines) {
115+
if (jsonData.score && jsonData.score.overall !== undefined) {
116+
const scoreEntries = Object.entries(jsonData.score)
117+
.filter(([key]) => key !== "overall" && key !== "uuid")
118+
.map(([key, value]) => `${key}: ${value}`)
119+
.join(', ');
120+
const packageName = jsonData.name || 'unknown';
121+
results.push(`${packageName}: ${scoreEntries}`);
122+
}
123+
else {
124+
const packageName = jsonData.name || 'unknown';
125+
results.push(`${packageName}: No score found`);
126+
}
127+
}
112128
}
113129
else {
114-
return {
115-
content: [
116-
{
117-
type: "text",
118-
text: `No score found for ${purl}`
119-
}
120-
]
121-
};
130+
const jsonData = JSON.parse(responseText);
131+
if (jsonData.score && jsonData.score.overall !== undefined) {
132+
const scoreEntries = Object.entries(jsonData.score)
133+
.filter(([key]) => key !== "overall" && key !== "uuid")
134+
.map(([key, value]) => `${key}: ${value}`)
135+
.join(', ');
136+
const packageName = jsonData.package?.name || 'unknown';
137+
results.push(`${packageName}: ${scoreEntries}`);
138+
}
122139
}
140+
return {
141+
content: [
142+
{
143+
type: "text",
144+
text: results.length > 0
145+
? `Dependency scores:\n${results.join('\n')}`
146+
: "No scores found for the provided packages"
147+
}
148+
]
149+
};
123150
}
124151
catch (e) {
125152
const error = e;
126-
const errorMsg = `JSON parsing error for ${purl}: ${error.message} -- Response: ${responseText}`;
153+
const errorMsg = `JSON parsing error: ${error.message} -- Response: ${responseText}`;
127154
logger.error(errorMsg);
128-
const llmResponse = `Package ${purl} not found.`;
129155
return {
130-
content: [{ type: "text", text: llmResponse }],
156+
content: [{ type: "text", text: "Error parsing response from Socket API" }],
131157
isError: true
132158
};
133159
}
134160
}
135161
catch (e) {
136162
const error = e;
137-
const errorMsg = `Error processing ${purl}: ${error.message}`;
163+
const errorMsg = `Error processing packages: ${error.message}`;
138164
logger.error(errorMsg);
139-
const llmResponse = `Package ${purl} not found.`;
140165
return {
141-
content: [{ type: "text", text: llmResponse }],
166+
content: [{ type: "text", text: "Error connecting to Socket API" }],
142167
isError: true
143168
};
144169
}

0 commit comments

Comments
 (0)