@socketsecurity/mcp 0.0.16 → 0.0.18
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 +19 -0
- package/README.md +221 -184
- package/dist/index.cjs +58128 -0
- package/dist/index.d.ts +4 -0
- package/hooks/socket-gate/README.md +117 -0
- package/hooks/socket-gate/index.mts +289 -0
- package/hooks/socket-gate/socket-gate.cjs +1047 -0
- package/package.json +91 -56
- package/index.d.ts +0 -3
- package/index.d.ts.map +0 -1
- package/index.js +0 -353
- package/mock-client/debug-client.d.ts +0 -3
- package/mock-client/debug-client.d.ts.map +0 -1
- package/mock-client/debug-client.js +0 -134
- package/mock-client/http-client.d.ts +0 -3
- package/mock-client/http-client.d.ts.map +0 -1
- package/mock-client/http-client.js +0 -123
- package/mock-client/stdio-client.d.ts +0 -3
- package/mock-client/stdio-client.d.ts.map +0 -1
- package/mock-client/stdio-client.js +0 -52
package/package.json
CHANGED
|
@@ -1,68 +1,103 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketsecurity/mcp",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.18",
|
|
4
|
+
"description": "Socket MCP server for scanning dependencies",
|
|
5
|
+
"keywords": [],
|
|
6
|
+
"author": "Alexandros Kapravelos",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/SocketDev/socket-mcp"
|
|
10
|
+
},
|
|
6
11
|
"bin": {
|
|
7
|
-
"socket-mcp": "./index.
|
|
12
|
+
"socket-mcp": "./dist/index.cjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"dist/index.cjs",
|
|
17
|
+
"dist/index.d.ts",
|
|
18
|
+
"hooks/socket-gate",
|
|
19
|
+
"package.json"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.cjs",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"provenance": true
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@anthropic-ai/mcpb": "^1.2.0",
|
|
29
|
+
"@modelcontextprotocol/sdk": "1.26.0",
|
|
30
|
+
"@redwoodjs/agent-ci": "0.16.2",
|
|
31
|
+
"@sinclair/typebox": "0.34.49",
|
|
32
|
+
"@socketregistry/packageurl-js": "1.4.2",
|
|
33
|
+
"@socketregistry/packageurl-js-stable": "npm:@socketregistry/packageurl-js@1.4.2",
|
|
34
|
+
"@socketsecurity/lib": "npm:@socketsecurity/lib@6.0.7",
|
|
35
|
+
"@socketsecurity/lib-stable": "npm:@socketsecurity/lib@6.0.7",
|
|
36
|
+
"@socketsecurity/sdk": "4.0.1",
|
|
37
|
+
"@socketsecurity/sdk-stable": "npm:@socketsecurity/sdk@4.0.1",
|
|
38
|
+
"@types/node": "^24.12.4",
|
|
39
|
+
"@types/shell-quote": "1.7.5",
|
|
40
|
+
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
|
41
|
+
"@vitest/coverage-v8": "4.1.6",
|
|
42
|
+
"nock": "14.0.15",
|
|
43
|
+
"npm-run-all2": "^8.0.4",
|
|
44
|
+
"oxfmt": "0.48.0",
|
|
45
|
+
"oxlint": "1.63.0",
|
|
46
|
+
"rolldown": "1.0.3",
|
|
47
|
+
"shell-quote": "1.8.4",
|
|
48
|
+
"taze": "19.11.0",
|
|
49
|
+
"untracked": "1.6.1",
|
|
50
|
+
"vitest": "4.1.6"
|
|
8
51
|
},
|
|
9
52
|
"engines": {
|
|
10
53
|
"node": ">=22.0.0",
|
|
11
|
-
"npm": ">=
|
|
54
|
+
"npm": ">=11.16.0"
|
|
55
|
+
},
|
|
56
|
+
"allowScripts": {
|
|
57
|
+
"cpu-features": false,
|
|
58
|
+
"esbuild": true,
|
|
59
|
+
"protobufjs": false,
|
|
60
|
+
"ssh2": false
|
|
61
|
+
},
|
|
62
|
+
"npm-run-all2": {
|
|
63
|
+
"nodeRun": true
|
|
12
64
|
},
|
|
13
65
|
"scripts": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"test:
|
|
18
|
-
"
|
|
19
|
-
"
|
|
66
|
+
"test": "node scripts/fleet/test.mts",
|
|
67
|
+
"test:e2e": "vitest run --config .config/repo/vitest.config.mts test/server.e2e.ts",
|
|
68
|
+
"cover": "node scripts/fleet/cover.mts",
|
|
69
|
+
"test:tsc": "tsgo --noEmit -p tsconfig.json",
|
|
70
|
+
"lint": "node scripts/fleet/lint.mts",
|
|
71
|
+
"lint:all": "node scripts/fleet/lint.mts --all",
|
|
72
|
+
"format": "oxfmt -c .config/fleet/oxfmtrc.json --write .",
|
|
73
|
+
"format:check": "oxfmt -c .config/fleet/oxfmtrc.json --check .",
|
|
74
|
+
"check": "node scripts/fleet/check.mts",
|
|
75
|
+
"check:all": "node scripts/fleet/check.mts --all",
|
|
76
|
+
"fix": "node scripts/fleet/fix.mts",
|
|
77
|
+
"fix:all": "node scripts/fleet/fix.mts --all",
|
|
78
|
+
"update": "node scripts/fleet/update.mts",
|
|
20
79
|
"build": "npm run clean && run-s build:*",
|
|
21
|
-
"build:
|
|
22
|
-
"build:
|
|
80
|
+
"build:bundle": "node scripts/build.mts",
|
|
81
|
+
"build:types": "tsgo -p tsconfig.dts.json",
|
|
82
|
+
"build:permissions": "chmod +x ./dist/index.cjs",
|
|
23
83
|
"build-mcpb": "run-s build build-mcpb:*",
|
|
24
|
-
"build-mcpb:versions_match": "node
|
|
25
|
-
"build-mcpb:validate": "
|
|
84
|
+
"build-mcpb:versions_match": "node scripts/check-versions.ts",
|
|
85
|
+
"build-mcpb:validate": "mcpb validate ./",
|
|
26
86
|
"build-mcpb:ensure-deps": "npm install --production --ignore-scripts",
|
|
27
|
-
"build-mcpb:mcpb-pack": "
|
|
28
|
-
"clean": "./scripts/clean.sh",
|
|
29
|
-
"debug-stdio": "node
|
|
30
|
-
"debug-sdk": "node
|
|
31
|
-
"debug-http": "node
|
|
32
|
-
"server-stdio": "
|
|
33
|
-
"server-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"mock-client/**/*.js",
|
|
43
|
-
"mock-client/**/*.d.ts*"
|
|
44
|
-
],
|
|
45
|
-
"author": "Alexandros Kapravelos",
|
|
46
|
-
"description": "Socket MCP server for scanning dependencies",
|
|
47
|
-
"repository": {
|
|
48
|
-
"type": "git",
|
|
49
|
-
"url": "https://github.com/SocketDev/socket-mcp"
|
|
50
|
-
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"@anthropic-ai/mcpb": "^1.1.0",
|
|
53
|
-
"@modelcontextprotocol/sdk": "^1.18.0",
|
|
54
|
-
"pino": "^10.0.0",
|
|
55
|
-
"pino-pretty": "^13.0.0",
|
|
56
|
-
"semver": "^7.7.2",
|
|
57
|
-
"zod": "^3.24.4"
|
|
58
|
-
},
|
|
59
|
-
"devDependencies": {
|
|
60
|
-
"@types/node": "^24.0.7",
|
|
61
|
-
"@types/semver": "^7.7.0",
|
|
62
|
-
"@types/triple-beam": "^1.3.5",
|
|
63
|
-
"c8": "^10.0.0",
|
|
64
|
-
"neostandard": "^0.12.0",
|
|
65
|
-
"npm-run-all2": "^8.0.1",
|
|
66
|
-
"typescript": "~5.9.2"
|
|
87
|
+
"build-mcpb:mcpb-pack": "mcpb pack ./",
|
|
88
|
+
"clean": "bash ./scripts/clean.sh",
|
|
89
|
+
"debug-stdio": "node ./mock-client/debug-client.ts",
|
|
90
|
+
"debug-sdk": "node ./mock-client/stdio-client.ts",
|
|
91
|
+
"debug-http": "node ./mock-client/http-client.ts",
|
|
92
|
+
"server-stdio": "SOCKET_API_TOKEN=${SOCKET_API_TOKEN:-${SOCKET_API_KEY}} node ./index.ts",
|
|
93
|
+
"server-stdio:debug": "SOCKET_DEBUG=1 SOCKET_API_TOKEN=${SOCKET_API_TOKEN:-${SOCKET_API_KEY}} node ./index.ts",
|
|
94
|
+
"server-http": "MCP_HTTP_MODE=true SOCKET_API_TOKEN=${SOCKET_API_TOKEN:-${SOCKET_API_KEY}} node ./index.ts",
|
|
95
|
+
"server-http:debug": "SOCKET_DEBUG=1 MCP_HTTP_MODE=true SOCKET_API_TOKEN=${SOCKET_API_TOKEN:-${SOCKET_API_KEY}} node ./index.ts",
|
|
96
|
+
"check:paths": "node scripts/fleet/check-paths.mts",
|
|
97
|
+
"security": "node scripts/fleet/security.mts",
|
|
98
|
+
"setup-security-tools": "node .claude/hooks/fleet/setup-security-tools/install.mts",
|
|
99
|
+
"lockstep": "node scripts/fleet/lockstep.mts",
|
|
100
|
+
"lockstep:emit-schema": "node scripts/fleet/lockstep-emit-schema.mts",
|
|
101
|
+
"ci:local": "agent-ci run --all"
|
|
67
102
|
}
|
|
68
|
-
}
|
|
103
|
+
}
|
package/index.d.ts
DELETED
package/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":""}
|
package/index.js
DELETED
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --experimental-strip-types
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
import pino from 'pino';
|
|
7
|
-
import readline from 'readline';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
import { readFileSync } from 'fs';
|
|
10
|
-
import { tmpdir } from 'os';
|
|
11
|
-
import { createServer } from 'http';
|
|
12
|
-
const __dirname = import.meta.dirname;
|
|
13
|
-
const packageJson = JSON.parse(readFileSync(join(__dirname, './package.json'), 'utf8'));
|
|
14
|
-
const VERSION = packageJson.version || '0.0.1';
|
|
15
|
-
const logger = pino({
|
|
16
|
-
level: 'info',
|
|
17
|
-
transport: {
|
|
18
|
-
targets: [
|
|
19
|
-
{
|
|
20
|
-
target: 'pino/file',
|
|
21
|
-
options: { destination: join(tmpdir(), 'socket-mcp-error.log') },
|
|
22
|
-
level: 'error'
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
target: 'pino/file',
|
|
26
|
-
options: { destination: join(tmpdir(), 'socket-mcp.log') },
|
|
27
|
-
level: 'info'
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
const SOCKET_API_URL = process.env['SOCKET_DEBUG'] === 'true'
|
|
33
|
-
? 'http://localhost:8866/v0/purl?alerts=false&compact=false&fixable=false&licenseattrib=false&licensedetails=false'
|
|
34
|
-
: 'https://api.socket.dev/v0/purl?alerts=false&compact=false&fixable=false&licenseattrib=false&licensedetails=false';
|
|
35
|
-
async function getApiKeyInteractively() {
|
|
36
|
-
const rl = readline.createInterface({
|
|
37
|
-
input: process.stdin,
|
|
38
|
-
output: process.stderr
|
|
39
|
-
});
|
|
40
|
-
const apiKey = await new Promise((resolve) => {
|
|
41
|
-
rl.question('Please enter your Socket API key: ', (answer) => {
|
|
42
|
-
rl.close();
|
|
43
|
-
resolve(answer);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
if (!apiKey) {
|
|
47
|
-
logger.error('No API key provided');
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
return apiKey;
|
|
51
|
-
}
|
|
52
|
-
let SOCKET_API_KEY = process.env['SOCKET_API_KEY'] || '';
|
|
53
|
-
function buildSocketHeaders() {
|
|
54
|
-
return {
|
|
55
|
-
'user-agent': `socket-mcp/${VERSION}`,
|
|
56
|
-
accept: 'application/x-ndjson',
|
|
57
|
-
'content-type': 'application/json',
|
|
58
|
-
authorization: `Bearer ${SOCKET_API_KEY}`
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
const server = new McpServer({
|
|
62
|
-
name: 'socket',
|
|
63
|
-
version: VERSION,
|
|
64
|
-
});
|
|
65
|
-
server.registerTool('depscore', {
|
|
66
|
-
title: 'Dependency Score Tool',
|
|
67
|
-
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).",
|
|
68
|
-
inputSchema: z.object({
|
|
69
|
-
packages: z.array(z.object({
|
|
70
|
-
ecosystem: z.string().describe('The package ecosystem (e.g., npm, pypi)').default('npm'),
|
|
71
|
-
depname: z.string().describe('The name of the dependency'),
|
|
72
|
-
version: z.string().describe("The version of the dependency, use 'unknown' if not known").default('unknown'),
|
|
73
|
-
})).describe('Array of packages to check'),
|
|
74
|
-
}),
|
|
75
|
-
annotations: {
|
|
76
|
-
readOnlyHint: true,
|
|
77
|
-
},
|
|
78
|
-
}, async ({ packages }) => {
|
|
79
|
-
logger.info(`Received request for ${packages.length} packages`);
|
|
80
|
-
const components = packages.map(pkg => {
|
|
81
|
-
const cleanedVersion = (pkg.version ?? 'unknown').replace(/[\^~]/g, '');
|
|
82
|
-
const ecosystem = pkg.ecosystem ?? 'npm';
|
|
83
|
-
let purl;
|
|
84
|
-
if (cleanedVersion === '1.0.0' || cleanedVersion === 'unknown' || !cleanedVersion) {
|
|
85
|
-
purl = `pkg:${ecosystem}/${pkg.depname}`;
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
logger.info(`Using version ${cleanedVersion} for ${pkg.depname}`);
|
|
89
|
-
purl = `pkg:${ecosystem}/${pkg.depname}@${cleanedVersion}`;
|
|
90
|
-
}
|
|
91
|
-
return { purl };
|
|
92
|
-
});
|
|
93
|
-
try {
|
|
94
|
-
const response = await fetch(SOCKET_API_URL, {
|
|
95
|
-
method: 'POST',
|
|
96
|
-
headers: buildSocketHeaders(),
|
|
97
|
-
body: JSON.stringify({ components })
|
|
98
|
-
});
|
|
99
|
-
const responseText = await response.text();
|
|
100
|
-
if (response.status !== 200) {
|
|
101
|
-
const errorMsg = `Error processing packages: [${response.status}] ${responseText}`;
|
|
102
|
-
logger.error(errorMsg);
|
|
103
|
-
return {
|
|
104
|
-
content: [{ type: 'text', text: errorMsg }],
|
|
105
|
-
isError: true
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
else if (!responseText.trim()) {
|
|
109
|
-
const errorMsg = 'No packages were found.';
|
|
110
|
-
logger.error(errorMsg);
|
|
111
|
-
return {
|
|
112
|
-
content: [{ type: 'text', text: errorMsg }],
|
|
113
|
-
isError: true
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
const results = [];
|
|
118
|
-
if ((response.headers.get('content-type') || '').includes('x-ndjson')) {
|
|
119
|
-
const jsonLines = responseText.split('\n')
|
|
120
|
-
.filter(line => line.trim())
|
|
121
|
-
.map(line => JSON.parse(line));
|
|
122
|
-
if (!jsonLines.length) {
|
|
123
|
-
const errorMsg = 'No valid JSON objects found in NDJSON response';
|
|
124
|
-
return {
|
|
125
|
-
content: [{ type: 'text', text: errorMsg }],
|
|
126
|
-
isError: true
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
for (const jsonData of jsonLines) {
|
|
130
|
-
const purl = `pkg:${jsonData.type || 'unknown'}/${jsonData.name || 'unknown'}@${jsonData.version || 'unknown'}`;
|
|
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]) => {
|
|
135
|
-
const numValue = Number(value);
|
|
136
|
-
const displayValue = numValue <= 1 ? Math.round(numValue * 100) : numValue;
|
|
137
|
-
return `${key}: ${displayValue}`;
|
|
138
|
-
})
|
|
139
|
-
.join(', ');
|
|
140
|
-
results.push(`${purl}: ${scoreEntries}`);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
results.push(`${purl}: No score found`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
const jsonData = JSON.parse(responseText);
|
|
149
|
-
const purl = `pkg:${jsonData.type || 'unknown'}/${jsonData.name || 'unknown'}@${jsonData.version || 'unknown'}`;
|
|
150
|
-
if (jsonData.score && jsonData.score.overall !== undefined) {
|
|
151
|
-
const scoreEntries = Object.entries(jsonData.score)
|
|
152
|
-
.filter(([key]) => key !== 'overall' && key !== 'uuid')
|
|
153
|
-
.map(([key, value]) => {
|
|
154
|
-
const numValue = Number(value);
|
|
155
|
-
const displayValue = numValue <= 1 ? Math.round(numValue * 100) : numValue;
|
|
156
|
-
return `${key}: ${displayValue}`;
|
|
157
|
-
})
|
|
158
|
-
.join(', ');
|
|
159
|
-
results.push(`${purl}: ${scoreEntries}`);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
results.push(`${purl}: No score found`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return {
|
|
166
|
-
content: [
|
|
167
|
-
{
|
|
168
|
-
type: 'text',
|
|
169
|
-
text: results.length > 0
|
|
170
|
-
? `Dependency scores:\n${results.join('\n')}`
|
|
171
|
-
: 'No scores found for the provided packages'
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
catch (e) {
|
|
177
|
-
const error = e;
|
|
178
|
-
const errorMsg = `JSON parsing error: ${error.message} -- Response: ${responseText}`;
|
|
179
|
-
logger.error(errorMsg);
|
|
180
|
-
return {
|
|
181
|
-
content: [{ type: 'text', text: 'Error parsing response from Socket API' }],
|
|
182
|
-
isError: true
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch (e) {
|
|
187
|
-
const error = e;
|
|
188
|
-
const errorMsg = `Error processing packages: ${error.message}`;
|
|
189
|
-
logger.error(errorMsg);
|
|
190
|
-
return {
|
|
191
|
-
content: [{ type: 'text', text: 'Error connecting to Socket API' }],
|
|
192
|
-
isError: true
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
const useHttp = process.env['MCP_HTTP_MODE'] === 'true' || process.argv.includes('--http');
|
|
197
|
-
const port = parseInt(process.env['MCP_PORT'] || '3000', 10);
|
|
198
|
-
if (!SOCKET_API_KEY) {
|
|
199
|
-
if (useHttp) {
|
|
200
|
-
logger.error('SOCKET_API_KEY environment variable is not set');
|
|
201
|
-
SOCKET_API_KEY = await getApiKeyInteractively();
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
logger.error('SOCKET_API_KEY environment variable is required in stdio mode');
|
|
205
|
-
logger.error('Please set the SOCKET_API_KEY environment variable and try again');
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (useHttp) {
|
|
210
|
-
logger.info(`Starting HTTP server on port ${port}`);
|
|
211
|
-
let httpTransport = null;
|
|
212
|
-
const httpServer = createServer(async (req, res) => {
|
|
213
|
-
let url;
|
|
214
|
-
try {
|
|
215
|
-
url = new URL(req.url, `http://localhost:${port}`);
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
logger.warn(`Invalid URL in request: ${req.url} - ${error}`);
|
|
219
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
220
|
-
res.end(JSON.stringify({
|
|
221
|
-
jsonrpc: '2.0',
|
|
222
|
-
error: { code: -32000, message: 'Bad Request: Invalid URL' },
|
|
223
|
-
id: null
|
|
224
|
-
}));
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (url.pathname === '/health') {
|
|
228
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
229
|
-
res.end(JSON.stringify({
|
|
230
|
-
status: 'healthy',
|
|
231
|
-
service: 'socket-mcp',
|
|
232
|
-
version: VERSION,
|
|
233
|
-
timestamp: new Date().toISOString()
|
|
234
|
-
}));
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
const origin = req.headers.origin;
|
|
238
|
-
const isLocalhostOrigin = (originUrl) => {
|
|
239
|
-
try {
|
|
240
|
-
const url = new URL(originUrl);
|
|
241
|
-
return url.hostname === 'localhost' || url.hostname === '127.0.0.1';
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
const allowedOrigins = [
|
|
248
|
-
'https://mcp.socket.dev',
|
|
249
|
-
'https://mcp.socket-staging.dev'
|
|
250
|
-
];
|
|
251
|
-
const host = req.headers.host || '';
|
|
252
|
-
const allowedHosts = allowedOrigins.map(o => new URL(o).hostname);
|
|
253
|
-
const isAllowedHost = host === `localhost:${port}` ||
|
|
254
|
-
host === `127.0.0.1:${port}` ||
|
|
255
|
-
host === 'localhost' ||
|
|
256
|
-
host === '127.0.0.1' ||
|
|
257
|
-
allowedHosts.includes(host);
|
|
258
|
-
const isValidOrigin = origin
|
|
259
|
-
? (isLocalhostOrigin(origin) || allowedOrigins.includes(origin))
|
|
260
|
-
: isAllowedHost;
|
|
261
|
-
if (!isValidOrigin) {
|
|
262
|
-
logger.warn(`Rejected request from invalid origin: ${origin || 'missing'} (host: ${host})`);
|
|
263
|
-
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
264
|
-
res.end(JSON.stringify({
|
|
265
|
-
jsonrpc: '2.0',
|
|
266
|
-
error: { code: -32000, message: 'Forbidden: Invalid origin' },
|
|
267
|
-
id: null
|
|
268
|
-
}));
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
if (origin) {
|
|
272
|
-
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
273
|
-
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
274
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
|
|
275
|
-
}
|
|
276
|
-
if (req.method === 'OPTIONS') {
|
|
277
|
-
res.writeHead(200);
|
|
278
|
-
res.end();
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
if (url.pathname === '/') {
|
|
282
|
-
if (req.method === 'POST') {
|
|
283
|
-
let body = '';
|
|
284
|
-
req.on('data', chunk => (body += chunk));
|
|
285
|
-
req.on('end', async () => {
|
|
286
|
-
try {
|
|
287
|
-
const jsonData = JSON.parse(body);
|
|
288
|
-
if (jsonData && jsonData.method === 'initialize') {
|
|
289
|
-
const clientInfo = jsonData.params?.clientInfo;
|
|
290
|
-
logger.info(`Client connected: ${clientInfo?.name || 'unknown'} v${clientInfo?.version || 'unknown'} from ${origin || host}`);
|
|
291
|
-
if (httpTransport) {
|
|
292
|
-
try {
|
|
293
|
-
httpTransport.close();
|
|
294
|
-
}
|
|
295
|
-
catch { }
|
|
296
|
-
}
|
|
297
|
-
httpTransport = new StreamableHTTPServerTransport({
|
|
298
|
-
sessionIdGenerator: undefined,
|
|
299
|
-
enableJsonResponse: true
|
|
300
|
-
});
|
|
301
|
-
await server.connect(httpTransport);
|
|
302
|
-
await httpTransport.handleRequest(req, res, jsonData);
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
if (!httpTransport) {
|
|
306
|
-
httpTransport = new StreamableHTTPServerTransport({
|
|
307
|
-
sessionIdGenerator: undefined,
|
|
308
|
-
enableJsonResponse: true
|
|
309
|
-
});
|
|
310
|
-
await server.connect(httpTransport);
|
|
311
|
-
}
|
|
312
|
-
await httpTransport.handleRequest(req, res, jsonData);
|
|
313
|
-
}
|
|
314
|
-
catch (error) {
|
|
315
|
-
logger.error(`Error processing POST request: ${error}`);
|
|
316
|
-
if (!res.headersSent) {
|
|
317
|
-
res.writeHead(500);
|
|
318
|
-
res.end(JSON.stringify({
|
|
319
|
-
jsonrpc: '2.0',
|
|
320
|
-
error: { code: -32603, message: 'Internal server error' },
|
|
321
|
-
id: null
|
|
322
|
-
}));
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
res.writeHead(405);
|
|
329
|
-
res.end('Method not allowed');
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
res.writeHead(404);
|
|
334
|
-
res.end('Not found');
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
httpServer.listen(port, () => {
|
|
338
|
-
logger.info(`Socket MCP HTTP server version ${VERSION} started successfully on port ${port}`);
|
|
339
|
-
logger.info(`Connect to: http://localhost:${port}/`);
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
else {
|
|
343
|
-
logger.info('Starting in stdio mode');
|
|
344
|
-
const transport = new StdioServerTransport();
|
|
345
|
-
server.connect(transport)
|
|
346
|
-
.then(() => {
|
|
347
|
-
logger.info(`Socket MCP server version ${VERSION} started successfully`);
|
|
348
|
-
})
|
|
349
|
-
.catch((error) => {
|
|
350
|
-
logger.error(`Failed to start Socket MCP server: ${error.message}`);
|
|
351
|
-
process.exit(1);
|
|
352
|
-
});
|
|
353
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-client.d.ts","sourceRoot":"","sources":["debug-client.ts"],"names":[],"mappings":""}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node --experimental-strip-types
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
|
-
import readline from 'readline';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
class SimpleJSONRPCClient {
|
|
6
|
-
process;
|
|
7
|
-
rl;
|
|
8
|
-
requestId = 1;
|
|
9
|
-
pendingRequests = new Map();
|
|
10
|
-
constructor(command, args = [], env = {}) {
|
|
11
|
-
this.process = spawn(command, args, {
|
|
12
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
13
|
-
env: { ...process.env, ...env }
|
|
14
|
-
});
|
|
15
|
-
this.rl = readline.createInterface({
|
|
16
|
-
input: this.process.stdout,
|
|
17
|
-
crlfDelay: Infinity
|
|
18
|
-
});
|
|
19
|
-
this.rl.on('line', (line) => {
|
|
20
|
-
try {
|
|
21
|
-
const response = JSON.parse(line);
|
|
22
|
-
if (response.id && this.pendingRequests.has(response.id)) {
|
|
23
|
-
const { resolve, reject } = this.pendingRequests.get(response.id);
|
|
24
|
-
this.pendingRequests.delete(response.id);
|
|
25
|
-
if (response.error) {
|
|
26
|
-
reject(response.error);
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
resolve(response.result);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
else if (response.method) {
|
|
33
|
-
console.log('Notification:', response);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
console.error('Failed to parse response:', line);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
this.process.stderr.on('data', (data) => {
|
|
41
|
-
console.error('Server stderr:', data.toString());
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
async sendRequest(method, params = {}) {
|
|
45
|
-
const id = this.requestId++;
|
|
46
|
-
const request = {
|
|
47
|
-
jsonrpc: '2.0',
|
|
48
|
-
id,
|
|
49
|
-
method,
|
|
50
|
-
params
|
|
51
|
-
};
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
this.pendingRequests.set(id, { resolve, reject });
|
|
54
|
-
this.process.stdin.write(JSON.stringify(request) + '\n');
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
close() {
|
|
58
|
-
this.rl.close();
|
|
59
|
-
this.process.kill();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
async function main() {
|
|
63
|
-
const apiKey = process.env['SOCKET_API_KEY'];
|
|
64
|
-
if (!apiKey) {
|
|
65
|
-
console.error('Error: SOCKET_API_KEY environment variable is required');
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
console.log('Starting MCP server debug client...');
|
|
69
|
-
const serverPath = join(import.meta.dirname, '..', 'index.ts');
|
|
70
|
-
console.log(`Using server script: ${serverPath}`);
|
|
71
|
-
const client = new SimpleJSONRPCClient('node', ['--experimental-strip-types', serverPath], {
|
|
72
|
-
SOCKET_API_KEY: apiKey
|
|
73
|
-
});
|
|
74
|
-
try {
|
|
75
|
-
console.log('\n1. Initializing connection...');
|
|
76
|
-
const initResult = await client.sendRequest('initialize', {
|
|
77
|
-
protocolVersion: '0.1.0',
|
|
78
|
-
capabilities: {},
|
|
79
|
-
clientInfo: {
|
|
80
|
-
name: 'debug-client',
|
|
81
|
-
version: '1.0.0'
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
console.log('Initialize response:', JSON.stringify(initResult, null, 2));
|
|
85
|
-
console.log('\n2. Listing available tools...');
|
|
86
|
-
const toolsResult = await client.sendRequest('tools/list', {});
|
|
87
|
-
console.log('Available tools:', JSON.stringify(toolsResult, null, 2));
|
|
88
|
-
console.log('\n3. Calling depscore tool...');
|
|
89
|
-
const depscoreResult = await client.sendRequest('tools/call', {
|
|
90
|
-
name: 'depscore',
|
|
91
|
-
arguments: {
|
|
92
|
-
packages: [
|
|
93
|
-
{ depname: 'express', ecosystem: 'npm', version: '5.0.1' },
|
|
94
|
-
{ depname: 'lodash', ecosystem: 'npm', version: '4.17.21' },
|
|
95
|
-
{ depname: 'react', ecosystem: 'npm', version: '18.2.0' },
|
|
96
|
-
{ depname: 'flask', ecosystem: 'pypi', version: '2.3.2' },
|
|
97
|
-
{ depname: 'unknown-package', ecosystem: 'npm', version: 'unknown' }
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
console.log('Depscore result:', JSON.stringify(depscoreResult, null, 2));
|
|
102
|
-
console.log('\n4. Testing with minimal input (default to npm)...');
|
|
103
|
-
const minimalResult = await client.sendRequest('tools/call', {
|
|
104
|
-
name: 'depscore',
|
|
105
|
-
arguments: {
|
|
106
|
-
packages: [
|
|
107
|
-
{ depname: 'axios' },
|
|
108
|
-
{ depname: 'typescript' }
|
|
109
|
-
]
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
console.log('Minimal input result:', JSON.stringify(minimalResult, null, 2));
|
|
113
|
-
console.log('\n5. Testing error handling (empty packages)...');
|
|
114
|
-
try {
|
|
115
|
-
await client.sendRequest('tools/call', {
|
|
116
|
-
name: 'depscore',
|
|
117
|
-
arguments: {
|
|
118
|
-
packages: []
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
console.log('Expected error:', error);
|
|
124
|
-
}
|
|
125
|
-
console.log('\nDebug session complete!');
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
console.error('Client error:', error);
|
|
129
|
-
}
|
|
130
|
-
finally {
|
|
131
|
-
client.close();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
main().catch(console.error);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["http-client.ts"],"names":[],"mappings":""}
|