From 2df723804b6f86e0d1f85f76dd2648c058ae5059 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 30 May 2025 17:13:53 -0400 Subject: [PATCH 1/3] Normalize the reporting of connection setup and connection errors. The logging is verbose and different for every server type. This is an attempt to make the console output cleaner and more readable. * In server/index.js - in createTransport() - stringify query params to put them on one line - remove logging of transport creation (done in callers) - in /mcp POST handler - report POST request if session id is present - otherwise, report new StreamableHttp connection request - report creation of server transport - report creation of client transport - in /stdio GET handler - report new STDIO connection request - report creation of server transport - report creation of client transport - report 401 as authentication error without stack - in stderr data handler - if it includes "MODULE_NOT_FOUND" - report "Command not found" - remove transports - otherwise send to client as usual - in /sse GET handler - report SSE connection request - report 401 as authentication error without stack - report 404 as server that may not support SSE - report error with "ECONNREFUSED" as "Connection refused" without stack - only create, map, and proxy transports if serverTransport was successfully created - in /message POST handler - report received POST message * In mcpProxy.ts - in onServerError() - if error message includes 404 OR error.cause includes ECONNREFUSED, report "Connection refused." --- server/src/index.ts | 104 ++++++++++++++++++++++++----------------- server/src/mcpProxy.ts | 9 +++- 2 files changed, 68 insertions(+), 45 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 1586d0a0f..70023611c 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -53,7 +53,7 @@ const serverTransports: Map = new Map(); / const createTransport = async (req: express.Request): Promise => { const query = req.query; - console.log("Query parameters:", query); + console.log("Query parameters:", JSON.stringify(query)); const transportType = query.transportType as string; @@ -65,7 +65,7 @@ const createTransport = async (req: express.Request): Promise => { const { cmd, args } = findActualExecutable(command, origArgs); - console.log(`Stdio transport: command=${cmd}, args=${args}`); + console.log(`STDIO transport: command=${cmd}, args=${args}`); const transport = new StdioClientTransport({ command: cmd, @@ -75,8 +75,6 @@ const createTransport = async (req: express.Request): Promise => { }); await transport.start(); - - console.log("Spawned stdio transport"); return transport; } else if (transportType === "sse") { const url = query.url as string; @@ -104,8 +102,6 @@ const createTransport = async (req: express.Request): Promise => { }, }); await transport.start(); - - console.log("Connected to SSE transport"); return transport; } else if (transportType === "streamable-http") { const headers: HeadersInit = { @@ -130,7 +126,6 @@ const createTransport = async (req: express.Request): Promise => { }, ); await transport.start(); - console.log("Connected to Streamable HTTP transport"); return transport; } else { console.error(`Invalid transport type: ${transportType}`); @@ -159,11 +154,10 @@ app.get("/mcp", async (req, res) => { app.post("/mcp", async (req, res) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; - console.log(`Received POST message for sessionId ${sessionId}`); let serverTransport: Transport | undefined; if (!sessionId) { try { - console.log("New streamable-http connection"); + console.log("New StreamableHttp connection request"); try { serverTransport = await createTransport(req); } catch (error) { @@ -179,16 +173,17 @@ app.post("/mcp", async (req, res) => { throw error; } - console.log("Connected MCP client to server transport"); + console.log("Created StreamableHttp server transport"); const webAppTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: randomUUID, onsessioninitialized: (sessionId) => { webAppTransports.set(sessionId, webAppTransport); serverTransports.set(sessionId, serverTransport!); - console.log("Created streamable web app transport " + sessionId); + console.log("New client <-> proxy server sessionId: " + sessionId); }, }); + console.log("Created StreamableHttp client transport"); await webAppTransport.start(); @@ -207,6 +202,7 @@ app.post("/mcp", async (req, res) => { res.status(500).json(error); } } else { + console.log(`Received POST message for sessionId ${sessionId}`); try { const transport = webAppTransports.get( sessionId, @@ -255,15 +251,15 @@ app.delete("/mcp", async (req, res) => { app.get("/stdio", async (req, res) => { try { - console.log("New connection"); + console.log("New STDIO connection request"); let serverTransport: Transport | undefined; try { serverTransport = await createTransport(req); + console.log("Created server transport"); } catch (error) { if (error instanceof SseError && error.code === 401) { console.error( - "Received 401 Unauthorized from MCP server:", - error.message, + "Received 401 Unauthorized from MCP server. Authentication failure." ); res.status(401).json(error); return; @@ -272,23 +268,37 @@ app.get("/stdio", async (req, res) => { throw error; } - console.log("Connected MCP client to backing server transport"); - const webAppTransport = new SSEServerTransport("/message", res); + console.log("Created client transport"); + webAppTransports.set(webAppTransport.sessionId, webAppTransport); serverTransports.set(webAppTransport.sessionId, serverTransport); - console.log("Created client/server transports"); await webAppTransport.start(); (serverTransport as StdioClientTransport).stderr!.on("data", (chunk) => { - webAppTransport.send({ - jsonrpc: "2.0", - method: "notifications/stderr", - params: { - content: chunk.toString(), - }, - }); + if (chunk.toString().includes("MODULE_NOT_FOUND")) { + webAppTransport.send({ + jsonrpc: "2.0", + method: "notifications/stderr", + params: { + content: "Command not found, transports removed", + }, + }); + webAppTransport.close(); + serverTransport.close(); + webAppTransports.delete(webAppTransport.sessionId); + serverTransports.delete(webAppTransport.sessionId); + console.error("Command not found, transports removed"); + } else { + webAppTransport.send({ + jsonrpc: "2.0", + method: "notifications/stderr", + params: { + content: chunk.toString(), + }, + }); + } }); mcpProxy({ @@ -296,7 +306,6 @@ app.get("/stdio", async (req, res) => { transportToServer: serverTransport, }); - console.log("Set up MCP proxy"); } catch (error) { console.error("Error in /stdio route:", error); res.status(500).json(error); @@ -306,7 +315,7 @@ app.get("/stdio", async (req, res) => { app.get("/sse", async (req, res) => { try { console.log( - "New SSE connection. NOTE: The sse transport is deprecated and has been replaced by streamable-http", + "New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp", ); let serverTransport: Transport | undefined; try { @@ -314,32 +323,39 @@ app.get("/sse", async (req, res) => { } catch (error) { if (error instanceof SseError && error.code === 401) { console.error( - "Received 401 Unauthorized from MCP server:", - error.message, + "Received 401 Unauthorized from MCP server. Authentication failure." ); res.status(401).json(error); return; + } else if (error instanceof SseError && error.code === 404) { + console.error( + "Received 404 not found from MCP server. Does the MCP server support SSE?", + ); + res.status(404).json(error); + return; + } else if (JSON.stringify(error).includes("ECONNREFUSED")) { + console.error("Connection refused. Is the MCP server running?"); + res.status(500).json(error); + } else { + throw error; } - - throw error; } - console.log("Connected MCP client to backing server transport"); + if (serverTransport) { + const webAppTransport = new SSEServerTransport("/message", res); + webAppTransports.set(webAppTransport.sessionId, webAppTransport); + console.log("Created client transport"); + serverTransports.set(webAppTransport.sessionId, serverTransport!); + console.log("Created server transport"); - const webAppTransport = new SSEServerTransport("/message", res); - webAppTransports.set(webAppTransport.sessionId, webAppTransport); - console.log("Created client transport"); - serverTransports.set(webAppTransport.sessionId, serverTransport); - console.log("Created server transport"); - - await webAppTransport.start(); + await webAppTransport.start(); - mcpProxy({ - transportToClient: webAppTransport, - transportToServer: serverTransport, - }); + mcpProxy({ + transportToClient: webAppTransport, + transportToServer: serverTransport, + }); + } - console.log("Set up MCP proxy"); } catch (error) { console.error("Error in /sse route:", error); res.status(500).json(error); @@ -349,7 +365,7 @@ app.get("/sse", async (req, res) => { app.post("/message", async (req, res) => { try { const sessionId = req.query.sessionId; - console.log(`Received message for sessionId ${sessionId}`); + console.log(`Received POST message for sessionId ${sessionId}`); const transport = webAppTransports.get( sessionId as string, diff --git a/server/src/mcpProxy.ts b/server/src/mcpProxy.ts index 126605042..09ca4012a 100644 --- a/server/src/mcpProxy.ts +++ b/server/src/mcpProxy.ts @@ -6,7 +6,14 @@ function onClientError(error: Error) { } function onServerError(error: Error) { - console.error("Error from MCP server:", error); + if ( + (error?.message && error.message.includes("Error POSTing to endpoint (HTTP 404)")) || + (error?.cause && JSON.stringify(error.cause).includes('ECONNREFUSED')) + ) { + console.error("Connection refused. Is the MCP server running?"); + } else { + console.error("Error from MCP server:", error); + } } export default function mcpProxy({ From b732ab408588c9f955adda9a9f78314e95885a25 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 30 May 2025 17:51:22 -0400 Subject: [PATCH 2/3] prettier --- server/src/index.ts | 8 +++----- server/src/mcpProxy.ts | 9 +++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 70023611c..8fdce3684 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -259,7 +259,7 @@ app.get("/stdio", async (req, res) => { } catch (error) { if (error instanceof SseError && error.code === 401) { console.error( - "Received 401 Unauthorized from MCP server. Authentication failure." + "Received 401 Unauthorized from MCP server. Authentication failure.", ); res.status(401).json(error); return; @@ -305,7 +305,6 @@ app.get("/stdio", async (req, res) => { transportToClient: webAppTransport, transportToServer: serverTransport, }); - } catch (error) { console.error("Error in /stdio route:", error); res.status(500).json(error); @@ -323,7 +322,7 @@ app.get("/sse", async (req, res) => { } catch (error) { if (error instanceof SseError && error.code === 401) { console.error( - "Received 401 Unauthorized from MCP server. Authentication failure." + "Received 401 Unauthorized from MCP server. Authentication failure.", ); res.status(401).json(error); return; @@ -333,7 +332,7 @@ app.get("/sse", async (req, res) => { ); res.status(404).json(error); return; - } else if (JSON.stringify(error).includes("ECONNREFUSED")) { + } else if (JSON.stringify(error).includes("ECONNREFUSED")) { console.error("Connection refused. Is the MCP server running?"); res.status(500).json(error); } else { @@ -355,7 +354,6 @@ app.get("/sse", async (req, res) => { transportToServer: serverTransport, }); } - } catch (error) { console.error("Error in /sse route:", error); res.status(500).json(error); diff --git a/server/src/mcpProxy.ts b/server/src/mcpProxy.ts index 09ca4012a..15167305b 100644 --- a/server/src/mcpProxy.ts +++ b/server/src/mcpProxy.ts @@ -6,10 +6,11 @@ function onClientError(error: Error) { } function onServerError(error: Error) { - if ( - (error?.message && error.message.includes("Error POSTing to endpoint (HTTP 404)")) || - (error?.cause && JSON.stringify(error.cause).includes('ECONNREFUSED')) - ) { + if ( + (error?.message && + error.message.includes("Error POSTing to endpoint (HTTP 404)")) || + (error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED")) + ) { console.error("Connection refused. Is the MCP server running?"); } else { console.error("Error from MCP server:", error); From c485e57a3995362be56119aa103bf0da021b5fa0 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Tue, 3 Jun 2025 17:46:43 -0400 Subject: [PATCH 3/3] Add reporting of the Proxy <-> Server sessionId. It's only possible to get the session id for this leg of the proxy if it is a StreamableHttp connection. * In server/src/index.ts - slight format tweak of the Client <-> Proxy message so that the two messages will line up when displayed * In server/src/mcpProxy.ts - add reportedServerSession boolean initialized to false - in transportToServer.onmessage, - if reportedServerSession isn't set yet, report the sessionId if present on the transport, and then set reportedServerSession to true --- server/src/index.ts | 2 +- server/src/mcpProxy.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/server/src/index.ts b/server/src/index.ts index 1e43eea2b..37327fa27 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -197,7 +197,7 @@ app.post("/mcp", async (req, res) => { onsessioninitialized: (sessionId) => { webAppTransports.set(sessionId, webAppTransport); serverTransports.set(sessionId, serverTransport!); - console.log("New client <-> proxy server sessionId: " + sessionId); + console.log("Client <-> Proxy sessionId: " + sessionId); }, }); console.log("Created StreamableHttp client transport"); diff --git a/server/src/mcpProxy.ts b/server/src/mcpProxy.ts index 15167305b..9dee9de41 100644 --- a/server/src/mcpProxy.ts +++ b/server/src/mcpProxy.ts @@ -27,6 +27,8 @@ export default function mcpProxy({ let transportToClientClosed = false; let transportToServerClosed = false; + let reportedServerSession = false; + transportToClient.onmessage = (message) => { transportToServer.send(message).catch((error) => { // Send error response back to client if it was a request (has id) and connection is still open @@ -46,6 +48,15 @@ export default function mcpProxy({ }; transportToServer.onmessage = (message) => { + if (!reportedServerSession) { + if (transportToServer.sessionId) { + // Can only report for StreamableHttp + console.error( + "Proxy <-> Server sessionId: " + transportToServer.sessionId, + ); + } + reportedServerSession = true; + } transportToClient.send(message).catch(onClientError); };