diff --git a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py index dcb8a4a6f61..319db48ee98 100644 --- a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py +++ b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py @@ -3642,7 +3642,7 @@ class MCPRequestHandler: requires_fresh_policy: bool = False, ) -> list[str]: """ - Resolve MCP access groups to server IDs by querying BOTH the MCP server table (DB) AND config-loaded servers. + Resolve MCP access groups against the operation snapshot, or database and config outside an operation. ``requires_fresh_policy`` reads the writer and propagates a read fault instead of resolving to no servers. """ from litellm.proxy.proxy_server import prisma_client @@ -3653,6 +3653,10 @@ class MCPRequestHandler: global_mcp_server_manager, ) + snapshot: Final = global_mcp_server_manager.catalog.current() + if snapshot is not None: + return list(MCPRequestHandler._get_config_server_ids_for_access_groups(snapshot.servers, access_groups)) + # Use the new helper for config-loaded servers server_ids: Final = MCPRequestHandler._get_config_server_ids_for_access_groups( global_mcp_server_manager.config_mcp_servers, access_groups diff --git a/tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py index 74be8bcb827..9b04021340b 100644 --- a/tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/unit/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -18607,3 +18607,21 @@ async def test_catalog_revision_failure_preserves_state_and_recovers(monkeypatch assert unavailable.value.detail == "MCP server configuration could not be refreshed" assert manager.registry["catalog-server"].name == "initial" assert (await manager.catalog.resolve("catalog-server")).name == "updated" + + +@pytest.mark.asyncio +@pytest.mark.parametrize("groups", [[], ["catalog-group"]]) +async def test_access_group_resolution_uses_the_operation_catalog(monkeypatch, groups): + from litellm.proxy._experimental.mcp_server import mcp_server_manager + from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import MCPRequestHandler + + row = _catalog_row().model_copy(update={"mcp_access_groups": groups}) + _catalog_database(monkeypatch, AsyncMock(return_value=[row])) + manager = MCPServerManager() + monkeypatch.setattr(mcp_server_manager, "global_mcp_server_manager", manager) + stale = AsyncMock(return_value=set() if groups else {row.server_id}) + monkeypatch.setattr(MCPRequestHandler, "_get_db_server_ids_for_access_groups", stale) + async with manager.catalog.operation(): + allowed = await MCPRequestHandler._get_mcp_servers_from_access_groups(["catalog-group"]) + assert set(allowed) == ({row.server_id} if groups else set()) + stale.assert_not_awaited()