@mcpjam/inspector 0.1.0
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/LICENSE +21 -0
- package/README.md +258 -0
- package/cli/build/cli.js +195 -0
- package/cli/build/client/connection.js +33 -0
- package/cli/build/client/index.js +6 -0
- package/cli/build/client/prompts.js +23 -0
- package/cli/build/client/resources.js +30 -0
- package/cli/build/client/tools.js +64 -0
- package/cli/build/client/types.js +1 -0
- package/cli/build/error-handler.js +18 -0
- package/cli/build/index.js +166 -0
- package/cli/build/transport.js +47 -0
- package/client/bin/client.js +57 -0
- package/client/bin/start.js +124 -0
- package/client/dist/assets/OAuthCallback-D9GEIvKa.js +56 -0
- package/client/dist/assets/OAuthDebugCallback-zUpX3mpm.js +45 -0
- package/client/dist/assets/index-CIxCnj5w.css +2312 -0
- package/client/dist/assets/index-DTUWTApO.js +37142 -0
- package/client/dist/assets/oauthUtils-DTcoXpSP.js +33 -0
- package/client/dist/index.html +14 -0
- package/client/dist/mcp.svg +12 -0
- package/package.json +63 -0
- package/server/build/index.js +300 -0
- package/server/build/mcpProxy.js +47 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const parseOAuthCallbackParams = (location) => {
|
|
2
|
+
const params = new URLSearchParams(location);
|
|
3
|
+
const code = params.get("code");
|
|
4
|
+
if (code) {
|
|
5
|
+
return { successful: true, code };
|
|
6
|
+
}
|
|
7
|
+
const error = params.get("error");
|
|
8
|
+
const error_description = params.get("error_description");
|
|
9
|
+
const error_uri = params.get("error_uri");
|
|
10
|
+
if (error) {
|
|
11
|
+
return { successful: false, error, error_description, error_uri };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
successful: false,
|
|
15
|
+
error: "invalid_request",
|
|
16
|
+
error_description: "Missing code or error in response",
|
|
17
|
+
error_uri: null
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const generateOAuthErrorDescription = (params) => {
|
|
21
|
+
const error = params.error;
|
|
22
|
+
const errorDescription = params.error_description;
|
|
23
|
+
const errorUri = params.error_uri;
|
|
24
|
+
return [
|
|
25
|
+
`Error: ${error}.`,
|
|
26
|
+
errorDescription ? `Details: ${errorDescription}.` : "",
|
|
27
|
+
errorUri ? `More info: ${errorUri}.` : ""
|
|
28
|
+
].filter(Boolean).join("\n");
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
generateOAuthErrorDescription as g,
|
|
32
|
+
parseOAuthCallbackParams as p
|
|
33
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/mcp.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>MCP Inspector</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DTUWTApO.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CIxCnj5w.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_19_13)">
|
|
3
|
+
<path d="M18 84.8528L85.8822 16.9706C95.2548 7.59798 110.451 7.59798 119.823 16.9706V16.9706C129.196 26.3431 129.196 41.5391 119.823 50.9117L68.5581 102.177" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
|
4
|
+
<path d="M69.2652 101.47L119.823 50.9117C129.196 41.5391 144.392 41.5391 153.765 50.9117L154.118 51.2652C163.491 60.6378 163.491 75.8338 154.118 85.2063L92.7248 146.6C89.6006 149.724 89.6006 154.789 92.7248 157.913L105.331 170.52" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
|
5
|
+
<path d="M102.853 33.9411L52.6482 84.1457C43.2756 93.5183 43.2756 108.714 52.6482 118.087V118.087C62.0208 127.459 77.2167 127.459 86.5893 118.087L136.794 67.8822" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
|
6
|
+
</g>
|
|
7
|
+
<defs>
|
|
8
|
+
<clipPath id="clip0_19_13">
|
|
9
|
+
<rect width="180" height="180" fill="white"/>
|
|
10
|
+
</clipPath>
|
|
11
|
+
</defs>
|
|
12
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcpjam/inspector",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCPJam inspector",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "MCPJam (https://mcpjam.com)",
|
|
7
|
+
"homepage": "https://mcpjam.com",
|
|
8
|
+
"bugs": "https://github.com/mcpjam/inspector/issues",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"mcp-inspector": "cli/build/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"client/bin",
|
|
15
|
+
"client/dist",
|
|
16
|
+
"server/build",
|
|
17
|
+
"cli/build"
|
|
18
|
+
],
|
|
19
|
+
"workspaces": [
|
|
20
|
+
"client",
|
|
21
|
+
"server",
|
|
22
|
+
"cli"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "npm run build-server && npm run build-client && npm run build-cli",
|
|
26
|
+
"build-server": "cd server && npm run build",
|
|
27
|
+
"build-client": "cd client && npm run build",
|
|
28
|
+
"build-cli": "cd cli && npm run build",
|
|
29
|
+
"clean": "rimraf ./node_modules ./client/node_modules ./cli/node_modules ./build ./client/dist ./server/build ./cli/build ./package-lock.json && npm install",
|
|
30
|
+
"dev": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev\"",
|
|
31
|
+
"dev:windows": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev:windows\"",
|
|
32
|
+
"start": "node client/bin/start.js",
|
|
33
|
+
"start-server": "cd server && npm run start",
|
|
34
|
+
"start-client": "cd client && npm run preview",
|
|
35
|
+
"test": "npm run prettier-check && cd client && npm test",
|
|
36
|
+
"test-cli": "cd cli && npm run test",
|
|
37
|
+
"prettier-fix": "prettier --write .",
|
|
38
|
+
"prettier-check": "prettier --check .",
|
|
39
|
+
"prepare": "npm run build",
|
|
40
|
+
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@mcpjam/inspector-cli": "^0.1.0",
|
|
44
|
+
"@mcpjam/inspector-client": "^0.1.0",
|
|
45
|
+
"@mcpjam/inspector-server": "^0.1.0",
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.11.5",
|
|
47
|
+
"concurrently": "^9.0.1",
|
|
48
|
+
"open": "^10.1.0",
|
|
49
|
+
"shell-quote": "^1.8.2",
|
|
50
|
+
"spawn-rx": "^5.1.2",
|
|
51
|
+
"ts-node": "^10.9.2",
|
|
52
|
+
"zod": "^3.23.8"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/jest": "^29.5.14",
|
|
56
|
+
"@types/node": "^22.7.5",
|
|
57
|
+
"@types/shell-quote": "^1.7.5",
|
|
58
|
+
"jest-fixed-jsdom": "^0.0.9",
|
|
59
|
+
"prettier": "3.3.3",
|
|
60
|
+
"rimraf": "^6.0.1",
|
|
61
|
+
"typescript": "^5.4.2"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { parse as shellParseArgs } from "shell-quote";
|
|
5
|
+
import { SSEClientTransport, SseError, } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
6
|
+
import { StdioClientTransport, getDefaultEnvironment, } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
8
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
10
|
+
import express from "express";
|
|
11
|
+
import { findActualExecutable } from "spawn-rx";
|
|
12
|
+
import mcpProxy from "./mcpProxy.js";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
const SSE_HEADERS_PASSTHROUGH = ["authorization"];
|
|
15
|
+
const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [
|
|
16
|
+
"authorization",
|
|
17
|
+
"mcp-session-id",
|
|
18
|
+
"last-event-id",
|
|
19
|
+
];
|
|
20
|
+
const defaultEnvironment = {
|
|
21
|
+
...getDefaultEnvironment(),
|
|
22
|
+
...(process.env.MCP_ENV_VARS ? JSON.parse(process.env.MCP_ENV_VARS) : {}),
|
|
23
|
+
};
|
|
24
|
+
const { values } = parseArgs({
|
|
25
|
+
args: process.argv.slice(2),
|
|
26
|
+
options: {
|
|
27
|
+
env: { type: "string", default: "" },
|
|
28
|
+
args: { type: "string", default: "" },
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const app = express();
|
|
32
|
+
app.use(cors());
|
|
33
|
+
app.use((req, res, next) => {
|
|
34
|
+
res.header("Access-Control-Expose-Headers", "mcp-session-id");
|
|
35
|
+
next();
|
|
36
|
+
});
|
|
37
|
+
const webAppTransports = new Map(); // Transports by sessionId
|
|
38
|
+
const createTransport = async (req) => {
|
|
39
|
+
const query = req.query;
|
|
40
|
+
console.log("Query parameters:", query);
|
|
41
|
+
const transportType = query.transportType;
|
|
42
|
+
if (transportType === "stdio") {
|
|
43
|
+
const command = query.command;
|
|
44
|
+
const origArgs = shellParseArgs(query.args);
|
|
45
|
+
const queryEnv = query.env ? JSON.parse(query.env) : {};
|
|
46
|
+
const env = { ...process.env, ...defaultEnvironment, ...queryEnv };
|
|
47
|
+
const { cmd, args } = findActualExecutable(command, origArgs);
|
|
48
|
+
console.log(`Stdio transport: command=${cmd}, args=${args}`);
|
|
49
|
+
const transport = new StdioClientTransport({
|
|
50
|
+
command: cmd,
|
|
51
|
+
args,
|
|
52
|
+
env,
|
|
53
|
+
stderr: "pipe",
|
|
54
|
+
});
|
|
55
|
+
await transport.start();
|
|
56
|
+
console.log("Spawned stdio transport");
|
|
57
|
+
return transport;
|
|
58
|
+
}
|
|
59
|
+
else if (transportType === "sse") {
|
|
60
|
+
const url = query.url;
|
|
61
|
+
const headers = {
|
|
62
|
+
Accept: "text/event-stream",
|
|
63
|
+
};
|
|
64
|
+
for (const key of SSE_HEADERS_PASSTHROUGH) {
|
|
65
|
+
if (req.headers[key] === undefined) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const value = req.headers[key];
|
|
69
|
+
headers[key] = Array.isArray(value) ? value[value.length - 1] : value;
|
|
70
|
+
}
|
|
71
|
+
console.log(`SSE transport: url=${url}, headers=${Object.keys(headers)}`);
|
|
72
|
+
const transport = new SSEClientTransport(new URL(url), {
|
|
73
|
+
eventSourceInit: {
|
|
74
|
+
fetch: (url, init) => fetch(url, { ...init, headers }),
|
|
75
|
+
},
|
|
76
|
+
requestInit: {
|
|
77
|
+
headers,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
await transport.start();
|
|
81
|
+
console.log("Connected to SSE transport");
|
|
82
|
+
return transport;
|
|
83
|
+
}
|
|
84
|
+
else if (transportType === "streamable-http") {
|
|
85
|
+
const headers = {
|
|
86
|
+
Accept: "text/event-stream, application/json",
|
|
87
|
+
};
|
|
88
|
+
for (const key of STREAMABLE_HTTP_HEADERS_PASSTHROUGH) {
|
|
89
|
+
if (req.headers[key] === undefined) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const value = req.headers[key];
|
|
93
|
+
headers[key] = Array.isArray(value) ? value[value.length - 1] : value;
|
|
94
|
+
}
|
|
95
|
+
const transport = new StreamableHTTPClientTransport(new URL(query.url), {
|
|
96
|
+
requestInit: {
|
|
97
|
+
headers,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
await transport.start();
|
|
101
|
+
console.log("Connected to Streamable HTTP transport");
|
|
102
|
+
return transport;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.error(`Invalid transport type: ${transportType}`);
|
|
106
|
+
throw new Error("Invalid transport type specified");
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
let backingServerTransport;
|
|
110
|
+
app.get("/mcp", async (req, res) => {
|
|
111
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
112
|
+
console.log(`Received GET message for sessionId ${sessionId}`);
|
|
113
|
+
try {
|
|
114
|
+
const transport = webAppTransports.get(sessionId);
|
|
115
|
+
if (!transport) {
|
|
116
|
+
res.status(404).end("Session not found");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
await transport.handleRequest(req, res);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.error("Error in /mcp route:", error);
|
|
125
|
+
res.status(500).json(error);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
app.post("/mcp", async (req, res) => {
|
|
129
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
130
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
131
|
+
if (!sessionId) {
|
|
132
|
+
try {
|
|
133
|
+
console.log("New streamable-http connection");
|
|
134
|
+
try {
|
|
135
|
+
await backingServerTransport?.close();
|
|
136
|
+
backingServerTransport = await createTransport(req);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (error instanceof SseError && error.code === 401) {
|
|
140
|
+
console.error("Received 401 Unauthorized from MCP server:", error.message);
|
|
141
|
+
res.status(401).json(error);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
console.log("Connected MCP client to backing server transport");
|
|
147
|
+
const webAppTransport = new StreamableHTTPServerTransport({
|
|
148
|
+
sessionIdGenerator: randomUUID,
|
|
149
|
+
onsessioninitialized: (sessionId) => {
|
|
150
|
+
webAppTransports.set(sessionId, webAppTransport);
|
|
151
|
+
console.log("Created streamable web app transport " + sessionId);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
await webAppTransport.start();
|
|
155
|
+
mcpProxy({
|
|
156
|
+
transportToClient: webAppTransport,
|
|
157
|
+
transportToServer: backingServerTransport,
|
|
158
|
+
});
|
|
159
|
+
await webAppTransport.handleRequest(req, res, req.body);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
console.error("Error in /mcp POST route:", error);
|
|
163
|
+
res.status(500).json(error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
try {
|
|
168
|
+
const transport = webAppTransports.get(sessionId);
|
|
169
|
+
if (!transport) {
|
|
170
|
+
res.status(404).end("Transport not found for sessionId " + sessionId);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
await transport.handleRequest(req, res);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.error("Error in /mcp route:", error);
|
|
178
|
+
res.status(500).json(error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
app.get("/stdio", async (req, res) => {
|
|
183
|
+
try {
|
|
184
|
+
console.log("New connection");
|
|
185
|
+
try {
|
|
186
|
+
await backingServerTransport?.close();
|
|
187
|
+
backingServerTransport = await createTransport(req);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (error instanceof SseError && error.code === 401) {
|
|
191
|
+
console.error("Received 401 Unauthorized from MCP server:", error.message);
|
|
192
|
+
res.status(401).json(error);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
console.log("Connected MCP client to backing server transport");
|
|
198
|
+
const webAppTransport = new SSEServerTransport("/message", res);
|
|
199
|
+
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
200
|
+
console.log("Created web app transport");
|
|
201
|
+
await webAppTransport.start();
|
|
202
|
+
backingServerTransport.stderr.on("data", (chunk) => {
|
|
203
|
+
webAppTransport.send({
|
|
204
|
+
jsonrpc: "2.0",
|
|
205
|
+
method: "notifications/stderr",
|
|
206
|
+
params: {
|
|
207
|
+
content: chunk.toString(),
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
mcpProxy({
|
|
212
|
+
transportToClient: webAppTransport,
|
|
213
|
+
transportToServer: backingServerTransport,
|
|
214
|
+
});
|
|
215
|
+
console.log("Set up MCP proxy");
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
console.error("Error in /stdio route:", error);
|
|
219
|
+
res.status(500).json(error);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
app.get("/sse", async (req, res) => {
|
|
223
|
+
try {
|
|
224
|
+
console.log("New SSE connection. NOTE: The sse transport is deprecated and has been replaced by streamable-http");
|
|
225
|
+
try {
|
|
226
|
+
await backingServerTransport?.close();
|
|
227
|
+
backingServerTransport = await createTransport(req);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
if (error instanceof SseError && error.code === 401) {
|
|
231
|
+
console.error("Received 401 Unauthorized from MCP server:", error.message);
|
|
232
|
+
res.status(401).json(error);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
console.log("Connected MCP client to backing server transport");
|
|
238
|
+
const webAppTransport = new SSEServerTransport("/message", res);
|
|
239
|
+
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
240
|
+
console.log("Created web app transport");
|
|
241
|
+
await webAppTransport.start();
|
|
242
|
+
mcpProxy({
|
|
243
|
+
transportToClient: webAppTransport,
|
|
244
|
+
transportToServer: backingServerTransport,
|
|
245
|
+
});
|
|
246
|
+
console.log("Set up MCP proxy");
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
console.error("Error in /sse route:", error);
|
|
250
|
+
res.status(500).json(error);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
app.post("/message", async (req, res) => {
|
|
254
|
+
try {
|
|
255
|
+
const sessionId = req.query.sessionId;
|
|
256
|
+
console.log(`Received message for sessionId ${sessionId}`);
|
|
257
|
+
const transport = webAppTransports.get(sessionId);
|
|
258
|
+
if (!transport) {
|
|
259
|
+
res.status(404).end("Session not found");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
await transport.handlePostMessage(req, res);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
console.error("Error in /message route:", error);
|
|
266
|
+
res.status(500).json(error);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
app.get("/health", (req, res) => {
|
|
270
|
+
res.json({
|
|
271
|
+
status: "ok",
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
app.get("/config", (req, res) => {
|
|
275
|
+
try {
|
|
276
|
+
res.json({
|
|
277
|
+
defaultEnvironment,
|
|
278
|
+
defaultCommand: values.env,
|
|
279
|
+
defaultArgs: values.args,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.error("Error in /config route:", error);
|
|
284
|
+
res.status(500).json(error);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
const PORT = process.env.PORT || 6277;
|
|
288
|
+
const server = app.listen(PORT);
|
|
289
|
+
server.on("listening", () => {
|
|
290
|
+
console.log(`⚙️ Proxy server listening on port ${PORT}`);
|
|
291
|
+
});
|
|
292
|
+
server.on("error", (err) => {
|
|
293
|
+
if (err.message.includes(`EADDRINUSE`)) {
|
|
294
|
+
console.error(`❌ Proxy Server PORT IS IN USE at port ${PORT} ❌ `);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
console.error(err.message);
|
|
298
|
+
}
|
|
299
|
+
process.exit(1);
|
|
300
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
function onClientError(error) {
|
|
3
|
+
console.error("Error from inspector client:", error);
|
|
4
|
+
}
|
|
5
|
+
function onServerError(error) {
|
|
6
|
+
console.error("Error from MCP server:", error);
|
|
7
|
+
}
|
|
8
|
+
export default function mcpProxy({ transportToClient, transportToServer, }) {
|
|
9
|
+
let transportToClientClosed = false;
|
|
10
|
+
let transportToServerClosed = false;
|
|
11
|
+
transportToClient.onmessage = (message) => {
|
|
12
|
+
transportToServer.send(message).catch((error) => {
|
|
13
|
+
// Send error response back to client if it was a request (has id) and connection is still open
|
|
14
|
+
if (isJSONRPCRequest(message) && !transportToClientClosed) {
|
|
15
|
+
const errorResponse = {
|
|
16
|
+
jsonrpc: "2.0",
|
|
17
|
+
id: message.id,
|
|
18
|
+
error: {
|
|
19
|
+
code: -32001,
|
|
20
|
+
message: error.message,
|
|
21
|
+
data: error,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
transportToClient.send(errorResponse).catch(onClientError);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
transportToServer.onmessage = (message) => {
|
|
29
|
+
transportToClient.send(message).catch(onClientError);
|
|
30
|
+
};
|
|
31
|
+
transportToClient.onclose = () => {
|
|
32
|
+
if (transportToServerClosed) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
transportToClientClosed = true;
|
|
36
|
+
transportToServer.close().catch(onServerError);
|
|
37
|
+
};
|
|
38
|
+
transportToServer.onclose = () => {
|
|
39
|
+
if (transportToClientClosed) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
transportToServerClosed = true;
|
|
43
|
+
transportToClient.close().catch(onClientError);
|
|
44
|
+
};
|
|
45
|
+
transportToClient.onerror = onClientError;
|
|
46
|
+
transportToServer.onerror = onServerError;
|
|
47
|
+
}
|