Skip to content

Commit 4e54d0d

Browse files
authored
Merge pull request #518 from szmania/cli-27-local-mcp-tools-remain-on-mode-switch
fix: Local MCP tools remain available after mode switch from agent to non-agent
2 parents c2459fb + cfae0eb commit 4e54d0d

3 files changed

Lines changed: 88 additions & 14 deletions

File tree

cecli/coders/base_coder.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ async def create(
278278
main_model = models.Model(models.DEFAULT_MODEL_NAME, io=io)
279279

280280
if edit_format == "code":
281-
edit_format = None
282-
if edit_format is None:
281+
edit_format = main_model.edit_format
282+
elif edit_format is None:
283283
if from_coder:
284284
edit_format = from_coder.edit_format
285285
else:
@@ -332,12 +332,14 @@ async def create(
332332

333333
if res is not None:
334334
if from_coder:
335-
if from_coder.mcp_manager:
336-
res.mcp_manager = from_coder.mcp_manager
337-
338-
# Transfer TUI app weak reference
339-
res.tui = from_coder.tui
340-
res.context_management_enabled = from_coder.context_management_enabled
335+
if res.mcp_manager:
336+
# When switching to a non-agent coder, disconnect the "Local" MCP server
337+
# (which provides agent-only tools like tool calling and file editing)
338+
# so it's not available in non-agent modes.
339+
if not isinstance(res, coders.AgentCoder):
340+
local_server = res.mcp_manager.get_server("Local")
341+
if local_server and local_server.is_connected:
342+
await res.mcp_manager.disconnect_server("Local")
341343

342344
await res.initialize_mcp_tools()
343345

cecli/main.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,17 +1239,16 @@ def get_io(pretty):
12391239
kwargs["num_cache_warming_pings"] = 0
12401240
kwargs["args"] = coder.args
12411241

1242-
if kwargs["edit_format"] != AgentCoder.edit_format and (
1243-
coder := kwargs.get("from_coder")
1244-
):
1245-
if coder.mcp_manager.get_server("Local"):
1246-
await coder.mcp_manager.disconnect_server("Local")
1247-
12481242
for tag in [MessageTag.SYSTEM, MessageTag.EXAMPLES, MessageTag.STATIC]:
12491243
ConversationService.get_manager(coder).clear_tag(tag)
12501244

1245+
old_coder = coder
12511246
coder = await Coder.create(**kwargs)
12521247

1248+
if isinstance(old_coder, AgentCoder) and not isinstance(coder, AgentCoder):
1249+
if coder.mcp_manager and coder.mcp_manager.get_server("Local"):
1250+
await coder.mcp_manager.disconnect_server("Local")
1251+
12531252
if switch.kwargs.get("show_announcements") is False:
12541253
coder.suppress_announcements_for_next_prompt = True
12551254

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import asyncio
2+
import unittest
3+
from unittest.mock import MagicMock, patch
4+
5+
from cecli.coders import Coder
6+
7+
8+
class TestCoderSwitching(unittest.TestCase):
9+
@patch("cecli.coders.agent_coder.ToolRegistry")
10+
def test_switch_from_agent_to_non_agent(self, mock_tool_registry):
11+
async def run_test():
12+
# Mock dependencies
13+
io = MagicMock()
14+
args = MagicMock()
15+
args.agent_config = "{}"
16+
args.verbose = False
17+
args.tui = False
18+
args.show_thinking = True
19+
args.auto_save = False
20+
args.file_diffs = True
21+
args.max_reflections = 3
22+
main_model = MagicMock()
23+
main_model.edit_format = "code"
24+
main_model.agent_model = None
25+
main_model.weak_model = None
26+
main_model.editor_model = None
27+
main_model.get_repo_map_tokens.return_value = 1024
28+
main_model.info = {}
29+
main_model.name = "test-model"
30+
main_model.reasoning_tag = "think"
31+
main_model.get_active_model.return_value = main_model
32+
33+
mock_tool_registry.get_registered_tools.return_value = ["edittext"]
34+
mock_tool_registry.get_tool.return_value = MagicMock()
35+
mock_tool_registry.build_registry.return_value = None
36+
37+
# 1. Start with an AgentCoder
38+
agent_coder = await Coder.create(
39+
main_model=main_model,
40+
edit_format="agent",
41+
io=io,
42+
args=args,
43+
)
44+
from cecli.coders import AgentCoder
45+
46+
self.assertIsInstance(agent_coder, AgentCoder)
47+
self.assertTrue(agent_coder.mcp_manager.get_server("Local").is_connected)
48+
49+
# 2. Switch to a non-agent coder
50+
code_coder = await Coder.create(
51+
from_coder=agent_coder,
52+
edit_format="code",
53+
)
54+
self.assertNotIsInstance(code_coder, AgentCoder)
55+
56+
# 3. Check that "Local" server is disconnected
57+
self.assertFalse(code_coder.mcp_manager.get_server("Local").is_connected)
58+
59+
# 4. Switch back to agent coder
60+
new_agent_coder = await Coder.create(
61+
from_coder=code_coder,
62+
edit_format="agent",
63+
)
64+
self.assertIsInstance(new_agent_coder, AgentCoder)
65+
66+
# 5. Check that "Local" server is re-connected
67+
self.assertTrue(new_agent_coder.mcp_manager.get_server("Local").is_connected)
68+
69+
asyncio.run(run_test())
70+
71+
72+
if __name__ == "__main__":
73+
unittest.main()

0 commit comments

Comments
 (0)