From 087aa74e6ec3a5c15b3382fada4592b4df42969c Mon Sep 17 00:00:00 2001 From: Stephen Schoettler Date: Tue, 26 May 2026 22:18:59 -0700 Subject: [PATCH] fix(cli): honor MCP probe connect timeout (cherry picked from commit b106dbe1c6a892789dcc4a0fdd460e1d100b8a66) (cherry picked from commit 2142c95ccfd9fc882f4252be561c844752c76a37) --- hermes_cli/mcp_config.py | 9 +++++-- tests/hermes_cli/test_mcp_config.py | 38 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/hermes_cli/mcp_config.py b/hermes_cli/mcp_config.py index 657d315c13c..7c3052b18b5 100644 --- a/hermes_cli/mcp_config.py +++ b/hermes_cli/mcp_config.py @@ -247,7 +247,7 @@ def _resolve_mcp_server_config(config: dict) -> dict: def _probe_single_server( - name: str, config: dict, connect_timeout: float = 30, *, details: Optional[dict] = None + name: str, config: dict, connect_timeout: Optional[float] = None, *, details: Optional[dict] = None ) -> List[Tuple[str, str]]: """Temporarily connect to one MCP server, list its tools, disconnect. @@ -271,9 +271,14 @@ def _probe_single_server( ) config = _resolve_mcp_server_config(config) + if connect_timeout is None: + raw_timeout = config.get("connect_timeout", 30) + try: + connect_timeout = max(1.0, float(raw_timeout)) + except (TypeError, ValueError): + connect_timeout = 30.0 _ensure_mcp_loop() - tools_found: List[Tuple[str, str]] = [] async def _probe(): diff --git a/tests/hermes_cli/test_mcp_config.py b/tests/hermes_cli/test_mcp_config.py index 1f9c2a95203..bc9f1cbfc3b 100644 --- a/tests/hermes_cli/test_mcp_config.py +++ b/tests/hermes_cli/test_mcp_config.py @@ -445,6 +445,44 @@ class TestMcpTest: assert "Connected" in out assert "Tools discovered: 2" in out + def test_probe_uses_configured_connect_timeout(self, monkeypatch): + """OAuth-capable probes must not hard-code a short 30s timeout.""" + import asyncio + from hermes_cli import mcp_config + import tools.mcp_tool as mcp_tool + + captured = {} + + class FakeServer: + _tools = [] + + async def shutdown(self): + captured["shutdown"] = True + + async def fake_connect(name, config): + return FakeServer() + + def fake_run_on_mcp_loop(coro, timeout): + captured["outer_timeout"] = timeout + return asyncio.run(coro) + + async def fake_wait_for(awaitable, timeout): + captured["inner_timeout"] = timeout + return await awaitable + + monkeypatch.setattr(mcp_tool, "_ensure_mcp_loop", lambda: None) + monkeypatch.setattr(mcp_tool, "_stop_mcp_loop_if_idle", lambda: None) + monkeypatch.setattr(mcp_tool, "_connect_server", fake_connect) + monkeypatch.setattr(mcp_tool, "_run_on_mcp_loop", fake_run_on_mcp_loop) + monkeypatch.setattr(mcp_config.asyncio, "wait_for", fake_wait_for) + + assert mcp_config._probe_single_server( + "supabase", {"connect_timeout": 300} + ) == [] + assert captured["inner_timeout"] == 300.0 + assert captured["outer_timeout"] == 310.0 + assert captured["shutdown"] is True + # --------------------------------------------------------------------------- # Tests: env var interpolation