From a2b40ddadc3ce326d6e79c0b9121d22242b7b21d Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Tue, 23 Jun 2026 17:47:56 -0700 Subject: [PATCH] fix(mcp): defer to v1 when an inbound credential would be overridden The graft attaches the resolved static credential as an httpx.Auth, whose auth flow writes its header after extra_headers. That silently overrode an inbound Authorization: a per-request mcp_auth_header override, or a header supplied via a guardrail hook / static_headers / forwarded caller header. v1 lets those win, so the graft had inverted the credential precedence for the migrated static modes. Mirror the v2 egress credential-isolation invariant: defer the request to v1 when mcp_auth_header is set, or when the header the resolved credential would write is already present in extra_headers. none writes no header, so it never defers. --- .../mcp_server/mcp_server_manager.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index cd7119b33ec..a3690b108c9 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -57,6 +57,7 @@ from litellm.proxy._experimental.mcp_server.sampling_handler import ( ) from litellm.proxy._experimental.mcp_server.oauth2_token_cache import resolve_mcp_auth from litellm.proxy._experimental.mcp_server.outbound_credentials import ( + ApiKeyConfig, Error, Ok, UpstreamCredentialProvider, @@ -1955,6 +1956,24 @@ class MCPServerManager: """ transport = server.transport or MCPTransport.sse spec = None if transport == MCPTransport.stdio else to_server_spec(server) + # Credential-isolation invariant (mirrors the v2 egress path): the resolved credential + # rides the httpx auth flow, which writes its header after extra_headers, so it would + # overwrite an inbound credential. Defer to v1 when a per-request override is present, or + # when the credential's header is already supplied via extra_headers (guardrail hook, + # static_headers, or a forwarded caller header) — v1 lets those win. ``none`` writes no + # header, so it never conflicts. + if spec is not None and ( + mcp_auth_header + or ( + isinstance(spec.config, ApiKeyConfig) + and extra_headers + and any( + key.lower() == spec.config.header_name.lower() + for key in extra_headers + ) + ) + ): + spec = None auth_value = ( await resolve_mcp_auth(server, mcp_auth_header, subject_token=subject_token) if spec is None