From d1fcbd2c93df541b6bd8ade49337853e807ea8d5 Mon Sep 17 00:00:00 2001 From: olaservo Date: Mon, 8 Dec 2025 07:22:29 -0700 Subject: [PATCH] fix: detect 401 errors from StreamableHTTP transport for OAuth flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server-side error handling only checked for SseError instances when detecting 401 responses from MCP servers. StreamableHTTPClientTransport throws a different error type (generic Error with "HTTP 401" message) when there's no authProvider configured. This caused the proxy to return 500 instead of 401 to the client, preventing the OAuth discovery and redirect flow from triggering. Added is401Error() helper that checks for: - SseError with code 401 - StreamableHTTPError with code 401 - Generic Error with "HTTP 401" or "(401)" in message Fixes #960 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- server/src/index.ts | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 2680f22e5..388fdaca7 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -17,7 +17,10 @@ import { StdioClientTransport, getDefaultEnvironment, } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { + StreamableHTTPClientTransport, + StreamableHTTPError, +} from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; @@ -44,6 +47,22 @@ const { values } = parseArgs({ }, }); +/** + * Helper function to detect 401 Unauthorized errors from various transport types. + * StreamableHTTPClientTransport throws a generic Error with "HTTP 401" in the message + * when there's no authProvider configured, while SSEClientTransport throws SseError. + */ +const is401Error = (error: unknown): boolean => { + if (error instanceof SseError && error.code === 401) return true; + if (error instanceof StreamableHTTPError && error.code === 401) return true; + if ( + error instanceof Error && + (error.message.includes("HTTP 401") || error.message.includes("(401)")) + ) + return true; + return false; +}; + // Function to get HTTP headers. const getHttpHeaders = (req: express.Request): Record => { const headers: Record = {}; @@ -508,10 +527,10 @@ app.post( req.body, ); } catch (error) { - if (error instanceof SseError && error.code === 401) { + if (is401Error(error)) { console.error( "Received 401 Unauthorized from MCP server:", - error.message, + error instanceof Error ? error.message : error, ); res.status(401).json(error); return; @@ -649,7 +668,7 @@ app.get( transportToServer: serverTransport, }); } catch (error) { - if (error instanceof SseError && error.code === 401) { + if (is401Error(error)) { console.error( "Received 401 Unauthorized from MCP server. Authentication failure.", ); @@ -695,7 +714,7 @@ app.get( transportToServer: serverTransport, }); } catch (error) { - if (error instanceof SseError && error.code === 401) { + if (is401Error(error)) { console.error( "Received 401 Unauthorized from MCP server. Authentication failure.", );