diff --git a/tests/hermes_cli/test_mcp_startup.py b/tests/hermes_cli/test_mcp_startup.py index 45d80bfdd6..76bd1661e3 100644 --- a/tests/hermes_cli/test_mcp_startup.py +++ b/tests/hermes_cli/test_mcp_startup.py @@ -240,8 +240,14 @@ def test_discover_mcp_tools_spawns_only_allowed_servers(monkeypatch): } seen: dict[str, dict] = {} + sdk_probes = {"n": 0} + + def _fake_ensure_sdk(): + sdk_probes["n"] += 1 + return True + monkeypatch.setattr(mcp_tool, "_load_mcp_config", lambda: dict(servers)) - monkeypatch.setattr(mcp_tool, "_ensure_mcp_sdk", lambda: True) + monkeypatch.setattr(mcp_tool, "_ensure_mcp_sdk", _fake_ensure_sdk) monkeypatch.setattr(mcp_tool, "_try_acquire_mcp_discovery_lock", lambda: mcp_tool._LOCK_UNAVAILABLE) monkeypatch.setattr(mcp_tool, "_release_mcp_discovery_lock", lambda *_a, **_k: None, raising=False) @@ -262,10 +268,13 @@ def test_discover_mcp_tools_spawns_only_allowed_servers(monkeypatch): mcp_tool.discover_mcp_tools(allowed_mcp_names=["terminal", "code-mcp"]) assert set(seen) == {"code-mcp"} - # `-t terminal` — no MCP server in the filter: skip the whole MCP load. + # `-t terminal` — no MCP server in the filter: skip the whole MCP load, + # including the ~260ms `mcp` SDK import. seen.clear() + sdk_probes["n"] = 0 assert mcp_tool.discover_mcp_tools(allowed_mcp_names=["terminal"]) == [] assert seen == {} + assert sdk_probes["n"] == 0 def test_background_discovery_honors_server_filter(monkeypatch, _reset_mcp_server_filter): diff --git a/tools/mcp_tool.py b/tools/mcp_tool.py index 52e7926d2d..fa91f4c09f 100644 --- a/tools/mcp_tool.py +++ b/tools/mcp_tool.py @@ -8231,12 +8231,6 @@ def discover_mcp_tools(allowed_mcp_names: Optional[List[str]] = None) -> List[st logger.debug("No MCP servers configured") return [] - # SDK import is deferred to HERE so a config with zero MCP servers (the - # default) never pays the ~260ms `mcp` import on CLI startup. - if not _ensure_mcp_sdk(): - logger.debug("MCP SDK not available -- skipping MCP tool discovery") - return [] - if allowed_mcp_names is not None: # Filter by MCP-server-name match. Built-in toolset names that aren't # MCP servers will simply not match — that's fine; they don't need @@ -8256,6 +8250,13 @@ def discover_mcp_tools(allowed_mcp_names: Optional[List[str]] = None) -> List[st logger.debug("No MCP servers in --toolsets filter; skipping MCP load entirely") return [] + # SDK import is deferred to HERE so a config with zero MCP servers (the + # default) — or a -t/--toolsets filter that keeps none — never pays the + # ~260ms `mcp` import on CLI startup. + if not _ensure_mcp_sdk(): + logger.debug("MCP SDK not available -- skipping MCP tool discovery") + return [] + # Cross-process discovery guard (#62771). A lock loser waits for # the holder, then performs its own process-local discovery. If locking is # unavailable or the bounded wait expires, preserve the previous