litellm - 💡(How to fix) Fix [Bug]: Interactive OAuth2 MCP server returns 500 instead of 401+WWW-Authenticate when x-litellm-api-key is present but no Google OAuth token exists yet

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When an MCP server is configured with auth_type: oauth2, authorization_url, and token_url (interactive PKCE flow — NOT delegate_auth_to_upstream), sending a request with only x-litellm-api-key (valid LiteLLM key, no Authorization header) returns HTTP 500 instead of a clean HTTP 401 + WWW-Authenticate header.

This prevents MCP clients (OpenCode, Claude Code) from initiating the OAuth browser flow, because they need a clean 401 with the authorization_uri to discover the authorize endpoint. Instead they receive a 500 and fail with "Error POSTing to endpoint: {"error":"MCP request failed","details":""}".

Error Message

Traceback (most recent call last): File ".../litellm/proxy/_experimental/mcp_server/server.py", line 2946, in handle_streamable_http_mcp ) = await extract_mcp_auth_context(scope, path) File ".../litellm/proxy/_experimental/mcp_server/server.py", line 2617, in extract_mcp_auth_context ) = await MCPRequestHandler.process_mcp_request(scope) File ".../litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py", line 124, in process_mcp_request validated_user_api_key_auth = await user_api_key_auth(...) File ".../litellm/proxy/auth/user_api_key_auth.py", line 1675, in _user_api_key_auth_builder raise ProxyException(...)

Root Cause

In process_mcp_request in user_api_key_auth_mcp.py, when has_explicit_litellm_key is True (i.e., x-litellm-api-key header is present), the code calls user_api_key_auth() with the LiteLLM key. For a valid key this succeeds. However, when the subsequent MCP handler in handle_streamable_http_mcp tries to resolve the OAuth2 server and finds no stored Google token for the user, it should raise an HTTPException(401) with WWW-Authenticate. Instead, the exception is not being surfaced correctly through the ASGI response path.

The result: the ProxyException raised internally during auth context extraction for the x-litellm-api-key path is not caught by the ProxyException handler added in PR #22737 / PR #23499 for the Authorization-only path.

Fix Action

Workaround

Use delegate_auth_to_upstream: true on the server config. This bypasses LiteLLM's auth check entirely and lets the MCP client handle OAuth directly with the upstream. The client (OpenCode, Claude Code) then stores the token locally rather than in LiteLLM's DB.

mcp_servers:
  Gmail:
    url: "https://gmailmcp.googleapis.com/mcp/v1"
    transport: "http"
    auth_type: "oauth2"
    delegate_auth_to_upstream: true

This works but loses the per-user server-side token storage benefit of the interactive PKCE flow.

Code Example

mcp_servers:
  Gmail:
    url: "https://gmailmcp.googleapis.com/mcp/v1"
    transport: "http"
    auth_type: "oauth2"
    client_id: "<google-oauth-client-id>"
    client_secret: "<google-oauth-client-secret>"
    authorization_url: "https://accounts.google.com/o/oauth2/v2/auth"
    token_url: "https://oauth2.googleapis.com/token"
    scopes:
      - "https://www.googleapis.com/auth/gmail.modify"
      - "https://www.googleapis.com/auth/userinfo.profile"

general_settings:
  use_x_forwarded_for: true
  mcp_trusted_proxy_ranges:
    - "169.254.0.0/16"

---

curl -X POST https://your-litellm-proxy/Gmail/mcp \
  -H "x-litellm-api-key: Bearer sk-your-valid-key" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

---

HTTP/2 401
WWW-Authenticate: Bearer authorization_uri=https://your-litellm-proxy/.well-known/oauth-authorization-server/Gmail

---

HTTP/2 500
{"error": "MCP request failed", "details": ""}

---

Traceback (most recent call last):
  File ".../litellm/proxy/_experimental/mcp_server/server.py", line 2946, in handle_streamable_http_mcp
    ) = await extract_mcp_auth_context(scope, path)
  File ".../litellm/proxy/_experimental/mcp_server/server.py", line 2617, in extract_mcp_auth_context
    ) = await MCPRequestHandler.process_mcp_request(scope)
  File ".../litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py", line 124, in process_mcp_request
    validated_user_api_key_auth = await user_api_key_auth(...)
  File ".../litellm/proxy/auth/user_api_key_auth.py", line 1675, in _user_api_key_auth_builder
    raise ProxyException(...)

---

mcp_servers:
  Gmail:
    url: "https://gmailmcp.googleapis.com/mcp/v1"
    transport: "http"
    auth_type: "oauth2"
    delegate_auth_to_upstream: true
RAW_BUFFERClick to expand / collapse

Description

When an MCP server is configured with auth_type: oauth2, authorization_url, and token_url (interactive PKCE flow — NOT delegate_auth_to_upstream), sending a request with only x-litellm-api-key (valid LiteLLM key, no Authorization header) returns HTTP 500 instead of a clean HTTP 401 + WWW-Authenticate header.

This prevents MCP clients (OpenCode, Claude Code) from initiating the OAuth browser flow, because they need a clean 401 with the authorization_uri to discover the authorize endpoint. Instead they receive a 500 and fail with "Error POSTing to endpoint: {"error":"MCP request failed","details":""}".

LiteLLM Version

1.85.0 (Docker image docker.io/litellm/litellm:1.85.0)

Configuration

mcp_servers:
  Gmail:
    url: "https://gmailmcp.googleapis.com/mcp/v1"
    transport: "http"
    auth_type: "oauth2"
    client_id: "<google-oauth-client-id>"
    client_secret: "<google-oauth-client-secret>"
    authorization_url: "https://accounts.google.com/o/oauth2/v2/auth"
    token_url: "https://oauth2.googleapis.com/token"
    scopes:
      - "https://www.googleapis.com/auth/gmail.modify"
      - "https://www.googleapis.com/auth/userinfo.profile"

general_settings:
  use_x_forwarded_for: true
  mcp_trusted_proxy_ranges:
    - "169.254.0.0/16"

Environment: Cloud Run behind Google Load Balancer with PROXY_BASE_URL set.

Steps to Reproduce

  1. Configure LiteLLM with an OAuth2 interactive MCP server (Google Workspace Gmail MCP, or any OAuth2 server requiring authorization_url)
  2. Send a tools/list or initialize POST to /Gmail/mcp (namespaced URL) with only x-litellm-api-key:
curl -X POST https://your-litellm-proxy/Gmail/mcp \
  -H "x-litellm-api-key: Bearer sk-your-valid-key" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
  1. Observe HTTP 500 response

Expected Behavior

HTTP 401 with WWW-Authenticate header pointing to the LiteLLM authorize endpoint:

HTTP/2 401
WWW-Authenticate: Bearer authorization_uri=https://your-litellm-proxy/.well-known/oauth-authorization-server/Gmail

This allows the MCP client to discover /Gmail/authorize, redirect the user to Google's consent screen, and complete the PKCE flow — storing the token in LiteLLM's DB for future requests.

Actual Behavior

HTTP/2 500
{"error": "MCP request failed", "details": ""}

Server logs show a ProxyException from user_api_key_auth:

Traceback (most recent call last):
  File ".../litellm/proxy/_experimental/mcp_server/server.py", line 2946, in handle_streamable_http_mcp
    ) = await extract_mcp_auth_context(scope, path)
  File ".../litellm/proxy/_experimental/mcp_server/server.py", line 2617, in extract_mcp_auth_context
    ) = await MCPRequestHandler.process_mcp_request(scope)
  File ".../litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py", line 124, in process_mcp_request
    validated_user_api_key_auth = await user_api_key_auth(...)
  File ".../litellm/proxy/auth/user_api_key_auth.py", line 1675, in _user_api_key_auth_builder
    raise ProxyException(...)

The ProxyException is not caught and converted to a 401 — it propagates as a 500.

Root Cause

In process_mcp_request in user_api_key_auth_mcp.py, when has_explicit_litellm_key is True (i.e., x-litellm-api-key header is present), the code calls user_api_key_auth() with the LiteLLM key. For a valid key this succeeds. However, when the subsequent MCP handler in handle_streamable_http_mcp tries to resolve the OAuth2 server and finds no stored Google token for the user, it should raise an HTTPException(401) with WWW-Authenticate. Instead, the exception is not being surfaced correctly through the ASGI response path.

The result: the ProxyException raised internally during auth context extraction for the x-litellm-api-key path is not caught by the ProxyException handler added in PR #22737 / PR #23499 for the Authorization-only path.

Workaround

Use delegate_auth_to_upstream: true on the server config. This bypasses LiteLLM's auth check entirely and lets the MCP client handle OAuth directly with the upstream. The client (OpenCode, Claude Code) then stores the token locally rather than in LiteLLM's DB.

mcp_servers:
  Gmail:
    url: "https://gmailmcp.googleapis.com/mcp/v1"
    transport: "http"
    auth_type: "oauth2"
    delegate_auth_to_upstream: true

This works but loses the per-user server-side token storage benefit of the interactive PKCE flow.

Impact

  • MCP clients cannot initiate the LiteLLM-brokered OAuth flow for Google Workspace MCP servers (Gmail, Drive, Calendar, etc.) when using x-litellm-api-key as the LiteLLM authentication method
  • This is the recommended auth method per the LiteLLM docs (to keep Authorization free for OAuth tokens)
  • The interactive PKCE flow with server-side token storage (the core POC value) does not work end-to-end

Related Issues / PRs

  • PR #22737 — fixed ProxyException → 500 for Authorization-only path
  • PR #23499 — fixed 401/404 for some auth error cases
  • PR #27847 — added pre-flight probe for token-forwarding servers

None of these cover the x-litellm-api-key present + interactive OAuth2 server path.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

litellm - 💡(How to fix) Fix [Bug]: Interactive OAuth2 MCP server returns 500 instead of 401+WWW-Authenticate when x-litellm-api-key is present but no Google OAuth token exists yet