@modelcontextprotocol/inspector 0.16.5 → 0.16.7
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 +39 -1
- package/cli/build/cli.js +34 -3
- package/cli/build/client/prompts.js +16 -1
- package/cli/build/client/tools.js +12 -2
- package/cli/build/index.js +32 -4
- package/cli/build/transport.js +16 -2
- package/cli/build/utils/awaitable-log.js +7 -0
- package/client/dist/assets/{OAuthCallback-BxZPCak0.js → OAuthCallback-aXDAN43J.js} +1 -1
- package/client/dist/assets/{OAuthDebugCallback-CwfxQhTF.js → OAuthDebugCallback-BanjyQ_V.js} +9 -1
- package/client/dist/assets/{index-BWZfLo5E.css → index-DZ1A0VH0.css} +37 -10
- package/client/dist/assets/{index-BIvDs4Sc.js → index-Dg7mXHRE.js} +230 -110
- package/client/dist/index.html +2 -2
- package/package.json +13 -6
- package/server/build/index.js +242 -126
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-Dg7mXHRE.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DZ1A0VH0.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root" class="w-full"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelcontextprotocol/inspector",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.7",
|
|
4
4
|
"description": "Model Context Protocol inspector",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
|
@@ -41,16 +41,16 @@
|
|
|
41
41
|
"prettier-fix": "prettier --write .",
|
|
42
42
|
"prettier-check": "prettier --check .",
|
|
43
43
|
"lint": "prettier --check . && cd client && npm run lint",
|
|
44
|
-
"prepare": "npm run build",
|
|
44
|
+
"prepare": "husky && npm run build",
|
|
45
45
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public",
|
|
46
46
|
"update-version": "node scripts/update-version.js",
|
|
47
47
|
"check-version": "node scripts/check-version-consistency.js"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@modelcontextprotocol/inspector-cli": "^0.16.
|
|
51
|
-
"@modelcontextprotocol/inspector-client": "^0.16.
|
|
52
|
-
"@modelcontextprotocol/inspector-server": "^0.16.
|
|
53
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
50
|
+
"@modelcontextprotocol/inspector-cli": "^0.16.7",
|
|
51
|
+
"@modelcontextprotocol/inspector-client": "^0.16.7",
|
|
52
|
+
"@modelcontextprotocol/inspector-server": "^0.16.7",
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.18.0",
|
|
54
54
|
"concurrently": "^9.2.0",
|
|
55
55
|
"open": "^10.2.0",
|
|
56
56
|
"shell-quote": "^1.8.3",
|
|
@@ -63,12 +63,19 @@
|
|
|
63
63
|
"@types/jest": "^29.5.14",
|
|
64
64
|
"@types/node": "^22.17.0",
|
|
65
65
|
"@types/shell-quote": "^1.7.5",
|
|
66
|
+
"husky": "^9.1.7",
|
|
66
67
|
"jest-fixed-jsdom": "^0.0.9",
|
|
68
|
+
"lint-staged": "^16.1.5",
|
|
67
69
|
"prettier": "^3.6.2",
|
|
68
70
|
"rimraf": "^6.0.1",
|
|
69
71
|
"typescript": "^5.4.2"
|
|
70
72
|
},
|
|
71
73
|
"engines": {
|
|
72
74
|
"node": ">=22.7.5"
|
|
75
|
+
},
|
|
76
|
+
"lint-staged": {
|
|
77
|
+
"**/*.{js,ts,jsx,tsx,json,md}": [
|
|
78
|
+
"prettier --write"
|
|
79
|
+
]
|
|
73
80
|
}
|
|
74
81
|
}
|
package/server/build/index.js
CHANGED
|
@@ -12,12 +12,6 @@ import { findActualExecutable } from "spawn-rx";
|
|
|
12
12
|
import mcpProxy from "./mcpProxy.js";
|
|
13
13
|
import { randomUUID, randomBytes, timingSafeEqual } from "node:crypto";
|
|
14
14
|
const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277";
|
|
15
|
-
const SSE_HEADERS_PASSTHROUGH = ["authorization"];
|
|
16
|
-
const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [
|
|
17
|
-
"authorization",
|
|
18
|
-
"mcp-session-id",
|
|
19
|
-
"last-event-id",
|
|
20
|
-
];
|
|
21
15
|
const defaultEnvironment = {
|
|
22
16
|
...getDefaultEnvironment(),
|
|
23
17
|
...(process.env.MCP_ENV_VARS ? JSON.parse(process.env.MCP_ENV_VARS) : {}),
|
|
@@ -33,34 +27,71 @@ const { values } = parseArgs({
|
|
|
33
27
|
},
|
|
34
28
|
});
|
|
35
29
|
// Function to get HTTP headers.
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
const getHttpHeaders = (req) => {
|
|
31
|
+
const headers = {};
|
|
32
|
+
// Iterate over all headers in the request
|
|
33
|
+
for (const key in req.headers) {
|
|
34
|
+
const lowerKey = key.toLowerCase();
|
|
35
|
+
// Check if the header is one we want to forward
|
|
36
|
+
if (lowerKey.startsWith("mcp-") ||
|
|
37
|
+
lowerKey === "authorization" ||
|
|
38
|
+
lowerKey === "last-event-id") {
|
|
39
|
+
// Exclude the proxy's own authentication header
|
|
40
|
+
if (lowerKey !== "x-mcp-proxy-auth") {
|
|
41
|
+
const value = req.headers[key];
|
|
42
|
+
if (typeof value === "string") {
|
|
43
|
+
// If the value is a string, use it directly
|
|
44
|
+
headers[key] = value;
|
|
45
|
+
}
|
|
46
|
+
else if (Array.isArray(value)) {
|
|
47
|
+
// If the value is an array, use the last element
|
|
48
|
+
const lastValue = value.at(-1);
|
|
49
|
+
if (lastValue !== undefined) {
|
|
50
|
+
headers[key] = lastValue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// If value is undefined, it's skipped, which is correct.
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
|
-
const value = req.headers[key];
|
|
51
|
-
headers[key] = Array.isArray(value) ? value[value.length - 1] : value;
|
|
52
56
|
}
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
// Handle the custom auth header separately. We expect `x-custom-auth-header`
|
|
58
|
+
// to be a string containing the name of the actual authentication header.
|
|
59
|
+
const customAuthHeaderName = req.headers["x-custom-auth-header"];
|
|
60
|
+
if (typeof customAuthHeaderName === "string") {
|
|
61
|
+
const lowerCaseHeaderName = customAuthHeaderName.toLowerCase();
|
|
62
|
+
const value = req.headers[lowerCaseHeaderName];
|
|
63
|
+
if (typeof value === "string") {
|
|
64
|
+
headers[customAuthHeaderName] = value;
|
|
65
|
+
}
|
|
66
|
+
else if (Array.isArray(value)) {
|
|
67
|
+
// If the actual auth header was sent multiple times, use the last value.
|
|
68
|
+
const lastValue = value.at(-1);
|
|
69
|
+
if (lastValue !== undefined) {
|
|
70
|
+
headers[customAuthHeaderName] = lastValue;
|
|
71
|
+
}
|
|
60
72
|
}
|
|
61
73
|
}
|
|
62
74
|
return headers;
|
|
63
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Updates a headers object in-place, preserving the original Accept header.
|
|
78
|
+
* This is necessary to ensure that transports holding a reference to the headers
|
|
79
|
+
* object see the updates.
|
|
80
|
+
* @param currentHeaders The headers object to update.
|
|
81
|
+
* @param newHeaders The new headers to apply.
|
|
82
|
+
*/
|
|
83
|
+
const updateHeadersInPlace = (currentHeaders, newHeaders) => {
|
|
84
|
+
// Preserve the Accept header, which is set at transport creation and
|
|
85
|
+
// is not present in subsequent client requests.
|
|
86
|
+
const accept = currentHeaders["Accept"];
|
|
87
|
+
// Clear the old headers and apply the new ones.
|
|
88
|
+
Object.keys(currentHeaders).forEach((key) => delete currentHeaders[key]);
|
|
89
|
+
Object.assign(currentHeaders, newHeaders);
|
|
90
|
+
// Restore the Accept header.
|
|
91
|
+
if (accept) {
|
|
92
|
+
currentHeaders["Accept"] = accept;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
64
95
|
const app = express();
|
|
65
96
|
app.use(cors());
|
|
66
97
|
app.use((req, res, next) => {
|
|
@@ -69,6 +100,7 @@ app.use((req, res, next) => {
|
|
|
69
100
|
});
|
|
70
101
|
const webAppTransports = new Map(); // Web app transports by web app sessionId
|
|
71
102
|
const serverTransports = new Map(); // Server Transports by web app sessionId
|
|
103
|
+
const sessionHeaderHolders = new Map(); // For dynamic header updates
|
|
72
104
|
// Use provided token from environment or generate a new one
|
|
73
105
|
const sessionToken = process.env.MCP_PROXY_AUTH_TOKEN || randomBytes(32).toString("hex");
|
|
74
106
|
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
|
|
@@ -126,12 +158,33 @@ const authMiddleware = (req, res, next) => {
|
|
|
126
158
|
}
|
|
127
159
|
next();
|
|
128
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* Creates a `fetch` function that merges dynamic session headers with the
|
|
163
|
+
* headers from the actual request, ensuring that request-specific headers like
|
|
164
|
+
* `Content-Type` are preserved.
|
|
165
|
+
*/
|
|
166
|
+
const createCustomFetch = (headerHolder) => {
|
|
167
|
+
return (input, init) => {
|
|
168
|
+
// Determine the headers from the original request/init.
|
|
169
|
+
// The SDK may pass a Request object or a URL and an init object.
|
|
170
|
+
const originalHeaders = input instanceof Request ? input.headers : init?.headers;
|
|
171
|
+
// Start with our dynamic session headers.
|
|
172
|
+
const finalHeaders = new Headers(headerHolder.headers);
|
|
173
|
+
// Merge the SDK's request-specific headers, letting them overwrite.
|
|
174
|
+
// This is crucial for preserving Content-Type on POST requests.
|
|
175
|
+
new Headers(originalHeaders).forEach((value, key) => {
|
|
176
|
+
finalHeaders.set(key, value);
|
|
177
|
+
});
|
|
178
|
+
// This works for both `fetch(url, init)` and `fetch(request)` style calls.
|
|
179
|
+
return fetch(input, { ...init, headers: finalHeaders });
|
|
180
|
+
};
|
|
181
|
+
};
|
|
129
182
|
const createTransport = async (req) => {
|
|
130
183
|
const query = req.query;
|
|
131
184
|
console.log("Query parameters:", JSON.stringify(query));
|
|
132
185
|
const transportType = query.transportType;
|
|
133
186
|
if (transportType === "stdio") {
|
|
134
|
-
const command = query.command;
|
|
187
|
+
const command = query.command.trim();
|
|
135
188
|
const origArgs = shellParseArgs(query.args);
|
|
136
189
|
const queryEnv = query.env ? JSON.parse(query.env) : {};
|
|
137
190
|
const env = { ...defaultEnvironment, ...process.env, ...queryEnv };
|
|
@@ -144,32 +197,35 @@ const createTransport = async (req) => {
|
|
|
144
197
|
stderr: "pipe",
|
|
145
198
|
});
|
|
146
199
|
await transport.start();
|
|
147
|
-
return transport;
|
|
200
|
+
return { transport };
|
|
148
201
|
}
|
|
149
202
|
else if (transportType === "sse") {
|
|
150
203
|
const url = query.url;
|
|
151
|
-
const headers = getHttpHeaders(req
|
|
204
|
+
const headers = getHttpHeaders(req);
|
|
205
|
+
headers["Accept"] = "text/event-stream";
|
|
206
|
+
const headerHolder = { headers };
|
|
152
207
|
console.log(`SSE transport: url=${url}, headers=${JSON.stringify(headers)}`);
|
|
153
208
|
const transport = new SSEClientTransport(new URL(url), {
|
|
154
209
|
eventSourceInit: {
|
|
155
|
-
fetch: (
|
|
210
|
+
fetch: createCustomFetch(headerHolder),
|
|
156
211
|
},
|
|
157
212
|
requestInit: {
|
|
158
|
-
headers,
|
|
213
|
+
headers: headerHolder.headers,
|
|
159
214
|
},
|
|
160
215
|
});
|
|
161
216
|
await transport.start();
|
|
162
|
-
return transport;
|
|
217
|
+
return { transport, headerHolder };
|
|
163
218
|
}
|
|
164
219
|
else if (transportType === "streamable-http") {
|
|
165
|
-
const headers = getHttpHeaders(req
|
|
220
|
+
const headers = getHttpHeaders(req);
|
|
221
|
+
headers["Accept"] = "text/event-stream, application/json";
|
|
222
|
+
const headerHolder = { headers };
|
|
166
223
|
const transport = new StreamableHTTPClientTransport(new URL(query.url), {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
224
|
+
// Pass a custom fetch to inject the latest headers on each request
|
|
225
|
+
fetch: createCustomFetch(headerHolder),
|
|
170
226
|
});
|
|
171
227
|
await transport.start();
|
|
172
|
-
return transport;
|
|
228
|
+
return { transport, headerHolder };
|
|
173
229
|
}
|
|
174
230
|
else {
|
|
175
231
|
console.error(`Invalid transport type: ${transportType}`);
|
|
@@ -179,6 +235,10 @@ const createTransport = async (req) => {
|
|
|
179
235
|
app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
180
236
|
const sessionId = req.headers["mcp-session-id"];
|
|
181
237
|
console.log(`Received GET message for sessionId ${sessionId}`);
|
|
238
|
+
const headerHolder = sessionHeaderHolders.get(sessionId);
|
|
239
|
+
if (headerHolder) {
|
|
240
|
+
updateHeadersInPlace(headerHolder.headers, getHttpHeaders(req));
|
|
241
|
+
}
|
|
182
242
|
try {
|
|
183
243
|
const transport = webAppTransports.get(sessionId);
|
|
184
244
|
if (!transport) {
|
|
@@ -196,29 +256,45 @@ app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) =>
|
|
|
196
256
|
});
|
|
197
257
|
app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
198
258
|
const sessionId = req.headers["mcp-session-id"];
|
|
199
|
-
|
|
200
|
-
|
|
259
|
+
if (sessionId) {
|
|
260
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
261
|
+
const headerHolder = sessionHeaderHolders.get(sessionId);
|
|
262
|
+
if (headerHolder) {
|
|
263
|
+
updateHeadersInPlace(headerHolder.headers, getHttpHeaders(req));
|
|
264
|
+
}
|
|
201
265
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
266
|
+
const transport = webAppTransports.get(sessionId);
|
|
267
|
+
if (!transport) {
|
|
268
|
+
res.status(404).end("Transport not found for sessionId " + sessionId);
|
|
205
269
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
console.error("Received 401 Unauthorized from MCP server:", error.message);
|
|
209
|
-
res.status(401).json(error);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
throw error;
|
|
270
|
+
else {
|
|
271
|
+
await transport.handleRequest(req, res);
|
|
213
272
|
}
|
|
214
|
-
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
console.error("Error in /mcp route:", error);
|
|
276
|
+
res.status(500).json(error);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
console.log("New StreamableHttp connection request");
|
|
281
|
+
try {
|
|
282
|
+
const { transport: serverTransport, headerHolder } = await createTransport(req);
|
|
215
283
|
const webAppTransport = new StreamableHTTPServerTransport({
|
|
216
284
|
sessionIdGenerator: randomUUID,
|
|
217
285
|
onsessioninitialized: (sessionId) => {
|
|
218
286
|
webAppTransports.set(sessionId, webAppTransport);
|
|
219
|
-
serverTransports.set(sessionId, serverTransport);
|
|
287
|
+
serverTransports.set(sessionId, serverTransport); // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
288
|
+
if (headerHolder) {
|
|
289
|
+
sessionHeaderHolders.set(sessionId, headerHolder);
|
|
290
|
+
}
|
|
220
291
|
console.log("Client <-> Proxy sessionId: " + sessionId);
|
|
221
292
|
},
|
|
293
|
+
onsessionclosed: (sessionId) => {
|
|
294
|
+
webAppTransports.delete(sessionId);
|
|
295
|
+
serverTransports.delete(sessionId);
|
|
296
|
+
sessionHeaderHolders.delete(sessionId);
|
|
297
|
+
},
|
|
222
298
|
});
|
|
223
299
|
console.log("Created StreamableHttp client transport");
|
|
224
300
|
await webAppTransport.start();
|
|
@@ -229,23 +305,12 @@ app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) =>
|
|
|
229
305
|
await webAppTransport.handleRequest(req, res, req.body);
|
|
230
306
|
}
|
|
231
307
|
catch (error) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
else {
|
|
237
|
-
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
238
|
-
try {
|
|
239
|
-
const transport = webAppTransports.get(sessionId);
|
|
240
|
-
if (!transport) {
|
|
241
|
-
res.status(404).end("Transport not found for sessionId " + sessionId);
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
await transport.handleRequest(req, res);
|
|
308
|
+
if (error instanceof SseError && error.code === 401) {
|
|
309
|
+
console.error("Received 401 Unauthorized from MCP server:", error.message);
|
|
310
|
+
res.status(401).json(error);
|
|
311
|
+
return;
|
|
245
312
|
}
|
|
246
|
-
|
|
247
|
-
catch (error) {
|
|
248
|
-
console.error("Error in /mcp route:", error);
|
|
313
|
+
console.error("Error in /mcp POST route:", error);
|
|
249
314
|
res.status(500).json(error);
|
|
250
315
|
}
|
|
251
316
|
}
|
|
@@ -253,10 +318,9 @@ app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) =>
|
|
|
253
318
|
app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
254
319
|
const sessionId = req.headers["mcp-session-id"];
|
|
255
320
|
console.log(`Received DELETE message for sessionId ${sessionId}`);
|
|
256
|
-
let serverTransport;
|
|
257
321
|
if (sessionId) {
|
|
258
322
|
try {
|
|
259
|
-
serverTransport = serverTransports.get(sessionId);
|
|
323
|
+
const serverTransport = serverTransports.get(sessionId);
|
|
260
324
|
if (!serverTransport) {
|
|
261
325
|
res.status(404).end("Transport not found for sessionId " + sessionId);
|
|
262
326
|
}
|
|
@@ -264,6 +328,7 @@ app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res)
|
|
|
264
328
|
await serverTransport.terminateSession();
|
|
265
329
|
webAppTransports.delete(sessionId);
|
|
266
330
|
serverTransports.delete(sessionId);
|
|
331
|
+
sessionHeaderHolders.delete(sessionId);
|
|
267
332
|
console.log(`Transports removed for sessionId ${sessionId}`);
|
|
268
333
|
}
|
|
269
334
|
res.status(200).end();
|
|
@@ -277,45 +342,91 @@ app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res)
|
|
|
277
342
|
app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
278
343
|
try {
|
|
279
344
|
console.log("New STDIO connection request");
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
catch (error) {
|
|
286
|
-
if (error instanceof SseError && error.code === 401) {
|
|
287
|
-
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
288
|
-
res.status(401).json(error);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
const webAppTransport = new SSEServerTransport("/message", res);
|
|
294
|
-
console.log("Created client transport");
|
|
345
|
+
const { transport: serverTransport } = await createTransport(req);
|
|
346
|
+
const proxyFullAddress = req.query.proxyFullAddress || "";
|
|
347
|
+
const prefix = proxyFullAddress || "";
|
|
348
|
+
const endpoint = `${prefix}/message`;
|
|
349
|
+
const webAppTransport = new SSEServerTransport(endpoint, res);
|
|
295
350
|
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
351
|
+
console.log("Created client transport");
|
|
296
352
|
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
353
|
+
console.log("Created server transport");
|
|
297
354
|
await webAppTransport.start();
|
|
298
355
|
serverTransport.stderr.on("data", (chunk) => {
|
|
299
356
|
if (chunk.toString().includes("MODULE_NOT_FOUND")) {
|
|
357
|
+
// Server command not found, remove transports
|
|
358
|
+
const message = "Command not found, transports removed";
|
|
300
359
|
webAppTransport.send({
|
|
301
360
|
jsonrpc: "2.0",
|
|
302
|
-
method: "notifications/
|
|
361
|
+
method: "notifications/message",
|
|
303
362
|
params: {
|
|
304
|
-
|
|
363
|
+
level: "emergency",
|
|
364
|
+
logger: "proxy",
|
|
365
|
+
data: {
|
|
366
|
+
message,
|
|
367
|
+
},
|
|
305
368
|
},
|
|
306
369
|
});
|
|
307
370
|
webAppTransport.close();
|
|
308
371
|
serverTransport.close();
|
|
309
372
|
webAppTransports.delete(webAppTransport.sessionId);
|
|
310
373
|
serverTransports.delete(webAppTransport.sessionId);
|
|
311
|
-
|
|
374
|
+
sessionHeaderHolders.delete(webAppTransport.sessionId);
|
|
375
|
+
console.error(message);
|
|
312
376
|
}
|
|
313
377
|
else {
|
|
378
|
+
// Inspect message and attempt to assign a RFC 5424 Syslog Protocol level
|
|
379
|
+
let level;
|
|
380
|
+
let message = chunk.toString().trim();
|
|
381
|
+
let ucMsg = chunk.toString().toUpperCase();
|
|
382
|
+
if (ucMsg.includes("DEBUG")) {
|
|
383
|
+
level = "debug";
|
|
384
|
+
}
|
|
385
|
+
else if (ucMsg.includes("INFO")) {
|
|
386
|
+
level = "info";
|
|
387
|
+
}
|
|
388
|
+
else if (ucMsg.includes("NOTICE")) {
|
|
389
|
+
level = "notice";
|
|
390
|
+
}
|
|
391
|
+
else if (ucMsg.includes("WARN")) {
|
|
392
|
+
level = "warning";
|
|
393
|
+
}
|
|
394
|
+
else if (ucMsg.includes("ERROR")) {
|
|
395
|
+
level = "error";
|
|
396
|
+
}
|
|
397
|
+
else if (ucMsg.includes("CRITICAL")) {
|
|
398
|
+
level = "critical";
|
|
399
|
+
}
|
|
400
|
+
else if (ucMsg.includes("ALERT")) {
|
|
401
|
+
level = "alert";
|
|
402
|
+
}
|
|
403
|
+
else if (ucMsg.includes("EMERGENCY")) {
|
|
404
|
+
level = "emergency";
|
|
405
|
+
}
|
|
406
|
+
else if (ucMsg.includes("SIGINT")) {
|
|
407
|
+
message = "SIGINT received. Server shutdown.";
|
|
408
|
+
level = "emergency";
|
|
409
|
+
}
|
|
410
|
+
else if (ucMsg.includes("SIGHUP")) {
|
|
411
|
+
message = "SIGHUP received. Server shutdown.";
|
|
412
|
+
level = "emergency";
|
|
413
|
+
}
|
|
414
|
+
else if (ucMsg.includes("SIGTERM")) {
|
|
415
|
+
message = "SIGTERM received. Server shutdown.";
|
|
416
|
+
level = "emergency";
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
level = "info";
|
|
420
|
+
}
|
|
314
421
|
webAppTransport.send({
|
|
315
422
|
jsonrpc: "2.0",
|
|
316
|
-
method: "notifications/
|
|
423
|
+
method: "notifications/message",
|
|
317
424
|
params: {
|
|
318
|
-
|
|
425
|
+
level,
|
|
426
|
+
logger: "stdio",
|
|
427
|
+
data: {
|
|
428
|
+
message,
|
|
429
|
+
},
|
|
319
430
|
},
|
|
320
431
|
});
|
|
321
432
|
}
|
|
@@ -326,50 +437,51 @@ app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) =
|
|
|
326
437
|
});
|
|
327
438
|
}
|
|
328
439
|
catch (error) {
|
|
440
|
+
if (error instanceof SseError && error.code === 401) {
|
|
441
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
442
|
+
res.status(401).json(error);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
329
445
|
console.error("Error in /stdio route:", error);
|
|
330
446
|
res.status(500).json(error);
|
|
331
447
|
}
|
|
332
448
|
});
|
|
333
449
|
app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
334
450
|
try {
|
|
335
|
-
console.log("New SSE connection request. NOTE: The
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
else if (error instanceof SseError && error.code === 404) {
|
|
347
|
-
console.error("Received 404 not found from MCP server. Does the MCP server support SSE?");
|
|
348
|
-
res.status(404).json(error);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
else if (JSON.stringify(error).includes("ECONNREFUSED")) {
|
|
352
|
-
console.error("Connection refused. Is the MCP server running?");
|
|
353
|
-
res.status(500).json(error);
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
throw error;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (serverTransport) {
|
|
360
|
-
const webAppTransport = new SSEServerTransport("/message", res);
|
|
361
|
-
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
362
|
-
console.log("Created client transport");
|
|
363
|
-
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
364
|
-
console.log("Created server transport");
|
|
365
|
-
await webAppTransport.start();
|
|
366
|
-
mcpProxy({
|
|
367
|
-
transportToClient: webAppTransport,
|
|
368
|
-
transportToServer: serverTransport,
|
|
369
|
-
});
|
|
451
|
+
console.log("New SSE connection request. NOTE: The SSE transport is deprecated and has been replaced by StreamableHttp");
|
|
452
|
+
const { transport: serverTransport, headerHolder } = await createTransport(req);
|
|
453
|
+
const proxyFullAddress = req.query.proxyFullAddress || "";
|
|
454
|
+
const prefix = proxyFullAddress || "";
|
|
455
|
+
const endpoint = `${prefix}/message`;
|
|
456
|
+
const webAppTransport = new SSEServerTransport(endpoint, res);
|
|
457
|
+
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
458
|
+
console.log("Created client transport");
|
|
459
|
+
serverTransports.set(webAppTransport.sessionId, serverTransport); // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
460
|
+
if (headerHolder) {
|
|
461
|
+
sessionHeaderHolders.set(webAppTransport.sessionId, headerHolder);
|
|
370
462
|
}
|
|
463
|
+
console.log("Created server transport");
|
|
464
|
+
await webAppTransport.start();
|
|
465
|
+
mcpProxy({
|
|
466
|
+
transportToClient: webAppTransport,
|
|
467
|
+
transportToServer: serverTransport,
|
|
468
|
+
});
|
|
371
469
|
}
|
|
372
470
|
catch (error) {
|
|
471
|
+
if (error instanceof SseError && error.code === 401) {
|
|
472
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
473
|
+
res.status(401).json(error);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
else if (error instanceof SseError && error.code === 404) {
|
|
477
|
+
console.error("Received 404 not found from MCP server. Does the MCP server support SSE?");
|
|
478
|
+
res.status(404).json(error);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
else if (JSON.stringify(error).includes("ECONNREFUSED")) {
|
|
482
|
+
console.error("Connection refused. Is the MCP server running?");
|
|
483
|
+
res.status(500).json(error);
|
|
484
|
+
}
|
|
373
485
|
console.error("Error in /sse route:", error);
|
|
374
486
|
res.status(500).json(error);
|
|
375
487
|
}
|
|
@@ -378,6 +490,10 @@ app.post("/message", originValidationMiddleware, authMiddleware, async (req, res
|
|
|
378
490
|
try {
|
|
379
491
|
const sessionId = req.query.sessionId;
|
|
380
492
|
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
493
|
+
const headerHolder = sessionHeaderHolders.get(sessionId);
|
|
494
|
+
if (headerHolder) {
|
|
495
|
+
updateHeadersInPlace(headerHolder.headers, getHttpHeaders(req));
|
|
496
|
+
}
|
|
381
497
|
const transport = webAppTransports.get(sessionId);
|
|
382
498
|
if (!transport) {
|
|
383
499
|
res.status(404).end("Session not found");
|