fix(mcp): resolve OAuth2 root endpoints returning "MCP server not found"#20784
Merged
ishaan-jaff merged 1 commit intoBerriAI:mainfrom Feb 10, 2026
Merged
Conversation
When MCP SDK hits root-level /register, /authorize, /token without server name prefix, auto-resolve to the single configured OAuth2 server. Also fix WWW-Authenticate header to use correct public URL behind reverse proxy.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Greptile OverviewGreptile SummaryFixed OAuth2 flow failing with "MCP server not found" when MCP SDK hits root-level endpoints ( Key changes:
Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py | Added _resolve_oauth2_server_for_root_endpoints() helper to auto-resolve single OAuth2 server for root endpoints; applied resolver to /register, /authorize, /token and discovery endpoints |
| litellm/proxy/_experimental/mcp_server/server.py | Fixed WWW-Authenticate header to use get_request_base_url() instead of str(request.base_url) for correct public URL behind reverse proxy |
| tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py | Added 5 new tests for root-level OAuth endpoint resolution; cleared global registry in existing tests to prevent resolver interference |
Sequence Diagram
sequenceDiagram
participant Client as MCP SDK Client
participant Proxy as LiteLLM Proxy
participant Resolver as _resolve_oauth2_server_for_root_endpoints()
participant Provider as OAuth2 Provider
Note over Client,Proxy: Root-level OAuth flow (no server name prefix)
Client->>Proxy: GET /.well-known/oauth-authorization-server
Proxy->>Resolver: Check for single OAuth2 server
Resolver-->>Proxy: Return server (if exactly 1 exists)
Proxy-->>Client: Return endpoints with /server_name/ prefix
Client->>Proxy: POST /register (no server name)
Proxy->>Resolver: Check for single OAuth2 server
Resolver-->>Proxy: Return resolved server
Proxy-->>Client: Return client_id: "server_name"
Client->>Proxy: GET /authorize?client_id=dummy_client
Proxy->>Resolver: Check for single OAuth2 server
Resolver-->>Proxy: Return resolved server
Proxy->>Provider: Redirect with actual client_id
Provider-->>Client: 307 Redirect to OAuth provider
Client->>Provider: User authorizes
Provider-->>Proxy: Callback with auth code
Proxy-->>Client: 302 Redirect with code
Client->>Proxy: POST /token (no server name)
Proxy->>Resolver: Check for single OAuth2 server
Resolver-->>Proxy: Return resolved server
Proxy->>Provider: Exchange code for token
Provider-->>Proxy: Return access_token
Proxy-->>Client: Return access_token
|
@michelligabriele thanks for this. I believe this will fix my issue, can you please confirm ? cc: @jquinter |
krrishdholakia
pushed a commit
that referenced
this pull request
Feb 10, 2026
…logging_payload is missing (#20851) * fix: Preserved nullable object fields by carrying schema properties * Fix: _convert_schema_types * Fix all mypy issues * Add alert about email notifications * fixing tests * extending timeout for long running tests * Text changes * [Feat] MCP Oauth2 Fixes - Add support for MCP M2M Oauth2 support (#20788) * add has_client_credentials * MCPOAuth2TokenCache * init MCP Oauth2 constants * MCPOAuth2TokenCache * resolve_mcp_auth * test fixes * docs fix * address greptile review: min TTL, env-configurable constants, tests, docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix mypy lint * fix oauth2 * remove old files * docs fix * address greptile comments * fix: atomic lock creation + validate JSON response shape - Use dict.setdefault() for atomic per-server lock creation - Add isinstance(body, dict) check before accessing token response fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace asserts with proper guards, wrap HTTP errors with context - Replace `assert` statements with `if/raise ValueError` (asserts can be disabled with python -O in production) - Wrap `httpx.HTTPStatusError` to provide a clear error message with server_id and status code - Add tests for HTTP error and non-dict JSON response error paths - Remove unused imports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * [UI] M2M OAuth2 UI Flow (#20794) * add has_client_credentials * MCPOAuth2TokenCache * init MCP Oauth2 constants * MCPOAuth2TokenCache * resolve_mcp_auth * test fixes * docs fix * address greptile review: min TTL, env-configurable constants, tests, docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix mypy lint * fix oauth2 * ui feat fixes * test M2M * test fix * ui feats * ui fixes * ui fix client ID * fix: backend endpoints * docs fix * fixes greptile --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * [Fix] prevent shared backend model key from being polluted by per-deployment custom pricing (#20679) * bug: custom price override for models * added associated test * fix(mcp): resolve OAuth2 root endpoints returning "MCP server not found" (#20784) When MCP SDK hits root-level /register, /authorize, /token without server name prefix, auto-resolve to the single configured OAuth2 server. Also fix WWW-Authenticate header to use correct public URL behind reverse proxy. * Add support for langchain_aws via litellm passthrough * fix(proxy): return early instead of raising ValueError when standard_logging_payload is missing The `_PROXY_VirtualKeyModelMaxBudgetLimiter.async_log_success_event` hook raises `ValueError` when `standard_logging_payload` is `None`. This breaks non-standard call types (e.g. vLLM `/classify`) that do not populate the payload, and the resulting exception disrupts downstream success callbacks like Langfuse. Return early with a debug log instead, matching the existing pattern used for missing `user_api_key_model_max_budget`. Fixes #18986 --------- Co-authored-by: Sameer Kankute <sameer@berri.ai> Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com> Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Shivam Rawat <161387515+shivamrawat1@users.noreply.github.com> Co-authored-by: michelligabriele <gabriele.michelli@icloud.com>
This was referenced Feb 10, 2026
Sameerlite
added a commit
that referenced
this pull request
Feb 11, 2026
…logging_payload is missing (#20851) * fix: Preserved nullable object fields by carrying schema properties * Fix: _convert_schema_types * Fix all mypy issues * Add alert about email notifications * fixing tests * extending timeout for long running tests * Text changes * [Feat] MCP Oauth2 Fixes - Add support for MCP M2M Oauth2 support (#20788) * add has_client_credentials * MCPOAuth2TokenCache * init MCP Oauth2 constants * MCPOAuth2TokenCache * resolve_mcp_auth * test fixes * docs fix * address greptile review: min TTL, env-configurable constants, tests, docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix mypy lint * fix oauth2 * remove old files * docs fix * address greptile comments * fix: atomic lock creation + validate JSON response shape - Use dict.setdefault() for atomic per-server lock creation - Add isinstance(body, dict) check before accessing token response fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace asserts with proper guards, wrap HTTP errors with context - Replace `assert` statements with `if/raise ValueError` (asserts can be disabled with python -O in production) - Wrap `httpx.HTTPStatusError` to provide a clear error message with server_id and status code - Add tests for HTTP error and non-dict JSON response error paths - Remove unused imports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * [UI] M2M OAuth2 UI Flow (#20794) * add has_client_credentials * MCPOAuth2TokenCache * init MCP Oauth2 constants * MCPOAuth2TokenCache * resolve_mcp_auth * test fixes * docs fix * address greptile review: min TTL, env-configurable constants, tests, docs - Fix zero-TTL edge case: floor at MCP_OAUTH2_TOKEN_CACHE_MIN_TTL (10s) - Make all MCP OAuth2 constants env-configurable via os.getenv() - Move test file to follow 1:1 mapping convention (test_oauth2_token_cache.py) - Add MCP OAuth doc page (mcp_oauth.md) with M2M and PKCE sections - Update FAQ in mcp.md to reflect M2M support - Add E2E test script and config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix mypy lint * fix oauth2 * ui feat fixes * test M2M * test fix * ui feats * ui fixes * ui fix client ID * fix: backend endpoints * docs fix * fixes greptile --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * [Fix] prevent shared backend model key from being polluted by per-deployment custom pricing (#20679) * bug: custom price override for models * added associated test * fix(mcp): resolve OAuth2 root endpoints returning "MCP server not found" (#20784) When MCP SDK hits root-level /register, /authorize, /token without server name prefix, auto-resolve to the single configured OAuth2 server. Also fix WWW-Authenticate header to use correct public URL behind reverse proxy. * Add support for langchain_aws via litellm passthrough * fix(proxy): return early instead of raising ValueError when standard_logging_payload is missing The `_PROXY_VirtualKeyModelMaxBudgetLimiter.async_log_success_event` hook raises `ValueError` when `standard_logging_payload` is `None`. This breaks non-standard call types (e.g. vLLM `/classify`) that do not populate the payload, and the resulting exception disrupts downstream success callbacks like Langfuse. Return early with a debug log instead, matching the existing pattern used for missing `user_api_key_model_max_budget`. Fixes #18986 --------- Co-authored-by: Sameer Kankute <sameer@berri.ai> Co-authored-by: yuneng-jiang <yuneng.jiang@gmail.com> Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Shivam Rawat <161387515+shivamrawat1@users.noreply.github.com> Co-authored-by: michelligabriele <gabriele.michelli@icloud.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When MCP SDK hits root-level /register, /authorize, /token without server name prefix, auto-resolve to the single configured OAuth2 server. Also fix WWW-Authenticate header to use correct public URL behind reverse proxy.
Relevant issues
Follow-up to #20602 — separate bug reported by the same user.
Pre-Submission checklist
tests/litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unitType
🐛 Bug Fix
Changes
Problem
After connecting an MCP client to a LiteLLM proxy behind a reverse proxy (e.g.
https://llm.example.com/mcp), the OAuth2 flow fails with{"detail":"MCP server not found"}at the/authorizeendpoint. The customer's URL showed:/authorize?client_id=dummy_client— the MCP SDK was hitting root-level OAuth endpoints without the server name prefix.Root Cause (3 issues chained together)
server.py—WWW-Authenticateheader usedstr(request.base_url)(internal URL likehttp://0.0.0.0:4000) instead ofget_request_base_url(request)(public URL). Behind a reverse proxy, the MCP SDK can't follow the internal URL and falls back to root-level OAuth discovery.discoverable_endpoints.py/register— Root/register(no server name prefix) immediately returnedclient_id: "dummy_client"without trying to resolve the actual server.discoverable_endpoints.py/authorize—lookup_name = mcp_server_name or client_idresolved to"dummy_client"which doesn't match any configured server → 404.Fix
_resolve_oauth2_server_for_root_endpoints()helper that auto-resolves to the single configured OAuth2 server when root-level endpoints are hit without a server name prefix. ReturnsNonewhen 0 or 2+ OAuth2 servers exist (ambiguous case)./register,/authorize,/tokenendpoints and both_build_oauth_authorization_server_response()and_build_oauth_protected_resource_response().WWW-Authenticateheader inserver.pyto useget_request_base_url(request)for correct public URL behind reverse proxy.E2E Verification (via ngrok → LiteLLM proxy → Atlassian MCP)
/.well-known/oauth-authorization-server) now returns endpoints with/atlassian_mcp/prefix/registerreturnsclient_id: "atlassian_mcp"instead of"dummy_client"/authorize?client_id=dummy_clientreturns 307 redirect to Atlassian instead of 404Tests added
5 new tests in
tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py:test_authorize_root_resolves_single_oauth2_servertest_authorize_root_fails_with_multiple_oauth2_serverstest_token_root_resolves_single_oauth2_servertest_register_root_resolves_single_oauth2_servertest_discovery_root_includes_server_name_prefix