Skip to content

Commit f979394

Browse files
committed
Allow MCP servers list to partially initialize
1 parent 10ea9ba commit f979394

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

aider/coders/base_coder.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,7 +1626,10 @@ async def _execute_all_tool_calls():
16261626
return tool_responses
16271627

16281628
def initialize_mcp_tools(self):
1629-
"""Initialize tools from all configured MCP servers."""
1629+
"""
1630+
Initialize tools from all configured MCP servers. MCP Servers that fail to be
1631+
initialized will not be available to the Coder instance.
1632+
"""
16301633
tools = []
16311634

16321635
async def get_server_tools(server):
@@ -1636,13 +1639,18 @@ async def get_server_tools(server):
16361639
session=session, format="openai"
16371640
)
16381641
return (server.name, server_tools)
1642+
except Exception:
1643+
self.io.tool_warning(
1644+
f"Error initializing MCP server {server.name}. A message has been logged."
1645+
)
1646+
return None
16391647
finally:
16401648
await server.disconnect()
16411649

16421650
async def get_all_server_tools():
16431651
tasks = [get_server_tools(server) for server in self.mcp_servers]
16441652
results = await asyncio.gather(*tasks)
1645-
return results
1653+
return [result for result in results if result is not None]
16461654

16471655
if self.mcp_servers:
16481656
tools = asyncio.run(get_all_server_tools())

tests/basic/test_coder.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import tempfile
33
import unittest
44
from pathlib import Path
5-
from unittest.mock import MagicMock, patch
5+
from unittest.mock import AsyncMock, MagicMock, patch
66

77
import git
88

@@ -575,6 +575,7 @@ def test_new_file_edit_one_commit(self):
575575
fname = Path("file.txt")
576576

577577
io = InputOutput(yes=True)
578+
io.tool_warning = MagicMock()
578579
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)])
579580

580581
self.assertTrue(fname.exists())
@@ -1351,6 +1352,60 @@ def test_mcp_server_connection(self, mock_mcp_client):
13511352
self.assertEqual(tool_responses[0]["tool_call_id"], "test_id")
13521353
self.assertEqual(tool_responses[0]["content"], "Tool execution result")
13531354

1355+
@patch("aider.coders.base_coder.experimental_mcp_client")
1356+
def test_coder_creation_with_failed_mcp_server(self, mock_mcp_client):
1357+
"""Test that a coder can still be created even if an MCP server fails to initialize."""
1358+
with GitTemporaryDirectory():
1359+
io = InputOutput(yes=True)
1360+
io.tool_warning = MagicMock()
1361+
1362+
# Create mock MCP servers - one working, one failing
1363+
working_server = AsyncMock()
1364+
working_server.name = "working_server"
1365+
working_server.connect = AsyncMock()
1366+
working_server.disconnect = AsyncMock()
1367+
1368+
failing_server = AsyncMock()
1369+
failing_server.name = "failing_server"
1370+
failing_server.connect = AsyncMock()
1371+
failing_server.disconnect = AsyncMock()
1372+
1373+
# Mock load_mcp_tools to succeed for working_server and fail for failing_server
1374+
async def mock_load_mcp_tools(session, format):
1375+
if session == await working_server.connect():
1376+
return [{"function": {"name": "working_tool"}}]
1377+
else:
1378+
raise Exception("Failed to load tools")
1379+
1380+
mock_mcp_client.load_mcp_tools = AsyncMock(side_effect=mock_load_mcp_tools)
1381+
1382+
# Create coder with both servers
1383+
coder = Coder.create(
1384+
self.GPT35,
1385+
"diff",
1386+
io=io,
1387+
mcp_servers=[working_server, failing_server],
1388+
verbose=True,
1389+
)
1390+
1391+
# Verify that coder was created successfully
1392+
self.assertIsInstance(coder, Coder)
1393+
1394+
# Verify that only the working server's tools were added
1395+
self.assertIsNotNone(coder.mcp_tools)
1396+
self.assertEqual(len(coder.mcp_tools), 1)
1397+
self.assertEqual(coder.mcp_tools[0][0], "working_server")
1398+
1399+
# Verify that the tool list contains only working tools
1400+
tool_list = coder.get_tool_list()
1401+
self.assertEqual(len(tool_list), 1)
1402+
self.assertEqual(tool_list[0]["function"]["name"], "working_tool")
1403+
1404+
# Verify that the warning was logged for the failing server
1405+
io.tool_warning.assert_called_with(
1406+
"Error initializing MCP server failing_server. A message has been logged."
1407+
)
1408+
13541409
@patch("aider.coders.base_coder.experimental_mcp_client")
13551410
def test_initialize_mcp_tools(self, mock_mcp_client):
13561411
"""Test that the coder initializes MCP tools correctly."""

0 commit comments

Comments
 (0)