mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(mcp): never validate a supplied header on the tools/list BYOK path
The pre-listing resolver ran the tool-call byok_auth_required check even when the caller already supplied x-mcp-auth, and it ran outside the per-server error boundary, so a single deprecated-header caller dropped the server from the aggregate list. Listing now returns a supplied header unchanged and falls back to the stored credential without raising Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
7ca6686e82
commit
c93d88ed79
2 changed files with 38 additions and 5 deletions
|
|
@ -1286,6 +1286,21 @@ async def _resolve_byok_mcp_auth_header(
|
|||
return mcp_auth_header
|
||||
|
||||
|
||||
async def _byok_listing_auth_header(
|
||||
mcp_server: MCPServer,
|
||||
user_api_key_auth: UserAPIKeyAuth | None,
|
||||
mcp_auth_header: str | dict[str, str] | None,
|
||||
) -> str | dict[str, str] | None:
|
||||
"""The credential a tools/list may use: a supplied header forwards unchanged, and a missing one
|
||||
falls back to the stored credential without the tool-call path's byok_auth_required raise."""
|
||||
if not mcp_server.is_byok or mcp_auth_header is not None:
|
||||
return mcp_auth_header
|
||||
|
||||
from litellm.proxy._experimental.mcp_server.operations import _get_byok_credential
|
||||
|
||||
return await _get_byok_credential(mcp_server, user_api_key_auth)
|
||||
|
||||
|
||||
def _client_forwarded_authorization_headers(
|
||||
mcp_server: MCPServer,
|
||||
oauth2_headers: dict[str, str] | None,
|
||||
|
|
@ -4401,11 +4416,7 @@ class MCPServerManager:
|
|||
verbose_logger.info("_get_tools_from_server for %s...", server.name)
|
||||
|
||||
client = None
|
||||
resolved_mcp_auth_header: Final = (
|
||||
mcp_auth_header
|
||||
if not server.is_byok or isinstance(mcp_auth_header, dict)
|
||||
else await _resolve_byok_mcp_auth_header(server, user_api_key_auth, mcp_auth_header)
|
||||
)
|
||||
resolved_mcp_auth_header: Final = await _byok_listing_auth_header(server, user_api_key_auth, mcp_auth_header)
|
||||
listed_caller: Final = ListedToolsCaller(
|
||||
user_api_key_auth=user_api_key_auth,
|
||||
mcp_auth_header=resolved_mcp_auth_header,
|
||||
|
|
|
|||
|
|
@ -7068,6 +7068,28 @@ class TestMCPServerManager:
|
|||
listed = manager.get_listed_tool(server, "turn", call_side)
|
||||
assert listed is not None and listed.description == "stored cred catalog"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_byok_supplied_header_lists_without_credential_validation(self):
|
||||
manager = MCPServerManager()
|
||||
server = MCPServer(
|
||||
server_id="byok-catalog",
|
||||
name="byok_catalog",
|
||||
transport=MCPTransport.http,
|
||||
url="http://byok-catalog",
|
||||
is_byok=True,
|
||||
)
|
||||
manager._create_mcp_client = AsyncMock(return_value=AsyncMock())
|
||||
manager._fetch_tools_with_timeout = AsyncMock(return_value=[MCPTool(name="turn", description="t", inputSchema={})])
|
||||
|
||||
with patch("litellm.proxy.proxy_server.prisma_client", None):
|
||||
await manager._get_tools_from_server(
|
||||
server=server,
|
||||
mcp_auth_header="Bearer hdr",
|
||||
user_api_key_auth=UserAPIKeyAuth(api_key="sk-litellm"),
|
||||
)
|
||||
|
||||
assert manager._create_mcp_client.await_args.kwargs["mcp_auth_header"] == "Bearer hdr"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("signer", "static_headers", "shared"),
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue