refactor(mcp): dedupe MCPUpstreamAuthError->HTTPException + thread client_ip into delegate-auth gate

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-20 18:07:36 +00:00
parent f0eb54ea9f
commit d1d9ff0121
No known key found for this signature in database
4 changed files with 29 additions and 39 deletions

View file

@ -207,7 +207,9 @@ class MCPRequestHandler:
elif (
not litellm_api_key
and MCPRequestHandler._target_servers_delegate_auth_to_upstream( # noqa: E501
path=request.url.path, mcp_servers=mcp_servers
path=request.url.path,
mcp_servers=mcp_servers,
client_ip=IPAddressUtils.get_mcp_client_ip(request),
)
):
# Operator opted this oauth2 server into upstream-delegated auth
@ -407,7 +409,7 @@ class MCPRequestHandler:
@staticmethod
def _target_servers_delegate_auth_to_upstream(
path: str, mcp_servers: Optional[List[str]]
path: str, mcp_servers: Optional[List[str]], client_ip: Optional[str]
) -> bool:
"""
True only when EVERY MCP server the request targets is configured for
@ -437,7 +439,9 @@ class MCPRequestHandler:
return False
for name in target_names:
server = global_mcp_server_manager.get_mcp_server_by_name(name)
server = global_mcp_server_manager.get_mcp_server_by_name(
name, client_ip=client_ip
)
if server is None or server.auth_type != MCPAuth.oauth2:
return False
# `is True` is intentional: opt-in must be an explicit boolean

View file

@ -2,6 +2,8 @@
from typing import Optional
from fastapi import HTTPException
class MCPUpstreamAuthError(Exception):
"""Raised when an upstream MCP server returns an authentication failure
@ -25,3 +27,19 @@ class MCPUpstreamAuthError(Exception):
self.www_authenticate = www_authenticate
self.server_name = server_name
super().__init__(f"Upstream MCP server {server_name!r} returned {status_code}")
def to_http_exception(self) -> HTTPException:
"""Convert this upstream-auth error into an ``HTTPException`` that
preserves the upstream status code and any ``WWW-Authenticate``
challenge, so standards-compliant MCP clients can trigger the
upstream OAuth flow.
"""
return HTTPException(
status_code=self.status_code,
detail="Unauthorized",
headers=(
{"www-authenticate": self.www_authenticate}
if self.www_authenticate
else None
),
)

View file

@ -445,15 +445,7 @@ if MCP_AVAILABLE:
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
),
)
raise e.to_http_exception()
except Exception as e:
verbose_logger.exception(f"Error getting tools from {server.name}: {e}")
return {
@ -546,15 +538,7 @@ if MCP_AVAILABLE:
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
),
)
raise e.to_http_exception()
except Exception as e:
verbose_logger.exception(f"Error getting tools from {server.name}: {e}")
return {

View file

@ -3183,15 +3183,7 @@ if MCP_AVAILABLE:
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
),
)
raise e.to_http_exception()
except HTTPException:
# Re-raise HTTP exceptions to preserve status codes and details
raise
@ -3275,15 +3267,7 @@ if MCP_AVAILABLE:
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
),
)
raise e.to_http_exception()
except HTTPException:
# Re-raise HTTP exceptions to preserve status codes and details
# (e.g. 401 + WWW-Authenticate challenges from OAuth pass-through).