From dfc5563641f0ef1a03a9ac97598ea20ded85b873 Mon Sep 17 00:00:00 2001 From: Cameron Aragon <69489633+camaragon@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:30:37 +0000 Subject: [PATCH] fix(acp): include MCP toolsets in ACP sessions --- acp_adapter/server.py | 12 +++++++++--- acp_adapter/session.py | 29 ++++++++++++++++++++++++++++- tests/acp/test_server.py | 8 +++++++- tests/acp/test_session.py | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/acp_adapter/server.py b/acp_adapter/server.py index d73c71157..612748d56 100644 --- a/acp_adapter/server.py +++ b/acp_adapter/server.py @@ -60,7 +60,7 @@ from acp_adapter.events import ( make_tool_progress_cb, ) from acp_adapter.permissions import make_approval_callback -from acp_adapter.session import SessionManager, SessionState +from acp_adapter.session import SessionManager, SessionState, _expand_acp_enabled_toolsets logger = logging.getLogger(__name__) @@ -287,7 +287,11 @@ class HermesACPAgent(acp.Agent): try: from model_tools import get_tool_definitions - enabled_toolsets = getattr(state.agent, "enabled_toolsets", None) or ["hermes-acp"] + enabled_toolsets = _expand_acp_enabled_toolsets( + getattr(state.agent, "enabled_toolsets", None) or ["hermes-acp"], + mcp_server_names=[server.name for server in mcp_servers], + ) + state.agent.enabled_toolsets = enabled_toolsets disabled_toolsets = getattr(state.agent, "disabled_toolsets", None) state.agent.tools = get_tool_definitions( enabled_toolsets=enabled_toolsets, @@ -754,7 +758,9 @@ class HermesACPAgent(acp.Agent): def _cmd_tools(self, args: str, state: SessionState) -> str: try: from model_tools import get_tool_definitions - toolsets = getattr(state.agent, "enabled_toolsets", None) or ["hermes-acp"] + toolsets = _expand_acp_enabled_toolsets( + getattr(state.agent, "enabled_toolsets", None) or ["hermes-acp"] + ) tools = get_tool_definitions(enabled_toolsets=toolsets, quiet_mode=True) if not tools: return "No tools available." diff --git a/acp_adapter/session.py b/acp_adapter/session.py index 3f5f78f9a..724573002 100644 --- a/acp_adapter/session.py +++ b/acp_adapter/session.py @@ -106,6 +106,24 @@ def _register_task_cwd(task_id: str, cwd: str) -> None: logger.debug("Failed to register ACP task cwd override", exc_info=True) +def _expand_acp_enabled_toolsets( + toolsets: List[str] | None = None, + mcp_server_names: List[str] | None = None, +) -> List[str]: + """Return ACP toolsets plus explicit MCP server toolsets for this session.""" + expanded: List[str] = [] + for name in list(toolsets or ["hermes-acp"]): + if name and name not in expanded: + expanded.append(name) + + for server_name in list(mcp_server_names or []): + toolset_name = f"mcp-{server_name}" + if server_name and toolset_name not in expanded: + expanded.append(toolset_name) + + return expanded + + def _clear_task_cwd(task_id: str) -> None: """Remove task-specific cwd overrides for an ACP session.""" if not task_id: @@ -537,9 +555,18 @@ class SessionManager: elif isinstance(model_cfg, str) and model_cfg.strip(): default_model = model_cfg.strip() + configured_mcp_servers = [ + name + for name, cfg in (config.get("mcp_servers") or {}).items() + if not isinstance(cfg, dict) or cfg.get("enabled", True) is not False + ] + kwargs = { "platform": "acp", - "enabled_toolsets": ["hermes-acp"], + "enabled_toolsets": _expand_acp_enabled_toolsets( + ["hermes-acp"], + mcp_server_names=configured_mcp_servers, + ), "quiet_mode": True, "session_id": session_id, "model": model or default_model, diff --git a/tests/acp/test_server.py b/tests/acp/test_server.py index faa4c18a7..d4afed101 100644 --- a/tests/acp/test_server.py +++ b/tests/acp/test_server.py @@ -904,9 +904,15 @@ class TestRegisterSessionMcpServers: ] with patch("tools.mcp_tool.register_mcp_servers", return_value=["mcp_srv_search"]), \ - patch("model_tools.get_tool_definitions", return_value=fake_tools): + patch("model_tools.get_tool_definitions", return_value=fake_tools) as mock_defs: await agent._register_session_mcp_servers(state, [server]) + mock_defs.assert_called_once_with( + enabled_toolsets=["hermes-acp", "mcp-srv"], + disabled_toolsets=None, + quiet_mode=True, + ) + assert state.agent.enabled_toolsets == ["hermes-acp", "mcp-srv"] assert state.agent.tools == fake_tools assert state.agent.valid_tool_names == {"mcp_srv_search", "terminal"} # _invalidate_system_prompt should have been called diff --git a/tests/acp/test_session.py b/tests/acp/test_session.py index 50d04b1a9..c86819f6d 100644 --- a/tests/acp/test_session.py +++ b/tests/acp/test_session.py @@ -138,6 +138,43 @@ class TestListAndCleanup: class TestPersistence: """Verify that sessions are persisted to SessionDB and can be restored.""" + def test_create_session_includes_registered_mcp_toolsets(self, tmp_path, monkeypatch): + captured = {} + + def fake_resolve_runtime_provider(requested=None, **kwargs): + return { + "provider": "openrouter", + "api_mode": "chat_completions", + "base_url": "https://openrouter.example/v1", + "api_key": "***", + "command": None, + "args": [], + } + + def fake_agent(**kwargs): + captured.update(kwargs) + return SimpleNamespace(model=kwargs.get("model"), enabled_toolsets=kwargs.get("enabled_toolsets")) + + monkeypatch.setattr("hermes_cli.config.load_config", lambda: { + "model": {"provider": "openrouter", "default": "test-model"}, + "mcp_servers": { + "olympus": {"command": "python", "enabled": True}, + "exa": {"url": "https://exa.ai/mcp"}, + "disabled": {"command": "python", "enabled": False}, + }, + }) + monkeypatch.setattr( + "hermes_cli.runtime_provider.resolve_runtime_provider", + fake_resolve_runtime_provider, + ) + db = SessionDB(tmp_path / "state.db") + + with patch("run_agent.AIAgent", side_effect=fake_agent): + manager = SessionManager(db=db) + manager.create_session(cwd="/work") + + assert captured["enabled_toolsets"] == ["hermes-acp", "mcp-olympus", "mcp-exa"] + def test_create_session_writes_to_db(self, manager): state = manager.create_session(cwd="/project") db = manager._get_db()