@@ -2584,8 +2584,78 @@ async def test_call_mcp_tool_uses_manager_permission_lookup():
25842584
25852585 assert result == expected_response
25862586 mock_get_allowed .assert_awaited_once ()
2587- assert mock_get_server .call_count == 2
2587+ # We call `_get_mcp_server_from_tool_name` multiple times:
2588+ # - for logging/metadata
2589+ # - for resolving the server during dispatch
2590+ # - and inside `call_tool` for guardrails/hooks
2591+ # The exact count isn't important, only that it is used.
2592+ assert mock_get_server .call_count >= 2
2593+ # First call should use the prefixed tool name
25882594 assert (
25892595 mock_get_server .call_args_list [0 ][0 ][0 ]
25902596 == f"{ mock_server .name } /gmail_send_email"
25912597 )
2598+
2599+
2600+ @pytest .mark .asyncio
2601+ async def test_call_mcp_tool_resolves_unprefixed_tool_name_and_checks_permissions ():
2602+ """
2603+ Ensure `call_mcp_tool` correctly resolves the MCP server for an unprefixed tool
2604+ name and enforces server-level permissions using that resolved server.
2605+ """
2606+ from litellm .proxy ._experimental .mcp_server .server import (
2607+ call_mcp_tool ,
2608+ global_mcp_server_manager ,
2609+ )
2610+
2611+ mock_server = MCPServer (
2612+ server_id = "server-123" ,
2613+ name = "test_server" ,
2614+ alias = "test_server" ,
2615+ server_name = "test_server" ,
2616+ url = "https://test-server.com/mcp" ,
2617+ transport = MCPTransport .http ,
2618+ mcp_info = {"server_name" : "test_server" },
2619+ )
2620+
2621+ expected_response = [TextContent (type = "text" , text = "ok" )]
2622+
2623+ with patch .object (
2624+ global_mcp_server_manager ,
2625+ "get_allowed_mcp_servers" ,
2626+ new_callable = AsyncMock ,
2627+ ) as mock_get_allowed , patch .object (
2628+ global_mcp_server_manager ,
2629+ "get_mcp_servers_from_ids" ,
2630+ return_value = [mock_server ],
2631+ ), patch .object (
2632+ global_mcp_server_manager ,
2633+ "_get_mcp_server_from_tool_name" ,
2634+ return_value = mock_server ,
2635+ ) as mock_get_server , patch (
2636+ "litellm.proxy._experimental.mcp_server.server.global_mcp_tool_registry"
2637+ ) as mock_tool_registry , patch (
2638+ "litellm.proxy._experimental.mcp_server.server._handle_managed_mcp_tool" ,
2639+ new_callable = AsyncMock ,
2640+ ) as mock_handle_managed , patch (
2641+ "litellm.proxy._experimental.mcp_server.server.MCPRequestHandler.is_tool_allowed" ,
2642+ return_value = True ,
2643+ ) as mock_is_allowed :
2644+ mock_get_allowed .return_value = [mock_server .server_id ]
2645+ mock_tool_registry .get_tool .return_value = None
2646+ mock_handle_managed .return_value = expected_response
2647+
2648+ # Call with UNPREFIXED tool name; server should be resolved via mapping
2649+ result = await call_mcp_tool (
2650+ name = "gmail_send_email" ,
2651+ arguments = {"body" : "hello" },
2652+ mcp_servers = ["test_server" ],
2653+ )
2654+
2655+ assert result == expected_response
2656+ mock_get_allowed .assert_awaited_once ()
2657+ # We should resolve the server at least once using the unprefixed name
2658+ assert mock_get_server .call_count >= 1
2659+ assert mock_get_server .call_args_list [0 ][0 ][0 ] == "gmail_send_email"
2660+ # Permissions check should be invoked with the resolved server name
2661+ mock_is_allowed .assert_called_once ()
0 commit comments