fix(mcp): stop phantom 401 trace on OAuth2 passthrough success - #30411
fix(mcp): stop phantom 401 trace on OAuth2 passthrough success#30411ryan-crabbe-berri wants to merge 1 commit into
Conversation
For an OAuth2-mode MCP server the client sends its upstream OAuth token as `Authorization: Bearer <token>`. process_mcp_request validated that bearer as a LiteLLM key first, which always fails (virtual keys must start with `sk-`), and the auth exception handler logged that failure to the observability callbacks as a 401 error span before the OAuth2 passthrough recovered. Every successful OAuth2 tool call therefore carried a phantom 401 in its traces. Pass the token through directly when the target is OAuth2-mode and the bearer is not `sk-`-shaped, skipping the validation that could only fail. An `sk-`-shaped bearer still validates normally so a LiteLLM key supplied via Authorization keeps resolving the user.
Greptile SummaryThis PR fixes a phantom 401 OTel trace emitted on every successful OAuth2 passthrough tool call. The root cause was that
Confidence Score: 5/5Safe to merge — the change only eliminates a doomed, side-effectful validation call; the granted access level for non- The early-return path for non- No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py | Refactors the OAuth2 bearer handling in process_mcp_request to short-circuit LiteLLM key validation for non-sk- bearers on OAuth2-mode targets, eliminating the phantom 401 OTel span; backward-compat sk- bearer path and exception-handler fallback are both preserved. |
| tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py | Adds two new unit tests: one asserting user_api_key_auth is never invoked for a non-sk- bearer on an OAuth2 target, and one confirming an sk--shaped bearer still goes through LiteLLM key validation; both use only mocks with no real network calls. |
Reviews (1): Last reviewed commit: "fix(mcp): stop OAuth2 passthrough from l..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| client_ip=client_ip, | ||
| ) | ||
| bearer_token = _get_bearer_token(litellm_api_key) | ||
| if targets_oauth2 and not bearer_token.startswith("sk-"): |
There was a problem hiding this comment.
Medium: JWT proxy credentials bypass MCP identity checks
user_api_key_auth accepts more than sk- virtual keys: when JWT or OAuth2 proxy auth is enabled, a non-sk- bearer can be the caller's LiteLLM credential. This shortcut admits that request as anonymous on OAuth2 MCP targets, so a user whose token would normally resolve to key/team MCP permissions, spend/rate limits, and stored-token scoping can call targets through the anonymous OAuth2 passthrough path instead. Keep validating bearer tokens that can be LiteLLM credentials, or gate the no-validation passthrough behind an explicit upstream-delegated-auth signal rather than only not startswith("sk-").
PR overviewThis pull request updates MCP OAuth2 passthrough authentication handling to avoid emitting a misleading 401 trace when passthrough succeeds. The touched code is in the MCP server user API key authentication path. There is one open security issue remaining in the MCP authentication flow. The current shortcut for OAuth2 passthrough can let JWT or OAuth2 proxy credentials skip normal LiteLLM identity validation and proceed as anonymous, bypassing key/team MCP permissions, spend or rate limits, and stored-token scoping. No issues have been addressed yet, so the PR still needs a guardrail before this authentication path is safe. Open issues (1)
Fixed/addressed: 0 · PR risk: 7/10 |
107175c to
d562450
Compare
Relevant issues
Linear ticket
Pre-Submission checklist
make test-unitProof of Fix
For an OAuth2-mode MCP server such as Atlassian, an MCP client authenticates by sending its upstream OAuth token as
Authorization: Bearer <token>. Before this changeprocess_mcp_requestvalidated that bearer as a LiteLLM key first; that always fails for an opaque OAuth token, and the auth exception handler logged the failure to the observability callbacks as a 401 error span before the OAuth2 passthrough recovered. Every successful OAuth2 tool call therefore carried a phantom 401 in its tracesRepro against a live proxy that has an OAuth2 MCP server configured and
callbacks: ["otel"]:Before: the proxy logs
401: LiteLLM Virtual Key expected. Received=...and emits a 401 OTel span for the request even though the tool returns a resultAfter: no
Virtual Key expectedline is logged and no 401 span is emitted; the opaque bearer takes the passthrough path and the tool result is unchangedType
Bug Fix
Changes
process_mcp_requestforwards an opaque upstream OAuth2 token straight to an OAuth2-mode target instead of validating it as a LiteLLM key first. A bearer that LiteLLM can actually authenticate still validates normally so the user is resolved: ansk-key always, and a JWT-shaped bearer whenenable_jwt_authis on. A failed validation on a non-OAuth2 server still propagates instead of granting an anonymous session. The branch is factored into_resolve_oauth2_header_authwith early returnsTests added in
test_user_api_key_auth_mcp.py: opaque token skips validation (no phantom 401),sk-and JWT (when enabled) still validate, a JWT-shaped token passes through when JWT auth is off, and a non-sk-bearer on a non-OAuth2 target still raises