@modelcontextprotocol/inspector 0.16.6 → 0.16.8
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 +25 -1
- package/client/dist/assets/{OAuthCallback-Cmffn8om.js → OAuthCallback-7gQlGWPl.js} +1 -1
- package/client/dist/assets/{OAuthDebugCallback-BhzcPtuF.js → OAuthDebugCallback-67pOP7RZ.js} +1 -1
- package/client/dist/assets/{index-B_qh-Btq.js → index-3LzLs-Yz.js} +744 -221
- package/client/dist/assets/{index-C217Ot8c.css → index-CFrqgd8F.css} +97 -0
- package/client/dist/index.html +2 -2
- package/package.json +6 -5
- package/server/build/index.js +246 -119
package/server/build/index.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import cors from "cors";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { parse as shellParseArgs } from "shell-quote";
|
|
5
|
+
import nodeFetch, { Headers as NodeHeaders } from "node-fetch";
|
|
6
|
+
// Type-compatible wrappers for node-fetch to work with browser-style types
|
|
7
|
+
const fetch = nodeFetch;
|
|
8
|
+
const Headers = NodeHeaders;
|
|
5
9
|
import { SSEClientTransport, SseError, } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
6
10
|
import { StdioClientTransport, getDefaultEnvironment, } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
11
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -12,12 +16,6 @@ import { findActualExecutable } from "spawn-rx";
|
|
|
12
16
|
import mcpProxy from "./mcpProxy.js";
|
|
13
17
|
import { randomUUID, randomBytes, timingSafeEqual } from "node:crypto";
|
|
14
18
|
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
19
|
const defaultEnvironment = {
|
|
22
20
|
...getDefaultEnvironment(),
|
|
23
21
|
...(process.env.MCP_ENV_VARS ? JSON.parse(process.env.MCP_ENV_VARS) : {}),
|
|
@@ -33,34 +31,91 @@ const { values } = parseArgs({
|
|
|
33
31
|
},
|
|
34
32
|
});
|
|
35
33
|
// Function to get HTTP headers.
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
const getHttpHeaders = (req) => {
|
|
35
|
+
const headers = {};
|
|
36
|
+
// Iterate over all headers in the request
|
|
37
|
+
for (const key in req.headers) {
|
|
38
|
+
const lowerKey = key.toLowerCase();
|
|
39
|
+
// Check if the header is one we want to forward
|
|
40
|
+
if (lowerKey.startsWith("mcp-") ||
|
|
41
|
+
lowerKey === "authorization" ||
|
|
42
|
+
lowerKey === "last-event-id") {
|
|
43
|
+
// Exclude the proxy's own authentication header and the Client <-> Proxy session ID header
|
|
44
|
+
if (lowerKey !== "x-mcp-proxy-auth" && lowerKey !== "mcp-session-id") {
|
|
45
|
+
const value = req.headers[key];
|
|
46
|
+
if (typeof value === "string") {
|
|
47
|
+
// If the value is a string, use it directly
|
|
48
|
+
headers[key] = value;
|
|
49
|
+
}
|
|
50
|
+
else if (Array.isArray(value)) {
|
|
51
|
+
// If the value is an array, use the last element
|
|
52
|
+
const lastValue = value.at(-1);
|
|
53
|
+
if (lastValue !== undefined) {
|
|
54
|
+
headers[key] = lastValue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// If value is undefined, it's skipped, which is correct.
|
|
58
|
+
}
|
|
49
59
|
}
|
|
50
|
-
const value = req.headers[key];
|
|
51
|
-
headers[key] = Array.isArray(value) ? value[value.length - 1] : value;
|
|
52
60
|
}
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
// Handle the custom auth header separately. We expect `x-custom-auth-header`
|
|
62
|
+
// to be a string containing the name of the actual authentication header.
|
|
63
|
+
const customAuthHeaderName = req.headers["x-custom-auth-header"];
|
|
64
|
+
if (typeof customAuthHeaderName === "string") {
|
|
65
|
+
const lowerCaseHeaderName = customAuthHeaderName.toLowerCase();
|
|
66
|
+
const value = req.headers[lowerCaseHeaderName];
|
|
67
|
+
if (typeof value === "string") {
|
|
68
|
+
headers[customAuthHeaderName] = value;
|
|
69
|
+
}
|
|
70
|
+
else if (Array.isArray(value)) {
|
|
71
|
+
// If the actual auth header was sent multiple times, use the last value.
|
|
72
|
+
const lastValue = value.at(-1);
|
|
73
|
+
if (lastValue !== undefined) {
|
|
74
|
+
headers[customAuthHeaderName] = lastValue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Handle multiple custom headers (new approach)
|
|
79
|
+
if (req.headers["x-custom-auth-headers"] !== undefined) {
|
|
80
|
+
try {
|
|
81
|
+
const customHeaderNames = JSON.parse(req.headers["x-custom-auth-headers"]);
|
|
82
|
+
if (Array.isArray(customHeaderNames)) {
|
|
83
|
+
customHeaderNames.forEach((headerName) => {
|
|
84
|
+
const lowerCaseHeaderName = headerName.toLowerCase();
|
|
85
|
+
if (req.headers[lowerCaseHeaderName] !== undefined) {
|
|
86
|
+
const value = req.headers[lowerCaseHeaderName];
|
|
87
|
+
headers[headerName] = Array.isArray(value)
|
|
88
|
+
? value[value.length - 1]
|
|
89
|
+
: value;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.warn("Failed to parse x-custom-auth-headers:", error);
|
|
60
96
|
}
|
|
61
97
|
}
|
|
62
98
|
return headers;
|
|
63
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* Updates a headers object in-place, preserving the original Accept header.
|
|
102
|
+
* This is necessary to ensure that transports holding a reference to the headers
|
|
103
|
+
* object see the updates.
|
|
104
|
+
* @param currentHeaders The headers object to update.
|
|
105
|
+
* @param newHeaders The new headers to apply.
|
|
106
|
+
*/
|
|
107
|
+
const updateHeadersInPlace = (currentHeaders, newHeaders) => {
|
|
108
|
+
// Preserve the Accept header, which is set at transport creation and
|
|
109
|
+
// is not present in subsequent client requests.
|
|
110
|
+
const accept = currentHeaders["Accept"];
|
|
111
|
+
// Clear the old headers and apply the new ones.
|
|
112
|
+
Object.keys(currentHeaders).forEach((key) => delete currentHeaders[key]);
|
|
113
|
+
Object.assign(currentHeaders, newHeaders);
|
|
114
|
+
// Restore the Accept header.
|
|
115
|
+
if (accept) {
|
|
116
|
+
currentHeaders["Accept"] = accept;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
64
119
|
const app = express();
|
|
65
120
|
app.use(cors());
|
|
66
121
|
app.use((req, res, next) => {
|
|
@@ -69,6 +124,7 @@ app.use((req, res, next) => {
|
|
|
69
124
|
});
|
|
70
125
|
const webAppTransports = new Map(); // Web app transports by web app sessionId
|
|
71
126
|
const serverTransports = new Map(); // Server Transports by web app sessionId
|
|
127
|
+
const sessionHeaderHolders = new Map(); // For dynamic header updates
|
|
72
128
|
// Use provided token from environment or generate a new one
|
|
73
129
|
const sessionToken = process.env.MCP_PROXY_AUTH_TOKEN || randomBytes(32).toString("hex");
|
|
74
130
|
const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH;
|
|
@@ -126,6 +182,73 @@ const authMiddleware = (req, res, next) => {
|
|
|
126
182
|
}
|
|
127
183
|
next();
|
|
128
184
|
};
|
|
185
|
+
/**
|
|
186
|
+
* Converts a Node.js ReadableStream to a web-compatible ReadableStream
|
|
187
|
+
* This is necessary for the EventSource polyfill which expects web streams
|
|
188
|
+
*/
|
|
189
|
+
const createWebReadableStream = (nodeStream) => {
|
|
190
|
+
return new ReadableStream({
|
|
191
|
+
start(controller) {
|
|
192
|
+
nodeStream.on("data", (chunk) => {
|
|
193
|
+
controller.enqueue(chunk);
|
|
194
|
+
});
|
|
195
|
+
nodeStream.on("end", () => {
|
|
196
|
+
controller.close();
|
|
197
|
+
});
|
|
198
|
+
nodeStream.on("error", (err) => {
|
|
199
|
+
controller.error(err);
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Creates a `fetch` function that merges dynamic session headers with the
|
|
206
|
+
* headers from the actual request, ensuring that request-specific headers like
|
|
207
|
+
* `Content-Type` are preserved. For SSE requests, it also converts Node.js
|
|
208
|
+
* streams to web-compatible streams.
|
|
209
|
+
*/
|
|
210
|
+
const createCustomFetch = (headerHolder) => {
|
|
211
|
+
return async (input, init) => {
|
|
212
|
+
// Determine the headers from the original request/init.
|
|
213
|
+
// The SDK may pass a Request object or a URL and an init object.
|
|
214
|
+
const originalHeaders = input instanceof Request ? input.headers : init?.headers;
|
|
215
|
+
// Start with our dynamic session headers.
|
|
216
|
+
const finalHeaders = new Headers(headerHolder.headers);
|
|
217
|
+
// Merge the SDK's request-specific headers, letting them overwrite.
|
|
218
|
+
// This is crucial for preserving Content-Type on POST requests.
|
|
219
|
+
new Headers(originalHeaders).forEach((value, key) => {
|
|
220
|
+
finalHeaders.set(key, value);
|
|
221
|
+
});
|
|
222
|
+
// Convert Headers to a plain object for node-fetch compatibility
|
|
223
|
+
const headersObject = {};
|
|
224
|
+
finalHeaders.forEach((value, key) => {
|
|
225
|
+
headersObject[key] = value;
|
|
226
|
+
});
|
|
227
|
+
// Get the response from node-fetch (cast input and init to handle type differences)
|
|
228
|
+
const response = await fetch(input, { ...init, headers: headersObject });
|
|
229
|
+
// Check if this is an SSE request by looking at the Accept header
|
|
230
|
+
const acceptHeader = finalHeaders.get("Accept");
|
|
231
|
+
const isSSE = acceptHeader?.includes("text/event-stream");
|
|
232
|
+
if (isSSE && response.body) {
|
|
233
|
+
// For SSE requests, we need to convert the Node.js stream to a web ReadableStream
|
|
234
|
+
// because the EventSource polyfill expects web-compatible streams
|
|
235
|
+
const webStream = createWebReadableStream(response.body);
|
|
236
|
+
// Create a new response with the web-compatible stream
|
|
237
|
+
// Convert node-fetch headers to plain object for web Response compatibility
|
|
238
|
+
const responseHeaders = {};
|
|
239
|
+
response.headers.forEach((value, key) => {
|
|
240
|
+
responseHeaders[key] = value;
|
|
241
|
+
});
|
|
242
|
+
return new Response(webStream, {
|
|
243
|
+
status: response.status,
|
|
244
|
+
statusText: response.statusText,
|
|
245
|
+
headers: responseHeaders,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// For non-SSE requests, return the response as-is (cast to handle type differences)
|
|
249
|
+
return response;
|
|
250
|
+
};
|
|
251
|
+
};
|
|
129
252
|
const createTransport = async (req) => {
|
|
130
253
|
const query = req.query;
|
|
131
254
|
console.log("Query parameters:", JSON.stringify(query));
|
|
@@ -144,32 +267,35 @@ const createTransport = async (req) => {
|
|
|
144
267
|
stderr: "pipe",
|
|
145
268
|
});
|
|
146
269
|
await transport.start();
|
|
147
|
-
return transport;
|
|
270
|
+
return { transport };
|
|
148
271
|
}
|
|
149
272
|
else if (transportType === "sse") {
|
|
150
273
|
const url = query.url;
|
|
151
|
-
const headers = getHttpHeaders(req
|
|
274
|
+
const headers = getHttpHeaders(req);
|
|
275
|
+
headers["Accept"] = "text/event-stream";
|
|
276
|
+
const headerHolder = { headers };
|
|
152
277
|
console.log(`SSE transport: url=${url}, headers=${JSON.stringify(headers)}`);
|
|
153
278
|
const transport = new SSEClientTransport(new URL(url), {
|
|
154
279
|
eventSourceInit: {
|
|
155
|
-
fetch: (
|
|
280
|
+
fetch: createCustomFetch(headerHolder),
|
|
156
281
|
},
|
|
157
282
|
requestInit: {
|
|
158
|
-
headers,
|
|
283
|
+
headers: headerHolder.headers,
|
|
159
284
|
},
|
|
160
285
|
});
|
|
161
286
|
await transport.start();
|
|
162
|
-
return transport;
|
|
287
|
+
return { transport, headerHolder };
|
|
163
288
|
}
|
|
164
289
|
else if (transportType === "streamable-http") {
|
|
165
|
-
const headers = getHttpHeaders(req
|
|
290
|
+
const headers = getHttpHeaders(req);
|
|
291
|
+
headers["Accept"] = "text/event-stream, application/json";
|
|
292
|
+
const headerHolder = { headers };
|
|
166
293
|
const transport = new StreamableHTTPClientTransport(new URL(query.url), {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
294
|
+
// Pass a custom fetch to inject the latest headers on each request
|
|
295
|
+
fetch: createCustomFetch(headerHolder),
|
|
170
296
|
});
|
|
171
297
|
await transport.start();
|
|
172
|
-
return transport;
|
|
298
|
+
return { transport, headerHolder };
|
|
173
299
|
}
|
|
174
300
|
else {
|
|
175
301
|
console.error(`Invalid transport type: ${transportType}`);
|
|
@@ -179,6 +305,10 @@ const createTransport = async (req) => {
|
|
|
179
305
|
app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
180
306
|
const sessionId = req.headers["mcp-session-id"];
|
|
181
307
|
console.log(`Received GET message for sessionId ${sessionId}`);
|
|
308
|
+
const headerHolder = sessionHeaderHolders.get(sessionId);
|
|
309
|
+
if (headerHolder) {
|
|
310
|
+
updateHeadersInPlace(headerHolder.headers, getHttpHeaders(req));
|
|
311
|
+
}
|
|
182
312
|
try {
|
|
183
313
|
const transport = webAppTransports.get(sessionId);
|
|
184
314
|
if (!transport) {
|
|
@@ -196,29 +326,45 @@ app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) =>
|
|
|
196
326
|
});
|
|
197
327
|
app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
198
328
|
const sessionId = req.headers["mcp-session-id"];
|
|
199
|
-
|
|
200
|
-
|
|
329
|
+
if (sessionId) {
|
|
330
|
+
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
331
|
+
const headerHolder = sessionHeaderHolders.get(sessionId);
|
|
332
|
+
if (headerHolder) {
|
|
333
|
+
updateHeadersInPlace(headerHolder.headers, getHttpHeaders(req));
|
|
334
|
+
}
|
|
201
335
|
try {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
336
|
+
const transport = webAppTransports.get(sessionId);
|
|
337
|
+
if (!transport) {
|
|
338
|
+
res.status(404).end("Transport not found for sessionId " + sessionId);
|
|
205
339
|
}
|
|
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;
|
|
340
|
+
else {
|
|
341
|
+
await transport.handleRequest(req, res);
|
|
213
342
|
}
|
|
214
|
-
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
console.error("Error in /mcp route:", error);
|
|
346
|
+
res.status(500).json(error);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
console.log("New StreamableHttp connection request");
|
|
351
|
+
try {
|
|
352
|
+
const { transport: serverTransport, headerHolder } = await createTransport(req);
|
|
215
353
|
const webAppTransport = new StreamableHTTPServerTransport({
|
|
216
354
|
sessionIdGenerator: randomUUID,
|
|
217
355
|
onsessioninitialized: (sessionId) => {
|
|
218
356
|
webAppTransports.set(sessionId, webAppTransport);
|
|
219
|
-
serverTransports.set(sessionId, serverTransport);
|
|
357
|
+
serverTransports.set(sessionId, serverTransport); // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
358
|
+
if (headerHolder) {
|
|
359
|
+
sessionHeaderHolders.set(sessionId, headerHolder);
|
|
360
|
+
}
|
|
220
361
|
console.log("Client <-> Proxy sessionId: " + sessionId);
|
|
221
362
|
},
|
|
363
|
+
onsessionclosed: (sessionId) => {
|
|
364
|
+
webAppTransports.delete(sessionId);
|
|
365
|
+
serverTransports.delete(sessionId);
|
|
366
|
+
sessionHeaderHolders.delete(sessionId);
|
|
367
|
+
},
|
|
222
368
|
});
|
|
223
369
|
console.log("Created StreamableHttp client transport");
|
|
224
370
|
await webAppTransport.start();
|
|
@@ -229,23 +375,12 @@ app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) =>
|
|
|
229
375
|
await webAppTransport.handleRequest(req, res, req.body);
|
|
230
376
|
}
|
|
231
377
|
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);
|
|
378
|
+
if (error instanceof SseError && error.code === 401) {
|
|
379
|
+
console.error("Received 401 Unauthorized from MCP server:", error.message);
|
|
380
|
+
res.status(401).json(error);
|
|
381
|
+
return;
|
|
245
382
|
}
|
|
246
|
-
|
|
247
|
-
catch (error) {
|
|
248
|
-
console.error("Error in /mcp route:", error);
|
|
383
|
+
console.error("Error in /mcp POST route:", error);
|
|
249
384
|
res.status(500).json(error);
|
|
250
385
|
}
|
|
251
386
|
}
|
|
@@ -253,10 +388,9 @@ app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) =>
|
|
|
253
388
|
app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
254
389
|
const sessionId = req.headers["mcp-session-id"];
|
|
255
390
|
console.log(`Received DELETE message for sessionId ${sessionId}`);
|
|
256
|
-
let serverTransport;
|
|
257
391
|
if (sessionId) {
|
|
258
392
|
try {
|
|
259
|
-
serverTransport = serverTransports.get(sessionId);
|
|
393
|
+
const serverTransport = serverTransports.get(sessionId);
|
|
260
394
|
if (!serverTransport) {
|
|
261
395
|
res.status(404).end("Transport not found for sessionId " + sessionId);
|
|
262
396
|
}
|
|
@@ -264,6 +398,7 @@ app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res)
|
|
|
264
398
|
await serverTransport.terminateSession();
|
|
265
399
|
webAppTransports.delete(sessionId);
|
|
266
400
|
serverTransports.delete(sessionId);
|
|
401
|
+
sessionHeaderHolders.delete(sessionId);
|
|
267
402
|
console.log(`Transports removed for sessionId ${sessionId}`);
|
|
268
403
|
}
|
|
269
404
|
res.status(200).end();
|
|
@@ -277,18 +412,7 @@ app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res)
|
|
|
277
412
|
app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
278
413
|
try {
|
|
279
414
|
console.log("New STDIO connection request");
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
serverTransport = await createTransport(req);
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
if (error instanceof SseError && error.code === 401) {
|
|
286
|
-
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
287
|
-
res.status(401).json(error);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
throw error;
|
|
291
|
-
}
|
|
415
|
+
const { transport: serverTransport } = await createTransport(req);
|
|
292
416
|
const proxyFullAddress = req.query.proxyFullAddress || "";
|
|
293
417
|
const prefix = proxyFullAddress || "";
|
|
294
418
|
const endpoint = `${prefix}/message`;
|
|
@@ -317,6 +441,7 @@ app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) =
|
|
|
317
441
|
serverTransport.close();
|
|
318
442
|
webAppTransports.delete(webAppTransport.sessionId);
|
|
319
443
|
serverTransports.delete(webAppTransport.sessionId);
|
|
444
|
+
sessionHeaderHolders.delete(webAppTransport.sessionId);
|
|
320
445
|
console.error(message);
|
|
321
446
|
}
|
|
322
447
|
else {
|
|
@@ -382,6 +507,11 @@ app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) =
|
|
|
382
507
|
});
|
|
383
508
|
}
|
|
384
509
|
catch (error) {
|
|
510
|
+
if (error instanceof SseError && error.code === 401) {
|
|
511
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
512
|
+
res.status(401).json(error);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
385
515
|
console.error("Error in /stdio route:", error);
|
|
386
516
|
res.status(500).json(error);
|
|
387
517
|
}
|
|
@@ -389,46 +519,39 @@ app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) =
|
|
|
389
519
|
app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => {
|
|
390
520
|
try {
|
|
391
521
|
console.log("New SSE connection request. NOTE: The SSE transport is deprecated and has been replaced by StreamableHttp");
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
else if (error instanceof SseError && error.code === 404) {
|
|
403
|
-
console.error("Received 404 not found from MCP server. Does the MCP server support SSE?");
|
|
404
|
-
res.status(404).json(error);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
else if (JSON.stringify(error).includes("ECONNREFUSED")) {
|
|
408
|
-
console.error("Connection refused. Is the MCP server running?");
|
|
409
|
-
res.status(500).json(error);
|
|
410
|
-
}
|
|
411
|
-
else {
|
|
412
|
-
throw error;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (serverTransport) {
|
|
416
|
-
const proxyFullAddress = req.query.proxyFullAddress || "";
|
|
417
|
-
const prefix = proxyFullAddress || "";
|
|
418
|
-
const endpoint = `${prefix}/message`;
|
|
419
|
-
const webAppTransport = new SSEServerTransport(endpoint, res);
|
|
420
|
-
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
421
|
-
console.log("Created client transport");
|
|
422
|
-
serverTransports.set(webAppTransport.sessionId, serverTransport);
|
|
423
|
-
console.log("Created server transport");
|
|
424
|
-
await webAppTransport.start();
|
|
425
|
-
mcpProxy({
|
|
426
|
-
transportToClient: webAppTransport,
|
|
427
|
-
transportToServer: serverTransport,
|
|
428
|
-
});
|
|
522
|
+
const { transport: serverTransport, headerHolder } = await createTransport(req);
|
|
523
|
+
const proxyFullAddress = req.query.proxyFullAddress || "";
|
|
524
|
+
const prefix = proxyFullAddress || "";
|
|
525
|
+
const endpoint = `${prefix}/message`;
|
|
526
|
+
const webAppTransport = new SSEServerTransport(endpoint, res);
|
|
527
|
+
webAppTransports.set(webAppTransport.sessionId, webAppTransport);
|
|
528
|
+
console.log("Created client transport");
|
|
529
|
+
serverTransports.set(webAppTransport.sessionId, serverTransport); // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
530
|
+
if (headerHolder) {
|
|
531
|
+
sessionHeaderHolders.set(webAppTransport.sessionId, headerHolder);
|
|
429
532
|
}
|
|
533
|
+
console.log("Created server transport");
|
|
534
|
+
await webAppTransport.start();
|
|
535
|
+
mcpProxy({
|
|
536
|
+
transportToClient: webAppTransport,
|
|
537
|
+
transportToServer: serverTransport,
|
|
538
|
+
});
|
|
430
539
|
}
|
|
431
540
|
catch (error) {
|
|
541
|
+
if (error instanceof SseError && error.code === 401) {
|
|
542
|
+
console.error("Received 401 Unauthorized from MCP server. Authentication failure.");
|
|
543
|
+
res.status(401).json(error);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
else if (error instanceof SseError && error.code === 404) {
|
|
547
|
+
console.error("Received 404 not found from MCP server. Does the MCP server support SSE?");
|
|
548
|
+
res.status(404).json(error);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
else if (JSON.stringify(error).includes("ECONNREFUSED")) {
|
|
552
|
+
console.error("Connection refused. Is the MCP server running?");
|
|
553
|
+
res.status(500).json(error);
|
|
554
|
+
}
|
|
432
555
|
console.error("Error in /sse route:", error);
|
|
433
556
|
res.status(500).json(error);
|
|
434
557
|
}
|
|
@@ -437,6 +560,10 @@ app.post("/message", originValidationMiddleware, authMiddleware, async (req, res
|
|
|
437
560
|
try {
|
|
438
561
|
const sessionId = req.query.sessionId;
|
|
439
562
|
console.log(`Received POST message for sessionId ${sessionId}`);
|
|
563
|
+
const headerHolder = sessionHeaderHolders.get(sessionId);
|
|
564
|
+
if (headerHolder) {
|
|
565
|
+
updateHeadersInPlace(headerHolder.headers, getHttpHeaders(req));
|
|
566
|
+
}
|
|
440
567
|
const transport = webAppTransports.get(sessionId);
|
|
441
568
|
if (!transport) {
|
|
442
569
|
res.status(404).end("Session not found");
|