fix(mcp,auth): address greptile review concerns

- handle_sse_mcp now calls _raise_preemptive_401_for_unauthenticated_servers
  so SSE clients to pass-through OAuth MCP servers receive the RFC 9728
  401 + WWW-Authenticate challenge that the streamable-HTTP path already emits.
- get_request_route strips a trailing slash from root_path before length-based
  prefix removal so non-canonical ASGI root_path values like "/litellm/"
  don't strip the leading slash from the returned route.
- _mcp_oauth_user_api_key_auth's cookie JWT decode now passes
  options={"verify_aud": False} so a future revision of the UI session
  JWT containing an aud claim cannot silently downgrade the request to
  unauthenticated.

Co-authored-by: Claude <claude@anthropic.com>
This commit is contained in:
Claude (greptile fixer) 2026-05-20 15:31:57 +00:00
parent 76c704be34
commit 298936f83d
No known key found for this signature in database
3 changed files with 18 additions and 5 deletions

View file

@ -3025,6 +3025,15 @@ if MCP_AVAILABLE:
verbose_logger.debug(
f"MCP server auth headers: {list(mcp_server_auth_headers.keys()) if mcp_server_auth_headers else None}"
)
# https://datatracker.ietf.org/doc/html/rfc9728#name-www-authenticate-response
await _raise_preemptive_401_for_unauthenticated_servers(
scope=scope,
mcp_servers=mcp_servers,
oauth2_headers=oauth2_headers,
mcp_server_auth_headers=mcp_server_auth_headers,
user_api_key_auth=user_api_key_auth,
client_ip=_sse_client_ip,
)
set_auth_context(
user_api_key_auth=user_api_key_auth,
mcp_auth_header=mcp_auth_header,

View file

@ -506,13 +506,16 @@ def get_request_route(request: Request) -> str:
if not isinstance(scope, dict):
return str(request.url.path)
raw_path: str = str(scope.get("path", request.url.path))
root_path: str = str(scope.get("app_root_path", scope.get("root_path", "")))
root_path: str = str(
scope.get("app_root_path", scope.get("root_path", ""))
).rstrip("/")
if not isinstance(raw_path, str):
return str(request.url.path)
# Only strip root_path when it is a meaningful prefix (not bare "/").
# Stripping bare "/" would remove the leading slash from every path
# e.g. "/team/new" → "team/new", breaking route matching.
if root_path and root_path != "/" and raw_path.startswith(root_path):
# Only strip root_path when it is a meaningful prefix. Trailing
# slashes are stripped above so the result always keeps its leading
# "/" — stripping a bare "/" or "/prefix/" would otherwise produce
# paths like "team/new" and break route matching.
if root_path and raw_path.startswith(root_path):
return raw_path[len(root_path) :]
return raw_path
except Exception as e:

View file

@ -1522,6 +1522,7 @@ if MCP_AVAILABLE:
token_cookie,
master_key,
algorithms=["HS256"],
options={"verify_aud": False},
)
if decoded.get("login_method") in ("sso", "username_password"):
cookie_key = decoded.get("key", "")