fix(mcp): surface MCPUpstreamAuthError as 401 in SSE/HTTP transport handlers

Both handle_sse_mcp and handle_streamable_http_mcp only caught
HTTPException to preserve 401 + WWW-Authenticate challenges, but
MCPUpstreamAuthError (raised when a pass-through server's upstream
rejects a bearer token mid-session) inherits from Exception. It was
falling through to the generic handler and surfacing as an opaque 500.

Mirror the REST endpoint behavior: translate MCPUpstreamAuthError into
an HTTPException(status_code=e.status_code) with the upstream
www-authenticate header so standards-compliant MCP clients trigger the
upstream OAuth flow.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-20 17:18:22 +00:00
parent 5d8f74f1c1
commit a86f6e7eb6
No known key found for this signature in database

View file

@ -36,6 +36,7 @@ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import (
MCPRequestHandler,
)
from litellm.proxy._experimental.mcp_server.exceptions import MCPUpstreamAuthError
from litellm.proxy._experimental.mcp_server.discoverable_endpoints import (
get_request_base_url,
)
@ -3164,6 +3165,18 @@ if MCP_AVAILABLE:
_client_ip,
):
await session_manager.handle_request(scope, receive, send)
except MCPUpstreamAuthError as e:
# Pass-through server returned 401 — surface it to the client so
# standards-compliant MCP clients trigger the upstream OAuth flow.
raise HTTPException(
status_code=e.status_code,
detail="Unauthorized",
headers=(
{"www-authenticate": e.www_authenticate}
if e.www_authenticate
else None
),
)
except HTTPException:
# Re-raise HTTP exceptions to preserve status codes and details
raise
@ -3237,6 +3250,18 @@ if MCP_AVAILABLE:
_sse_client_ip,
):
await sse_session_manager.handle_request(scope, receive, send)
except MCPUpstreamAuthError as e:
# Pass-through server returned 401 — surface it to the client so
# standards-compliant MCP clients trigger the upstream OAuth flow.
raise HTTPException(
status_code=e.status_code,
detail="Unauthorized",
headers=(
{"www-authenticate": e.www_authenticate}
if e.www_authenticate
else None
),
)
except HTTPException:
# Re-raise HTTP exceptions to preserve status codes and details
# (e.g. 401 + WWW-Authenticate challenges from OAuth pass-through).