Merge pull request #966 from olaservo/fix/streamable-http-401-handling

fix: detect 401 errors from StreamableHTTP transport for OAuth flow
This commit is contained in:
Cliff Hall 2025-12-13 18:04:34 -05:00 • committed by GitHub
commit 1977e2a597
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -17,7 +17,10 @@ import {
StdioClientTransport, StdioClientTransport,
getDefaultEnvironment, getDefaultEnvironment,
} from "@modelcontextprotocol/sdk/client/stdio.js"; } 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 { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.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. // Function to get HTTP headers.
const getHttpHeaders = (req: express.Request): Record<string, string> => { const getHttpHeaders = (req: express.Request): Record<string, string> => {
const headers: Record<string, string> = {}; const headers: Record<string, string> = {};
@ -508,10 +527,10 @@ app.post(
req.body, req.body,
); );
} catch (error) { } catch (error) {
if (error instanceof SseError && error.code === 401) { if (is401Error(error)) {
console.error( console.error(
"Received 401 Unauthorized from MCP server:", "Received 401 Unauthorized from MCP server:",
error.message, error instanceof Error ? error.message : error,
); );
res.status(401).json(error); res.status(401).json(error);
return; return;
@ -649,7 +668,7 @@ app.get(
transportToServer: serverTransport, transportToServer: serverTransport,
}); });
} catch (error) { } catch (error) {
if (error instanceof SseError && error.code === 401) { if (is401Error(error)) {
console.error( console.error(
"Received 401 Unauthorized from MCP server. Authentication failure.", "Received 401 Unauthorized from MCP server. Authentication failure.",
); );
@ -695,7 +714,7 @@ app.get(
transportToServer: serverTransport, transportToServer: serverTransport,
}); });
} catch (error) { } catch (error) {
if (error instanceof SseError && error.code === 401) { if (is401Error(error)) {
console.error( console.error(
"Received 401 Unauthorized from MCP server. Authentication failure.", "Received 401 Unauthorized from MCP server. Authentication failure.",
); );