@modelcontextprotocol/inspector 0.13.0 → 0.14.1
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/README.md +66 -0
- package/client/bin/start.js +3 -1
- package/client/dist/assets/{OAuthCallback-DxvBcBuO.js → OAuthCallback-D7GXYYW8.js} +1 -1
- package/client/dist/assets/{OAuthDebugCallback-cWniOlCj.js → OAuthDebugCallback-25cy6Vcm.js} +14 -2
- package/client/dist/assets/{index-CIxCnj5w.css → index-C1MLjTGf.css} +111 -5
- package/client/dist/assets/{index-Dhd4mhZ2.js → index-TghXC4TH.js} +8210 -7608
- package/client/dist/index.html +2 -2
- package/package.json +6 -5
- package/server/build/index.js +205 -76
- package/server/build/mcpProxy.js +16 -1
package/client/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/mcp.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>MCP Inspector</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-TghXC4TH.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C1MLjTGf.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelcontextprotocol/inspector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Model Context Protocol inspector",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
|
@@ -36,14 +36,15 @@
|
|
|
36
36
|
"test-cli": "cd cli && npm run test",
|
|
37
37
|
"prettier-fix": "prettier --write .",
|
|
38
38
|
"prettier-check": "prettier --check .",
|
|
39
|
+
"lint": "prettier --check . && cd client && npm run lint",
|
|
39
40
|
"prepare": "npm run build",
|
|
40
41
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
|
-
"@modelcontextprotocol/inspector-cli": "^0.
|
|
44
|
-
"@modelcontextprotocol/inspector-client": "^0.
|
|
45
|
-
"@modelcontextprotocol/inspector-server": "^0.
|
|
46
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
44
|
+
"@modelcontextprotocol/inspector-cli": "^0.14.1",
|
|
45
|
+
"@modelcontextprotocol/inspector-client": "^0.14.1",
|
|
46
|
+
"@modelcontextprotocol/inspector-server": "^0.14.1",
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
47
48
|
"concurrently": "^9.0.1",
|
|
48
49
|
"open": "^10.1.0",
|
|
49
50
|
"shell-quote": "^1.8.2",
|
package/server/build/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
10
10
|
import express from "express";
|
|
11
11
|
import { findActualExecutable } from "spawn-rx";
|
|
12
12
|
import mcpProxy from "./mcpProxy.js";
|
|
13
|
-
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { randomUUID, randomBytes, timingSafeEqual } from "node:crypto";
|
|
14
14
|
const SSE_HEADERS_PASSTHROUGH = ["authorization"];
|
|
15
15
|
const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [
|
|
16
16
|
"authorization",
|
|
@@ -28,16 +28,100 @@ const { values } = parseArgs({
|
|
|
28
28
|
args: { type: "string", default: "" },
|
|
29
29
|
},
|
|
30
30
|
});
|
|
31
|
+
// Function to get HTTP headers.
|
|
32
|
+
// Supports only "sse" and "streamable-http" transport types.
|
|
33
|
+
const getHttpHeaders = (req, transportType) => {
|
|
34
|
+
const headers = {
|
|
35
|
+
Accept: transportType === "sse"
|
|
36
|
+
? "text/event-stream"
|
|
37
|
+
: "text/event-stream, application/json",
|
|
38
|
+
};
|
|
39
|
+
const defaultHeaders = transportType === "sse"
|
|
40
|
+
? SSE_HEADERS_PASSTHROUGH
|
|
41
|
+
: STREAMABLE_HTTP_HEADERS_PASSTHROUGH;
|
|
42
|
+
for (const key of defaultHeaders) {
|
|
43
|
+
if (req.headers[key] === undefined) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const value = req.headers[key];
|
|
47
|
+
headers[key] = Array.isArray(value) ? value[value.length - 1] : value;
|
|
48
|
+
}
|
|
49
|
+
// If the header "x-custom-auth-header" is present, use its value as the custom header name.
|
|
50
|
+
if (req.headers["x-custom-auth-header"] !== undefined) {
|
|
51
|
+
const customHeaderName = req.headers["x-custom-auth-header"];
|
|
52
|
+
const lowerCaseHeaderName = customHeaderName.toLowerCase();
|
|
53
|
+
if (req.headers[lowerCaseHeaderName] !== undefined) {
|
|
54
|
+
const value = req.headers[lowerCaseHeaderName];
|
|
55
|
+
headers[customHeaderName] = value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return headers;
|
|
59
|
+
};
|
|
31
60
|
const app = express();
|
|
32
61
|
app.use(cors());
|
|
33
62
|
app.use((req, res, next) => {
|
|
34
63
|
res.header("Access-Control-Expose-Headers", "mcp-session-id");
|
|
35
64
|
next();
|
|
36
65
|
});
|
|
37
|
-
const webAppTransports = new Map(); //
|
|
66
|
+
const webAppTransports = new Map(); // Web app transports by web app sessionId
|
|
67
|
+
const serverTransports = new Map(); // Server Transports by web app sessionId
|
|
68
|
+
const sessionToken = randomBytes(32).toString("hex");
|
|
69
|
+
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
|
|
70
|
+
// Origin validation middleware to prevent DNS rebinding attacks
|
|
71
|
+
const originValidationMiddleware = (req, res, next) => {
|
|
72
|
+
const origin = req.headers.origin;
|
|
73
|
+
// Default origins based on CLIENT_PORT or use environment variable
|
|
74
|
+
const clientPort = process.env.CLIENT_PORT || "6274";
|
|
75
|
+
const defaultOrigins = [
|
|
76
|
+
`http://localhost:${clientPort}`,
|
|
77
|
+
`http://127.0.0.1:${clientPort}`,
|
|
78
|
+
];
|
|
79
|
+
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",") || defaultOrigins;
|
|
80
|
+
if (origin && !allowedOrigins.includes(origin)) {
|
|
81
|
+
console.error(`Invalid origin: ${origin}`);
|
|
82
|
+
res.status(403).json({
|
|
83
|
+
error: "Forbidden - invalid origin",
|
|
84
|
+
message: "Request blocked to prevent DNS rebinding attacks. Configure allowed origins via environment variable.",
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
next();
|
|
89
|
+
};
|
|
90
|
+
const authMiddleware = (req, res, next) => {
|
|
91
|
+
if (authDisabled) {
|
|
92
|
+
return next();
|
|
93
|
+
}
|
|
94
|
+
const sendUnauthorized = () => {
|
|
95
|
+
res.status(401).json({
|
|
96
|
+
error: "Unauthorized",
|
|
97
|
+
message: "Authentication required. Use the session token shown in the console when starting the server.",
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const authHeader = req.headers.authorization;
|
|
101
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
102
|
+
sendUnauthorized();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const providedToken = authHeader.substring(7); // Remove 'Bearer ' prefix
|
|
106
|
+
const expectedToken = sessionToken;
|
|
107
|
+
// Convert to buffers for timing-safe comparison
|
|
108
|
+
const providedBuffer = Buffer.from(providedToken);
|
|
109
|
+
const expectedBuffer = Buffer.from(expectedToken);
|
|
110
|
+
// Check length first to prevent timing attacks
|
|
111
|
+
if (providedBuffer.length !== expectedBuffer.length) {
|
|
112
|
+
sendUnauthorized();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Perform timing-safe comparison
|
|
116
|
+
if (!timingSafeEqual(providedBuffer, expectedBuffer)) {
|
|
117
|
+
sendUnauthorized();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
next();
|
|
121
|
+
};
|
|
38
122
|
const createTransport = async (req) => {
|
|
39
123
|
const query = req.query;
|
|
40
|
-
console.log("Query parameters:", query);
|
|
124
|
+
console.log("Query parameters:", JSON.stringify(query));
|
|
41
125
|
const transportType = query.transportType;
|
|
42
126
|
if (transportType === "stdio") {
|
|
43
127
|
const command = query.command;
|
|
@@ -45,7 +129,7 @@ const createTransport = async (req) => {
|
|
|
45
129
|
const queryEnv = query.env ? JSON.parse(query.env) : {};
|
|
46
130
|
const env = { ...process.env, ...defaultEnvironment, ...queryEnv };
|
|
47
131
|
const { cmd, args } = findActualExecutable(command, origArgs);
|
|
48
|
-
console.log(`
|
|
132
|
+
console.log(`STDIO transport: command=${cmd}, args=${args}`);
|
|
49
133
|
const transport = new StdioClientTransport({
|
|
50
134
|
command: cmd,
|
|
51
135
|
args,
|
|
@@ -53,21 +137,11 @@ const createTransport = async (req) => {
|
|
|
53
137
|
stderr: "pipe",
|
|
54
138
|
});
|
|
55
139
|
await transport.start();
|
|
56
|
-
console.log("Spawned stdio transport");
|
|
57
140
|
return transport;
|
|
58
141
|
}
|
|
59
142
|
else if (transportType === "sse") {
|
|
60
143
|
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
|
-
}
|
|
144
|
+
const headers = getHttpHeaders(req, transportType);
|
|
71
145
|
console.log(`SSE transport: url=${url}, headers=${Object.keys(headers)}`);
|
|
72
146
|
const transport = new SSEClientTransport(new URL(url), {
|
|
73
147
|
eventSourceInit: {
|
|
@@ -78,27 +152,16 @@ const createTransport = async (req) => {
|
|
|
78
152
|
},
|
|
79
153
|
});
|
|
80
154
|
await transport.start();
|
|
81
|
-
console.log("Connected to SSE transport");
|
|
82
155
|
return transport;
|
|
83
156
|
}
|
|
84
157
|
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
|
-
}
|
|
158
|
+
const headers = getHttpHeaders(req, transportType);
|
|
95
159
|
const transport = new StreamableHTTPClientTransport(new URL(query.url), {
|
|
96
160
|
requestInit: {
|
|
97
161
|
headers,
|
|
98
162
|
},
|
|
99
163
|
});
|
|
100
164
|
await transport.start();
|
|
101
|
-
console.log("Connected to Streamable HTTP transport");
|
|
102
165
|
return transport;
|
|
103
166
|
}
|
|
104
167
|
else {
|
|
@@ -106,8 +169,7 @@ const createTransport = async (req) => {
|
|
|
106
169
|
throw new Error("Invalid transport type specified");
|
|
107
170
|
}
|
|
108
171
|
};
|
|
109
|
-
|
|
110
|
-
app.get("/mcp", async (req, res) => {
|
|
172
|
+
app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
111
173
|
const sessionId = req.headers["mcp-session-id"];
|
|
112
174
|
console.log(`Received GET message for sessionId ${sessionId}`);
|
|
113
175
|
try {
|
|
@@ -125,15 +187,14 @@ app.get("/mcp", async (req, res) => {
|
|
|
125
187
|
res.status(500).json(error);
|
|
126
188
|
}
|
|
127
189
|
});
|
|
128
|
-
app.post("/mcp", async (req, res) => {
|
|
190
|
+
app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
129
191
|
const sessionId = req.headers["mcp-session-id"];
|
|
130
|
-
|
|
192
|
+
let serverTransport;
|
|
131
193
|
if (!sessionId) {
|
|
132
194
|
try {
|
|
133
|
-
console.log("New
|
|
195
|
+
console.log("New StreamableHttp connection request");
|
|
134
196
|
try {
|
|
135
|
-
await
|
|
136
|
-
backingServerTransport = await createTransport(req);
|
|
197
|
+
serverTransport = await createTransport(req);
|
|
137
198
|
}
|
|
138
199
|
catch (error) {
|
|
139
200
|
if (error instanceof SseError && error.code === 401) {
|
|
@@ -143,18 +204,20 @@ app.post("/mcp", async (req, res) => {
|
|
|
143
204
|
}
|
|
144
205
|
throw error;
|
|
145
206
|
}
|
|
146
|
-
console.log("
|
|
207
|
+
console.log("Created StreamableHttp server transport");
|
|
147
208
|
const webAppTransport = new StreamableHTTPServerTransport({
|
|
148
209
|
sessionIdGenerator: randomUUID,
|
|
149
210
|
onsessioninitialized: (sessionId) => {
|
|
150
211
|
webAppTransports.set(sessionId, webAppTransport);
|
|
151
|
-
|
|
212
|
+
serverTransports.set(sessionId, serverTransport);
|
|
213
|
+
console.log("Client <-> Proxy sessionId: " + sessionId);
|
|
152
214
|
},
|
|
153
215
|
});
|
|
216
|
+
console.log("Created StreamableHttp client transport");
|
|
154
217
|
await webAppTransport.start();
|
|
155
218
|
mcpProxy({
|
|
156
219
|
transportToClient: webAppTransport,
|
|
157
|
-
transportToServer:
|
|
220
|
+
transportToServer: serverTransport,
|
|
158
221
|
});
|
|
159
222
|
await webAppTransport.handleRequest(req, res, req.body);
|
|
160
223
|
}
|
|
@@ -164,6 +227,7 @@ app.post("/mcp", async (req, res) => {
|
|
|
164
227
|
}
|
|
165
228
|
}
|
|
166
229
|
else {
|
|
230
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
167
231
|
try {
|
|
168
232
|
const transport = webAppTransports.get(sessionId);
|
|
169
233
|
if (!transport) {
|
|
@@ -179,81 +243,134 @@ app.post("/mcp", async (req, res) => {
|
|
|
179
243
|
}
|
|
180
244
|
}
|
|
181
245
|
});
|
|
182
|
-
app.
|
|
246
|
+
app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
247
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
248
|
+
console.log(`Received DELETE message for sessionId ${sessionId}`);
|
|
249
|
+
let serverTransport;
|
|
250
|
+
if (sessionId) {
|
|
251
|
+
try {
|
|
252
|
+
serverTransport = serverTransports.get(sessionId);
|
|
253
|
+
if (!serverTransport) {
|
|
254
|
+
res.status(404).end("Transport not found for sessionId " + sessionId);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
await serverTransport.terminateSession();
|
|
258
|
+
webAppTransports.delete(sessionId);
|
|
259
|
+
serverTransports.delete(sessionId);
|
|
260
|
+
console.log(`Transports removed for sessionId ${sessionId}`);
|
|
261
|
+
}
|
|
262
|
+
res.status(200).end();
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
console.error("Error in /mcp route:", error);
|
|
266
|
+
res.status(500).json(error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
183
271
|
try {
|
|
184
|
-
console.log("New connection");
|
|
272
|
+
console.log("New STDIO connection request");
|
|
273
|
+
let serverTransport;
|
|
185
274
|
try {
|
|
186
|
-
await
|
|
187
|
-
|
|
275
|
+
serverTransport = await createTransport(req);
|
|
276
|
+
console.log("Created server transport");
|
|
188
277
|
}
|
|
189
278
|
catch (error) {
|
|
190
279
|
if (error instanceof SseError && error.code === 401) {
|
|
191
|
-
console.error("Received 401 Unauthorized from MCP server
|
|
280
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
192
281
|
res.status(401).json(error);
|
|
193
282
|
return;
|
|
194
283
|
}
|
|
195
284
|
throw error;
|
|
196
285
|
}
|
|
197
|
-
console.log("Connected MCP client to backing server transport");
|
|
198
286
|
const webAppTransport = new SSEServerTransport("/message", res);
|
|
287
|
+
console.log("Created client transport");
|
|
199
288
|
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
200
|
-
|
|
289
|
+
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
201
290
|
await webAppTransport.start();
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
291
|
+
serverTransport.stderr.on("data", (chunk) => {
|
|
292
|
+
if (chunk.toString().includes("MODULE_NOT_FOUND")) {
|
|
293
|
+
webAppTransport.send({
|
|
294
|
+
jsonrpc: "2.0",
|
|
295
|
+
method: "notifications/stderr",
|
|
296
|
+
params: {
|
|
297
|
+
content: "Command not found, transports removed",
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
webAppTransport.close();
|
|
301
|
+
serverTransport.close();
|
|
302
|
+
webAppTransports.delete(webAppTransport.sessionId);
|
|
303
|
+
serverTransports.delete(webAppTransport.sessionId);
|
|
304
|
+
console.error("Command not found, transports removed");
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
webAppTransport.send({
|
|
308
|
+
jsonrpc: "2.0",
|
|
309
|
+
method: "notifications/stderr",
|
|
310
|
+
params: {
|
|
311
|
+
content: chunk.toString(),
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
210
315
|
});
|
|
211
316
|
mcpProxy({
|
|
212
317
|
transportToClient: webAppTransport,
|
|
213
|
-
transportToServer:
|
|
318
|
+
transportToServer: serverTransport,
|
|
214
319
|
});
|
|
215
|
-
console.log("Set up MCP proxy");
|
|
216
320
|
}
|
|
217
321
|
catch (error) {
|
|
218
322
|
console.error("Error in /stdio route:", error);
|
|
219
323
|
res.status(500).json(error);
|
|
220
324
|
}
|
|
221
325
|
});
|
|
222
|
-
app.get("/sse", async (req, res) => {
|
|
326
|
+
app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
223
327
|
try {
|
|
224
|
-
console.log("New SSE connection. NOTE: The sse transport is deprecated and has been replaced by
|
|
328
|
+
console.log("New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp");
|
|
329
|
+
let serverTransport;
|
|
225
330
|
try {
|
|
226
|
-
await
|
|
227
|
-
backingServerTransport = await createTransport(req);
|
|
331
|
+
serverTransport = await createTransport(req);
|
|
228
332
|
}
|
|
229
333
|
catch (error) {
|
|
230
334
|
if (error instanceof SseError && error.code === 401) {
|
|
231
|
-
console.error("Received 401 Unauthorized from MCP server
|
|
335
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
232
336
|
res.status(401).json(error);
|
|
233
337
|
return;
|
|
234
338
|
}
|
|
235
|
-
|
|
339
|
+
else if (error instanceof SseError && error.code === 404) {
|
|
340
|
+
console.error("Received 404 not found from MCP server. Does the MCP server support SSE?");
|
|
341
|
+
res.status(404).json(error);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
else if (JSON.stringify(error).includes("ECONNREFUSED")) {
|
|
345
|
+
console.error("Connection refused. Is the MCP server running?");
|
|
346
|
+
res.status(500).json(error);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (serverTransport) {
|
|
353
|
+
const webAppTransport = new SSEServerTransport("/message", res);
|
|
354
|
+
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
355
|
+
console.log("Created client transport");
|
|
356
|
+
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
357
|
+
console.log("Created server transport");
|
|
358
|
+
await webAppTransport.start();
|
|
359
|
+
mcpProxy({
|
|
360
|
+
transportToClient: webAppTransport,
|
|
361
|
+
transportToServer: serverTransport,
|
|
362
|
+
});
|
|
236
363
|
}
|
|
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
364
|
}
|
|
248
365
|
catch (error) {
|
|
249
366
|
console.error("Error in /sse route:", error);
|
|
250
367
|
res.status(500).json(error);
|
|
251
368
|
}
|
|
252
369
|
});
|
|
253
|
-
app.post("/message", async (req, res) => {
|
|
370
|
+
app.post("/message", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
254
371
|
try {
|
|
255
372
|
const sessionId = req.query.sessionId;
|
|
256
|
-
console.log(`Received message for sessionId ${sessionId}`);
|
|
373
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
257
374
|
const transport = webAppTransports.get(sessionId);
|
|
258
375
|
if (!transport) {
|
|
259
376
|
res.status(404).end("Session not found");
|
|
@@ -271,7 +388,7 @@ app.get("/health", (req, res) => {
|
|
|
271
388
|
status: "ok",
|
|
272
389
|
});
|
|
273
390
|
});
|
|
274
|
-
app.get("/config", (req, res) => {
|
|
391
|
+
app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => {
|
|
275
392
|
try {
|
|
276
393
|
res.json({
|
|
277
394
|
defaultEnvironment,
|
|
@@ -284,10 +401,22 @@ app.get("/config", (req, res) => {
|
|
|
284
401
|
res.status(500).json(error);
|
|
285
402
|
}
|
|
286
403
|
});
|
|
287
|
-
const PORT = process.env.PORT || 6277;
|
|
288
|
-
const
|
|
404
|
+
const PORT = parseInt(process.env.PORT || "6277", 10);
|
|
405
|
+
const HOST = process.env.HOST || "127.0.0.1";
|
|
406
|
+
const server = app.listen(PORT, HOST);
|
|
289
407
|
server.on("listening", () => {
|
|
290
|
-
console.log(`⚙️ Proxy server listening on
|
|
408
|
+
console.log(`⚙️ Proxy server listening on ${HOST}:${PORT}`);
|
|
409
|
+
if (!authDisabled) {
|
|
410
|
+
console.log(`🔑 Session token: ${sessionToken}`);
|
|
411
|
+
console.log(`Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth`);
|
|
412
|
+
// Display clickable URL with pre-filled token
|
|
413
|
+
const clientPort = process.env.CLIENT_PORT || "6274";
|
|
414
|
+
const clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`;
|
|
415
|
+
console.log(`\n🔗 Open inspector with token pre-filled:\n ${clientUrl}\n (Auto-open is disabled when authentication is enabled)\n`);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
console.log(`⚠️ WARNING: Authentication is disabled. This is not recommended.`);
|
|
419
|
+
}
|
|
291
420
|
});
|
|
292
421
|
server.on("error", (err) => {
|
|
293
422
|
if (err.message.includes(`EADDRINUSE`)) {
|
package/server/build/mcpProxy.js
CHANGED
|
@@ -3,11 +3,19 @@ function onClientError(error) {
|
|
|
3
3
|
console.error("Error from inspector client:", error);
|
|
4
4
|
}
|
|
5
5
|
function onServerError(error) {
|
|
6
|
-
|
|
6
|
+
if ((error?.message &&
|
|
7
|
+
error.message.includes("Error POSTing to endpoint (HTTP 404)")) ||
|
|
8
|
+
(error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED"))) {
|
|
9
|
+
console.error("Connection refused. Is the MCP server running?");
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
console.error("Error from MCP server:", error);
|
|
13
|
+
}
|
|
7
14
|
}
|
|
8
15
|
export default function mcpProxy({ transportToClient, transportToServer, }) {
|
|
9
16
|
let transportToClientClosed = false;
|
|
10
17
|
let transportToServerClosed = false;
|
|
18
|
+
let reportedServerSession = false;
|
|
11
19
|
transportToClient.onmessage = (message) => {
|
|
12
20
|
transportToServer.send(message).catch((error) => {
|
|
13
21
|
// Send error response back to client if it was a request (has id) and connection is still open
|
|
@@ -26,6 +34,13 @@ export default function mcpProxy({ transportToClient, transportToServer, }) {
|
|
|
26
34
|
});
|
|
27
35
|
};
|
|
28
36
|
transportToServer.onmessage = (message) => {
|
|
37
|
+
if (!reportedServerSession) {
|
|
38
|
+
if (transportToServer.sessionId) {
|
|
39
|
+
// Can only report for StreamableHttp
|
|
40
|
+
console.error("Proxy <-> Server sessionId: " + transportToServer.sessionId);
|
|
41
|
+
}
|
|
42
|
+
reportedServerSession = true;
|
|
43
|
+
}
|
|
29
44
|
transportToClient.send(message).catch(onClientError);
|
|
30
45
|
};
|
|
31
46
|
transportToClient.onclose = () => {
|