mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-25 01:02:15 +00:00
fix(mcp): tighten cold-start bypass against CSV paths + dedupe upstream auth probe
- Return None from _parse_mcp_server_names_from_path for CSV multi-server paths (/mcp/a,b). The regex previously truncated at the first comma and silently passed a single server name to the cold-start gate. - Switch _is_mcp_passthrough_cold_start to all-targets semantics, matching _target_servers_use_oauth2: one non-passthrough target in a co-targeted set must not flip the anonymous-admission bypass open for the others. - Drop the redundant HTTPStatusError block in _extract_upstream_auth_failure - any HTTPStatusError carries a .response, so the preceding generic block already handles 401/403 detection. Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
parent
326c6bb84f
commit
78cb412419
2 changed files with 23 additions and 20 deletions
|
|
@ -19,10 +19,18 @@ from litellm.proxy.auth.ip_address_utils import IPAddressUtils
|
|||
|
||||
def _parse_mcp_server_names_from_path(path: str) -> Optional[List[str]]:
|
||||
"""Parse a single MCP server name from /mcp/{name} or /{name}/mcp path patterns.
|
||||
Returns None for the aggregate /mcp route (no bypass for multi-server paths)."""
|
||||
m = re.match(r"^/mcp/([^/,?#]+)", path)
|
||||
Returns None for the aggregate /mcp route (no bypass for multi-server paths).
|
||||
|
||||
Multi-server CSV paths like ``/mcp/server1,server2`` also return ``None`` —
|
||||
the cold-start bypass must not activate when any of the co-targeted servers
|
||||
might not be passthrough-eligible. The regex stops at ``/?#`` only; the
|
||||
comma check is handled explicitly below."""
|
||||
m = re.match(r"^/mcp/([^/?#]+)", path)
|
||||
if m:
|
||||
return [m.group(1)]
|
||||
segment = m.group(1)
|
||||
if "," in segment:
|
||||
return None
|
||||
return [segment]
|
||||
m = re.match(r"^/([^/,?#]+)/mcp", path)
|
||||
if m:
|
||||
return [m.group(1)]
|
||||
|
|
@ -32,10 +40,15 @@ def _parse_mcp_server_names_from_path(path: str) -> Optional[List[str]]:
|
|||
def _is_mcp_passthrough_cold_start(
|
||||
scope: Scope, mcp_servers: Optional[List[str]], client_ip: Optional[str]
|
||||
) -> bool:
|
||||
"""True when the request targets a pass-through server with no auth headers —
|
||||
the cold-start OAuth discovery case per RFC 9728 / MCP Authorization spec.
|
||||
Lets the route handler's 401 emitter produce the spec-compliant WWW-Authenticate
|
||||
challenge instead of surfacing a generic admission error."""
|
||||
"""True only when EVERY targeted server is a pass-through server with no
|
||||
auth headers — the cold-start OAuth discovery case per RFC 9728 / MCP
|
||||
Authorization spec. Lets the route handler's 401 emitter produce the
|
||||
spec-compliant WWW-Authenticate challenge instead of surfacing a generic
|
||||
admission error.
|
||||
|
||||
Uses "all" semantics (mirrors :meth:`MCPRequestHandler._target_servers_use_oauth2`):
|
||||
one non-passthrough target in a co-targeted set must not flip the bypass
|
||||
open for the others. Fails closed when any target cannot be resolved."""
|
||||
if not mcp_servers:
|
||||
return False
|
||||
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
|
||||
|
|
@ -46,9 +59,9 @@ def _is_mcp_passthrough_cold_start(
|
|||
server = global_mcp_server_manager.get_mcp_server_by_name(
|
||||
name, client_ip=client_ip
|
||||
)
|
||||
if server is not None and getattr(server, "is_oauth_passthrough", False):
|
||||
return True
|
||||
return False
|
||||
if server is None or not getattr(server, "is_oauth_passthrough", False):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _is_litellm_auth_admission_error(exc: Exception) -> bool:
|
||||
|
|
|
|||
|
|
@ -153,16 +153,6 @@ def _extract_upstream_auth_failure(
|
|||
www_authenticate = None
|
||||
return status_code, www_authenticate
|
||||
|
||||
if isinstance(current, HTTPStatusError):
|
||||
status_code = getattr(current.response, "status_code", None)
|
||||
if isinstance(status_code, int) and status_code in (401, 403):
|
||||
www_authenticate = None
|
||||
try:
|
||||
www_authenticate = current.response.headers.get("www-authenticate")
|
||||
except Exception:
|
||||
pass
|
||||
return status_code, www_authenticate
|
||||
|
||||
# anyio / PEP 654 ExceptionGroup
|
||||
sub_exceptions = getattr(current, "exceptions", None)
|
||||
if sub_exceptions:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue