fix(mcp): consistent www-authenticate casing + SSE toolset scoping

- Normalize the WWW-Authenticate header key emitted by
  _check_passthrough_upstream_auth to lowercase to match the other 401
  emitters in the OAuth pass-through flow.
- Mirror the streamable HTTP handler's toolset scoping in handle_sse_mcp:
  strip client-supplied x-mcp-toolset-id and apply _apply_toolset_scope
  before _check_passthrough_upstream_auth so the upstream probe list is
  derived from the fully-authorized server set.
- Tighten _has_client_supplied_mcp_auth signature so
  mcp_server_auth_headers is Optional, matching its caller in
  process_mcp_request.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-21 17:55:55 +00:00
parent e9ff79c96a
commit 7dfef63a02
No known key found for this signature in database
2 changed files with 22 additions and 3 deletions

View file

@ -93,7 +93,7 @@ def _is_litellm_auth_admission_error(exc: Exception) -> bool:
def _has_client_supplied_mcp_auth(
mcp_auth_header: Optional[str],
mcp_server_auth_headers: Dict[str, Dict[str, str]],
mcp_server_auth_headers: Optional[Dict[str, Dict[str, str]]],
) -> bool:
return bool(mcp_auth_header) or bool(mcp_server_auth_headers)

View file

@ -3142,7 +3142,7 @@ if MCP_AVAILABLE:
raise HTTPException(
status_code=401,
detail="Unauthorized",
headers={"WWW-Authenticate": www_authenticate},
headers={"www-authenticate": www_authenticate},
)
if probe_status == 403:
# Token is valid but the caller lacks permission — do not hint
@ -3311,10 +3311,29 @@ if MCP_AVAILABLE:
user_api_key_auth=user_api_key_auth,
client_ip=_sse_client_ip,
)
# Strip any client-supplied x-mcp-toolset-id to prevent forgery.
scope["headers"] = [
(k, v)
for k, v in scope.get("headers", [])
if k.lower() != b"x-mcp-toolset-id"
]
# Apply toolset scope if set server-side via ContextVar so the
# downstream probe list matches the fully-authorized server set
# (mirrors the streamable HTTP handler).
active_toolset_id = _mcp_active_toolset_id.get()
if active_toolset_id and user_api_key_auth is not None:
user_api_key_auth = await _apply_toolset_scope(
user_api_key_auth, active_toolset_id
)
# Pre-flight auth check for pass-through servers: surface upstream
# 401/403 as a proper challenge before the SSE session commits 200
# headers, so clients can refresh their OAuth token instead of
# being stuck with a silently empty tool list.
# being stuck with a silently empty tool list. Must run after
# toolset scoping so the probe list is derived from the fully-
# authorized server set, not the raw user-supplied names.
await _check_passthrough_upstream_auth(
scope, user_api_key_auth, mcp_servers, _sse_client_ip
)