mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
Merge 2585e89e9c into e6c4580a31
This commit is contained in:
commit
bc56fe29d9
2 changed files with 48 additions and 3 deletions
|
|
@ -3638,12 +3638,25 @@ class MCPServerManager:
|
|||
# arbitrary bearer upstream, so we keep the v2 spec and ignore the override for these; the
|
||||
# REST tools preview supplies its not-yet-persisted token through the resolver
|
||||
# (cred_provider), never this path.
|
||||
#
|
||||
# dcr_bridge is a different origin, not a caller override: for a client-forwarded-token
|
||||
# server admitted through the gateway-DCR bridge, `_admit_dcr_bridge_delegate` has already
|
||||
# opened the caller's signed envelope and injected the real upstream credential it sealed —
|
||||
# `mcp_auth_header` here IS that unsealed credential, not an arbitrary bearer the caller
|
||||
# attached. Keeping `spec` for PassthroughConfig in this case discards that credential and
|
||||
# falls through to the v2 resolver's own passthrough headers, which the DCR caller has no
|
||||
# way to populate (it only ever presents the one envelope bearer, which admission already
|
||||
# strips before egress). So a dcr_bridge server always takes the v1 path here regardless of
|
||||
# spec.config, same as the non-exempted modes above.
|
||||
if (
|
||||
spec is not None
|
||||
and mcp_auth_header
|
||||
and not isinstance(
|
||||
spec.config,
|
||||
(AuthorizationCodeConfig, IdJagConfig, PassthroughConfig, TokenExchangeConfig),
|
||||
and (
|
||||
server.is_dcr_bridge
|
||||
or not isinstance(
|
||||
spec.config,
|
||||
(AuthorizationCodeConfig, IdJagConfig, PassthroughConfig, TokenExchangeConfig),
|
||||
)
|
||||
)
|
||||
):
|
||||
spec = None
|
||||
|
|
|
|||
|
|
@ -76,3 +76,35 @@ async def test_mcp_server_config_auth_value_header_used(token_key):
|
|||
emitted = next(client._resolved_auth.auth_flow(httpx.Request("POST", server.url)))
|
||||
assert emitted.headers["Authorization"] == "Bearer example_token"
|
||||
assert client.auth_type == MCPAuth.bearer_token
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dcr_bridge_oauth_delegate_uses_admission_injected_credential():
|
||||
"""A dcr_bridge + oauth_delegate server's mcp_auth_header must not be discarded.
|
||||
|
||||
``_admit_dcr_bridge_delegate`` opens the caller's signed envelope during admission and
|
||||
injects the real upstream credential it sealed as ``mcp_auth_header`` here -- it is not an
|
||||
arbitrary caller-supplied override. oauth_delegate maps to PassthroughConfig in the v2
|
||||
resolver (see outbound_credentials/adapter.py), which used to be exempted from the v1
|
||||
override path regardless of origin, silently discarding this credential and leaving the
|
||||
upstream MCP server with no Authorization header at all (regression: BerriAI/litellm#36358).
|
||||
"""
|
||||
server = MCPServer(
|
||||
server_id="test-dcr-bridge-server",
|
||||
name="Test DCR Bridge Server",
|
||||
server_name="test_dcr_bridge_server",
|
||||
alias="test_dcr_bridge",
|
||||
url="https://internal-mcp-server.example.com/mcp",
|
||||
transport=MCPTransport.http,
|
||||
auth_type=MCPAuth.oauth_delegate,
|
||||
dcr_bridge=True,
|
||||
)
|
||||
|
||||
manager = MCPServerManager()
|
||||
|
||||
client = await manager._create_mcp_client(
|
||||
server=server,
|
||||
mcp_auth_header="Bearer unsealed_upstream_token",
|
||||
)
|
||||
|
||||
assert client._mcp_auth_value == "Bearer unsealed_upstream_token"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue