mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-09 16:08:31 +00:00
fix(mcp): isolate tool discovery failures (#3772)
This commit is contained in:
parent
b0ce4b513c
commit
73874a9b35
3 changed files with 63 additions and 4 deletions
|
|
@ -615,9 +615,20 @@ async def get_mcp_tools() -> list[BaseTool]:
|
|||
tool_name_prefix=True,
|
||||
)
|
||||
|
||||
# Get all tools from all servers (discovers tool definitions via
|
||||
# temporary sessions – the persistent-session wrapping is applied below).
|
||||
tools = await client.get_tools()
|
||||
async def load_server_tools(server_name: str) -> list[BaseTool]:
|
||||
try:
|
||||
return await client.get_tools(server_name=server_name)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Skipping MCP server '{server_name}' after tool discovery failed: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
return []
|
||||
|
||||
# Get tools from each server independently so one broken MCP server does
|
||||
# not prevent healthy servers from contributing their tools.
|
||||
tools_by_server = await asyncio.gather(*(load_server_tools(name) for name in servers_config))
|
||||
tools = [tool for server_tools in tools_by_server for tool in server_tools]
|
||||
logger.info(f"Successfully loaded {len(tools)} tool(s) from MCP servers")
|
||||
|
||||
# Wrap each tool with persistent-session logic.
|
||||
|
|
|
|||
|
|
@ -889,7 +889,15 @@ async def test_http_transport_tools_not_pooled():
|
|||
patch("langchain_mcp_adapters.sessions.create_session", return_value=mock_cm),
|
||||
):
|
||||
mock_client_instance = MockClient.return_value
|
||||
mock_client_instance.get_tools = AsyncMock(return_value=[http_tool, stdio_tool])
|
||||
|
||||
async def get_tools_for_server(*, server_name: str | None = None):
|
||||
if server_name == "myserver":
|
||||
return [http_tool]
|
||||
if server_name == "playwright":
|
||||
return [stdio_tool]
|
||||
raise AssertionError(f"unexpected server_name: {server_name}")
|
||||
|
||||
mock_client_instance.get_tools = AsyncMock(side_effect=get_tools_for_server)
|
||||
|
||||
tools = await get_mcp_tools()
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,46 @@ def test_mcp_tool_sync_wrapper_generation():
|
|||
assert result == "result: 42"
|
||||
|
||||
|
||||
def test_mcp_tool_loading_skips_failed_server():
|
||||
"""A broken MCP server should not drop tools from healthy servers."""
|
||||
|
||||
async def mock_coro(x: int):
|
||||
return f"result: {x}"
|
||||
|
||||
good_tool = StructuredTool(
|
||||
name="good-server_search",
|
||||
description="search from healthy server",
|
||||
args_schema=MockArgs,
|
||||
func=None,
|
||||
coroutine=mock_coro,
|
||||
)
|
||||
|
||||
async def get_tools_for_server(*, server_name: str | None = None):
|
||||
if server_name == "good-server":
|
||||
return [good_tool]
|
||||
if server_name == "bad-server":
|
||||
raise RuntimeError("SSE endpoint returned text/html")
|
||||
raise AssertionError(f"unexpected server_name: {server_name}")
|
||||
|
||||
mock_client_instance = MagicMock()
|
||||
mock_client_instance.get_tools = AsyncMock(side_effect=get_tools_for_server)
|
||||
|
||||
with (
|
||||
patch("langchain_mcp_adapters.client.MultiServerMCPClient", return_value=mock_client_instance),
|
||||
patch("deerflow.config.extensions_config.ExtensionsConfig.from_file", return_value=MagicMock(model_extra={})),
|
||||
patch("deerflow.mcp.tools.build_servers_config", return_value={"good-server": {}, "bad-server": {}}),
|
||||
patch("deerflow.mcp.tools.get_initial_oauth_headers", new_callable=AsyncMock, return_value={}),
|
||||
patch("deerflow.mcp.tools.build_oauth_tool_interceptor", return_value=None),
|
||||
patch("deerflow.mcp.tools.logger.warning") as mock_warning,
|
||||
):
|
||||
tools = asyncio.run(get_mcp_tools())
|
||||
|
||||
assert [tool.name for tool in tools] == ["good-server_search"]
|
||||
assert tools[0].func is not None
|
||||
mock_warning.assert_called_once()
|
||||
assert "bad-server" in mock_warning.call_args[0][0]
|
||||
|
||||
|
||||
def test_mcp_tool_sync_wrapper_in_running_loop():
|
||||
"""Test the shared sync wrapper from production code."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue