mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-25 01:02:15 +00:00
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:
parent
f0eb54ea9f
commit
d1d9ff0121
4 changed files with 29 additions and 39 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue