litellm/tests/test_litellm/proxy/_experimental/mcp_server
tin-berri ec808edece
fix(mcp): resolve per-user OAuth identity authoritatively at the token endpoint (#31657)
* fix(mcp): resolve per-user OAuth identity authoritatively at the token endpoint

The OAuth token endpoint stored a user's per-server token under the identity
returned by _extract_user_id_from_request, which read only the Authorization
header and did getattr(cached, "user_id") on a raw user_api_key_cache lookup
with no model_type rehydration and no DB fallback. That silently returned None
in two common cases on a multi-replica gateway: the LiteLLM key arrives on
x-litellm-api-key (what MCP clients such as Claude Desktop and Claude Code
send) rather than Authorization, and a cross-replica cache hit deserializes to
a plain dict rather than a UserAPIKeyAuth, so getattr finds no attribute. When
it returned None the token was not persisted.

This was survivable until the authorization_code v2 migration began stripping
the caller's Authorization for migrated per-user OAuth servers and routing the
preemptive 401 existence check through the stored token, so a persist miss now
hard-fails: the egress challenges with 401 on every reconnect (the client sees
"rejected them on reconnect" or a successful connect with zero tools).

Resolve identity through get_key_object, the canonical resolver that reads the
cache with model_type and falls back to the DB, and accept the key from
x-litellm-api-key as well as Authorization. The silent persist skip is now a
warning. The caller-Authorization stripping stays as is, since reinstating it
would reopen the cross-user credential override it was added to prevent.

* fix(mcp): reject blocked or expired keys when resolving the token-endpoint identity

The OAuth token endpoint is unauthenticated, and get_key_object resolves a key row without the
blocked/expiry checks the main user_api_key_auth pipeline runs (that pipeline is bypassed here). So
a holder of a revoked or expired LiteLLM key could POST a valid upstream authorization code with
that key in x-litellm-api-key/Authorization and write or overwrite the stored per-user OAuth token
for that key's user. The cache-only resolver this replaced incidentally dropped blocked keys
(blocking purges the cache entry), so moving to the authoritative cache-then-DB resolution removed
that accidental shield.

Validate the resolved key before trusting its identity: return None when blocked or expired, so the
upsert is skipped. Deleted keys are already rejected, since get_key_object raises on a missing row.
Regression tests cover the blocked and expired cases and fail without the guard.
2026-06-29 18:09:19 -07:00
..
auth feat(mcp): opt-in least-privilege default for team key MCP access (#31380) 2026-06-25 18:49:15 -07:00
guardrail_translation fix(tests): Add missing mocks for MCP IP filtering and updated APIs (#20652) 2026-02-07 11:30:49 -08:00
outbound_credentials feat(mcp): cross-replica single-flight refresh for the v2 per-user OAuth store [2/2] (#31493) 2026-06-27 16:27:34 -07:00
test_byok_oauth_endpoints.py feat(mcp): allow native MCP OAuth support for cursor (#28327) 2026-05-20 15:28:44 -07:00
test_callback_oauth_error_responses.py Litellm oss staging 250526 (#28770) 2026-05-26 11:57:39 -07:00
test_db_credentials.py fix(ui): persist Tools-tab MCP OAuth token to DB (#29809) 2026-06-05 22:29:56 -07:00
test_discoverable_endpoints.py fix(mcp): resolve per-user OAuth identity authoritatively at the token endpoint (#31657) 2026-06-29 18:09:19 -07:00
test_is_tool_name_prefixed.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_jwt_mcp_enforcement.py fix(mcp): resolve team.access_group_ids → MCP servers (#28997) 2026-05-27 12:36:50 -07:00
test_jwt_mcp_simple.py fix(mcp): resolve team.access_group_ids → MCP servers (#28997) 2026-05-27 12:36:50 -07:00
test_mcp_cost_calculator.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_mcp_custom_fields.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_mcp_debug.py chore: litellm oss staging (#30968) 2026-06-23 07:31:44 -07:00
test_mcp_discovery.py fix(mcp): default Linear MCP registry entry to streamable HTTP (#30396) 2026-06-13 14:45:47 -07:00
test_mcp_elicitation_handler.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_env_vars.py fix(mcp): drop orphaned per-user credential rows when an MCP server is deleted (#30141) 2026-06-10 15:56:58 -07:00
test_mcp_header_alias_utils.py feat(mcp): Add tool call and tool list support via UI for Oauth mcps (#28454) 2026-05-22 09:04:04 -07:00
test_mcp_hook_extra_headers.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_metadata_preservation.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_mcp_oauth_passthrough.py [internal copy of #28008] Support MCP OAuth passthrough and issuer-scoped JWT auth (#28356) 2026-06-02 12:22:04 -07:00
test_mcp_oauth_passthrough_cold_start.py [internal copy of #28008] Support MCP OAuth passthrough and issuer-scoped JWT auth (#28356) 2026-06-02 12:22:04 -07:00
test_mcp_oauth_passthrough_tools.py feat: litellm oss 110626 (#30202) 2026-06-11 22:30:26 -07:00
test_mcp_partial_update.py fix(mcp): clear allowed_tools and tool overrides on MCP server edit (#29411) 2026-06-01 21:28:29 -07:00
test_mcp_sampling_completion_flow.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_sampling_model_access.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_sampling_model_resolution.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_sampling_priority_selection.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_sampling_request_builder.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_sampling_response_conversion.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_sampling_tool_conversion.py Litellm oss staging 040626 (#29671) 2026-06-04 11:07:20 -07:00
test_mcp_server.py feat(mcp): migrate authorization_code MCP to the v2 resolver (single-replica) [1/2] (#31473) 2026-06-26 21:19:57 -07:00
test_mcp_server_identity_env.py chore: litellm oss 170626 (#30637) 2026-06-17 21:11:12 -07:00
test_mcp_server_manager.py feat(mcp): migrate authorization_code MCP to the v2 resolver (single-replica) [1/2] (#31473) 2026-06-26 21:19:57 -07:00
test_mcp_session_logging.py Add MCP semantic conventions to otelv2 (#29468) 2026-06-02 11:45:36 -07:00
test_mcp_sigv4_auth.py feat(mcp): per-server env vars with global + per-user scopes (#28917) 2026-06-05 20:15:11 -07:00
test_mcp_stale_session.py feat(mcp): migrate authorization_code MCP to the v2 resolver (single-replica) [1/2] (#31473) 2026-06-26 21:19:57 -07:00
test_mcp_toolset_scope.py fix(mcp): resolve toolset tools by the server's known prefix (#31254) 2026-06-24 20:50:16 -07:00
test_oauth2_token_cache.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00
test_openapi_to_mcp_generator.py fix(mcp): forward extra_headers for OpenAPI MCP tools (#27383) 2026-05-09 15:10:54 -04:00
test_openapi_tool_auth.py fix(mcp): use canonical proxy_logging_obj, deny when MCP server is unresolvable 2026-05-01 22:28:46 +00:00
test_rest_endpoints.py feat(mcp): migrate authorization_code MCP to the v2 resolver (single-replica) [1/2] (#31473) 2026-06-26 21:19:57 -07:00
test_semantic_tool_filter.py chore: litellm oss staging (#30968) 2026-06-23 07:31:44 -07:00
test_short_mcp_tool_prefix.py fix(mcp): resolve toolset tools by the server's known prefix (#31254) 2026-06-24 20:50:16 -07:00
test_ui_session_utils.py style: run black formatter on files from main merge 2026-04-17 13:02:59 -07:00