mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
* fix(mcp): resolve call_tool by registry without requiring tool map
Multi-worker reloads put MCP servers in the registry from the DB but do
not re-run tools/list on every process. Gating call_tool on
tool_name_to_mcp_server_name_mapping made cold workers 500 with Tool not
found after another worker had already listed the tool. Treat a registry
match on server id/name/alias as enough; upstream rejects unknown tools
* test(e2e): poll MCP register, tools/list, and tools/call across multi-worker lag
Stage multi-worker gateways only load MCP servers and tool maps on the
process that handled the request. Poll until the server is listed, the
tool appears on tools/list, and tools/call is not a cold-worker 500 so
key-access and Datadog MCP e2e stop racing the LB
* Revert "fix(mcp): resolve call_tool by registry without requiring tool map"
This reverts commit 8b56e51e39.
* test(e2e): tighten MCP multi-worker lag classifier
Only retry tools/call on gateway shapes Tool <name> not found and
server_not_found, not any 500 that mentions tool/server not found, so
upstream failures are not retried until the poll deadline
* test(e2e): drop unit file for MCP lag classifier
The live await_call_tool polls already cover multi-worker lag; a separate
string-match unit module is not worth keeping
54 lines
2.1 KiB
Python
54 lines
2.1 KiB
Python
"""Live e2e: MCP tool selection via access group at key creation.
|
|
|
|
An admin registers the Datadog remote MCP server tagged with a server-side
|
|
access group (`mcp_access_groups`). A key minted with that access group
|
|
(`object_permission.mcp_access_groups`) sees the server's tools; a key minted
|
|
with a different group does not. This exercises access-group-scoped tool
|
|
selection, the enterprise MCP surface where keys are granted tool access groups
|
|
rather than explicit server ids.
|
|
|
|
A tools/list that leaks the server across the access-group boundary fails hard.
|
|
Requires DD_API_KEY + DD_APP_KEY (the suite's real MCP upstream).
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from datadog_mcp import SEARCH_LOGS_TOOL, register_datadog_mcp
|
|
from e2e_config import unique_marker
|
|
from e2e_http import unwrap
|
|
from lifecycle import ResourceManager
|
|
from mcp_client import McpClient
|
|
|
|
pytestmark = pytest.mark.e2e
|
|
|
|
|
|
class TestMcpAccessGroupToolSelection:
|
|
@pytest.mark.covers("mcp.list_tools.api_key.access_group_scoped")
|
|
def test_access_group_scopes_tool_selection(
|
|
self, client: McpClient, resources: ResourceManager
|
|
) -> None:
|
|
group = f"e2e-mcp-grp-{unique_marker()}"
|
|
server_id = register_datadog_mcp(client, resources, mcp_access_groups=[group])
|
|
client.await_registered(server_id)
|
|
|
|
granted = client.generate_key(
|
|
user_id=f"e2e-mcp-ag-granted-{unique_marker()}",
|
|
mcp_servers=None,
|
|
mcp_access_groups=[group],
|
|
)
|
|
resources.defer(lambda: client.proxy.delete_key(granted))
|
|
|
|
other = client.generate_key(
|
|
user_id=f"e2e-mcp-ag-other-{unique_marker()}",
|
|
mcp_servers=None,
|
|
mcp_access_groups=[f"e2e-mcp-grp-absent-{unique_marker()}"],
|
|
)
|
|
resources.defer(lambda: client.proxy.delete_key(other))
|
|
|
|
_ = client.await_tool(granted, server_id, SEARCH_LOGS_TOOL)
|
|
|
|
other_tools = unwrap(client.list_tools(other)).tool_names_for_server(server_id)
|
|
assert other_tools == frozenset(), (
|
|
f"key with a different access group saw the server's tools; access-group tool "
|
|
f"selection leaked across the boundary: {other_tools}"
|
|
)
|