Skip to content

Commit d1fcbd2

Browse files
olaservoclaude
andcommitted
fix: detect 401 errors from StreamableHTTP transport for OAuth flow
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 <[email protected]>
1 parent 2b79e36 commit d1fcbd2

File tree

1 file changed

+24
-5
lines changed

1 file changed

+24
-5
lines changed

server/src/index.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import {
1717
StdioClientTransport,
1818
getDefaultEnvironment,
1919
} from "@modelcontextprotocol/sdk/client/stdio.js";
20-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
20+
import {
21+
StreamableHTTPClientTransport,
22+
StreamableHTTPError,
23+
} from "@modelcontextprotocol/sdk/client/streamableHttp.js";
2124
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2225
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
2326
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
@@ -44,6 +47,22 @@ const { values } = parseArgs({
4447
},
4548
});
4649

50+
/**
51+
* Helper function to detect 401 Unauthorized errors from various transport types.
52+
* StreamableHTTPClientTransport throws a generic Error with "HTTP 401" in the message
53+
* when there's no authProvider configured, while SSEClientTransport throws SseError.
54+
*/
55+
const is401Error = (error: unknown): boolean => {
56+
if (error instanceof SseError && error.code === 401) return true;
57+
if (error instanceof StreamableHTTPError && error.code === 401) return true;
58+
if (
59+
error instanceof Error &&
60+
(error.message.includes("HTTP 401") || error.message.includes("(401)"))
61+
)
62+
return true;
63+
return false;
64+
};
65+
4766
// Function to get HTTP headers.
4867
const getHttpHeaders = (req: express.Request): Record<string, string> => {
4968
const headers: Record<string, string> = {};
@@ -508,10 +527,10 @@ app.post(
508527
req.body,
509528
);
510529
} catch (error) {
511-
if (error instanceof SseError && error.code === 401) {
530+
if (is401Error(error)) {
512531
console.error(
513532
"Received 401 Unauthorized from MCP server:",
514-
error.message,
533+
error instanceof Error ? error.message : error,
515534
);
516535
res.status(401).json(error);
517536
return;
@@ -649,7 +668,7 @@ app.get(
649668
transportToServer: serverTransport,
650669
});
651670
} catch (error) {
652-
if (error instanceof SseError && error.code === 401) {
671+
if (is401Error(error)) {
653672
console.error(
654673
"Received 401 Unauthorized from MCP server. Authentication failure.",
655674
);
@@ -695,7 +714,7 @@ app.get(
695714
transportToServer: serverTransport,
696715
});
697716
} catch (error) {
698-
if (error instanceof SseError && error.code === 401) {
717+
if (is401Error(error)) {
699718
console.error(
700719
"Received 401 Unauthorized from MCP server. Authentication failure.",
701720
);

0 commit comments

Comments
 (0)