From 8b54c29e149cb4c48433d2e23e798c49d9eaefc6 Mon Sep 17 00:00:00 2001 From: brandonriverott Date: Thu, 16 Jul 2026 22:43:52 -0400 Subject: [PATCH] fix: keep one-shot MCP resources alive through tool calls --- hermes_cli/oneshot.py | 13 ++++++- tests/hermes_cli/test_mcp_startup.py | 55 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/hermes_cli/oneshot.py b/hermes_cli/oneshot.py index e7db628a7c1de..2b9430d995a73 100644 --- a/hermes_cli/oneshot.py +++ b/hermes_cli/oneshot.py @@ -390,6 +390,10 @@ def _run_agent( # honour the same merge semantics as interactive CLI and gateway sessions. _fb = get_fallback_chain(cfg) + from hermes_cli.mcp_startup import wait_for_mcp_discovery + + wait_for_mcp_discovery() + agent = AIAgent( api_key=runtime.get("api_key"), base_url=runtime.get("base_url"), @@ -422,8 +426,13 @@ def _run_agent( agent.stream_delta_callback = None agent.tool_gen_callback = None - result = agent.run_conversation(prompt) - return (result.get("final_response") or "", result) + try: + result = agent.run_conversation(prompt) + return (result.get("final_response") or "", result) + finally: + from tools.mcp_tool import shutdown_mcp_servers + + shutdown_mcp_servers() def _oneshot_clarify_callback(question: str, choices=None) -> str: diff --git a/tests/hermes_cli/test_mcp_startup.py b/tests/hermes_cli/test_mcp_startup.py index 2dcc40f712614..e757d00ca91c0 100644 --- a/tests/hermes_cli/test_mcp_startup.py +++ b/tests/hermes_cli/test_mcp_startup.py @@ -14,6 +14,7 @@ import cli as cli_mod from hermes_cli import main as main_mod from hermes_cli import mcp_startup +from hermes_cli import oneshot as oneshot_mod @pytest.fixture(autouse=True) @@ -222,3 +223,57 @@ def _fake_agent(*_a, **_k): monkeypatch.setattr(cli_mod, "AIAgent", _fake_agent) assert cli._init_agent() is True + + +def test_oneshot_keeps_discovered_mcp_resource_open_until_tool_call_finishes(monkeypatch): + events = [] + resource = {"open": False} + + def _wait_for_discovery(timeout=None): + events.append("discovery_complete") + resource["open"] = True + + def _shutdown_mcp_servers(): + events.append("shutdown") + resource["open"] = False + + class FakeAgent: + def __init__(self, **_kwargs): + events.append("agent_built") + assert resource["open"] is True + + def run_conversation(self, prompt): + events.append("tool_call") + assert prompt == "call the MCP tool" + assert resource["open"] is True + return {"final_response": "tool result", "completed": True} + + monkeypatch.setattr(mcp_startup, "wait_for_mcp_discovery", _wait_for_discovery) + monkeypatch.setattr("tools.mcp_tool.shutdown_mcp_servers", _shutdown_mcp_servers) + monkeypatch.setattr("run_agent.AIAgent", FakeAgent) + monkeypatch.setattr( + "hermes_cli.config.load_config", + lambda: {"model": {"default": "test-model"}}, + ) + monkeypatch.setattr( + "hermes_cli.runtime_provider.resolve_runtime_provider", + lambda **_kwargs: { + "api_key": None, + "base_url": None, + "provider": "test", + "api_mode": None, + "credential_pool": None, + }, + ) + monkeypatch.setattr(oneshot_mod, "_create_session_db_for_oneshot", lambda: None) + + response, result = oneshot_mod._run_agent( + "call the MCP tool", + toolsets=["demo"], + use_config_toolsets=False, + ) + + assert response == "tool result" + assert result["completed"] is True + assert events == ["discovery_complete", "agent_built", "tool_call", "shutdown"] + assert resource["open"] is False