fix(mcp): warn and return empty when native MCP server returns no tools

This commit is contained in:
Greyson LaLonde
2026-04-30 02:41:01 +08:00
committed by GitHub
parent e0b86750c2
commit feb2e715a3
2 changed files with 107 additions and 0 deletions

View File

@@ -374,6 +374,7 @@ class MCPToolResolver:
"MCP connection failed due to event loop cleanup issues. "
"This may be due to authentication errors or server unavailability."
) from e
raise
except asyncio.CancelledError as e:
raise ConnectionError(
"MCP connection was cancelled. This may indicate an authentication "
@@ -401,6 +402,13 @@ class MCPToolResolver:
filtered_tools.append(tool)
tools_list = filtered_tools
if not tools_list:
self._logger.log(
"warning",
f"No tools discovered from MCP server: {server_name}",
)
return cast(list[BaseTool], []), []
def _client_factory() -> MCPClient:
transport, _ = self._create_transport(mcp_config)
return MCPClient(

View File

@@ -0,0 +1,99 @@
"""Tests for MCPToolResolver native (non-AMP) resolution paths."""
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from crewai.agent.core import Agent
from crewai.mcp.config import MCPServerHTTP
from crewai.mcp.tool_resolver import MCPToolResolver
@pytest.fixture
def agent():
return Agent(
role="Test Agent",
goal="Test goal",
backstory="Test backstory",
)
@pytest.fixture
def resolver(agent):
return MCPToolResolver(agent=agent, logger=agent._logger)
@pytest.fixture
def http_config():
return MCPServerHTTP(url="https://mcp.example.com/api")
class TestResolveNativeEmptyTools:
@patch("crewai.mcp.tool_resolver.MCPClient")
def test_logs_warning_and_returns_empty_when_server_has_no_tools(
self, mock_client_class, resolver, http_config
):
mock_client = AsyncMock()
mock_client.list_tools = AsyncMock(return_value=[])
mock_client.connected = False
mock_client.connect = AsyncMock()
mock_client.disconnect = AsyncMock()
mock_client_class.return_value = mock_client
mock_log = MagicMock()
resolver._logger = MagicMock(log=mock_log)
tools, clients = resolver._resolve_native(http_config)
assert tools == []
assert clients == []
warning_calls = [
call for call in mock_log.call_args_list if call.args[0] == "warning"
]
assert any(
"No tools discovered from MCP server" in call.args[1]
for call in warning_calls
)
@patch("crewai.mcp.tool_resolver.MCPClient")
def test_logs_warning_when_tool_filter_removes_all_tools(
self, mock_client_class, resolver
):
mock_client = AsyncMock()
mock_client.list_tools = AsyncMock(
return_value=[{"name": "search", "description": "Search"}]
)
mock_client.connected = False
mock_client.connect = AsyncMock()
mock_client.disconnect = AsyncMock()
mock_client_class.return_value = mock_client
config = MCPServerHTTP(
url="https://mcp.example.com/api",
tool_filter=lambda _tool: False,
)
mock_log = MagicMock()
resolver._logger = MagicMock(log=mock_log)
tools, clients = resolver._resolve_native(config)
assert tools == []
assert clients == []
warning_calls = [
call for call in mock_log.call_args_list if call.args[0] == "warning"
]
assert any(
"No tools discovered from MCP server" in call.args[1]
for call in warning_calls
)
class TestResolveNativeRuntimeError:
@patch("crewai.mcp.tool_resolver.asyncio.run")
def test_unmatched_runtime_error_is_wrapped_not_swallowed(
self, mock_asyncio_run, resolver, http_config
):
mock_asyncio_run.side_effect = RuntimeError("some other failure")
with pytest.raises(RuntimeError, match="Failed to get native MCP tools"):
resolver._resolve_native(http_config)