diff --git a/hermes_cli/mcp_catalog.py b/hermes_cli/mcp_catalog.py index 6566fa8276..b9355277db 100644 --- a/hermes_cli/mcp_catalog.py +++ b/hermes_cli/mcp_catalog.py @@ -561,7 +561,9 @@ def _apply_tool_selection( return if not probed: - _write_tools_filter(name, "include", None) + # Keep a prior explicit selection: "no tools today" must not widen ``include: []`` to + # "all tools" the next time the server does advertise some. + _write_tools_filter(name, "include", prior_selection) _say(" Server reported no tools.", Colors.YELLOW) return diff --git a/tests/hermes_cli/test_mcp_catalog.py b/tests/hermes_cli/test_mcp_catalog.py index 3367974e19..12f7886cbf 100644 --- a/tests/hermes_cli/test_mcp_catalog.py +++ b/tests/hermes_cli/test_mcp_catalog.py @@ -71,7 +71,7 @@ def _write_manifest(catalog_dir: Path, name: str, body: dict) -> Path: entry_dir = catalog_dir / name entry_dir.mkdir(exist_ok=True) path = entry_dir / "manifest.yaml" - with open(path, "w") as f: + with open(path, "w", encoding="utf-8") as f: yaml.safe_dump(body, f) return path @@ -421,6 +421,24 @@ class TestInstall: assert server["tools"]["include"] == ["tool_a"] assert "exclude" not in server["tools"] + def test_empty_discovery_reinstall_keeps_explicit_empty_include(self, catalog_dir, monkeypatch): + """A probe that succeeds with zero tools must not widen a deliberate ``include: []`` + to "all tools" (#12865): the block-all choice survives until the user changes it.""" + import hermes_cli.mcp_catalog as mc + from hermes_cli.config import load_config, save_config + + monkeypatch.setattr(mc, "_probe_tools", lambda name: []) + _write_manifest(catalog_dir, "demo", _basic_manifest()) + cfg = load_config() + cfg.setdefault("mcp_servers", {})["demo"] = { + "command": "npx", "args": ["-y", "demo-mcp"], "enabled": True, "tools": {"include": []}, + } + save_config(cfg) + + mc.install_entry(_entry("demo"), enable=True) + + assert load_config()["mcp_servers"]["demo"]["tools"]["include"] == [] + def test_probe_fail_reinstall_preserves_manual_exclude(self, catalog_dir): """A failed probe during reinstall keeps a hand-written tools.exclude on a manifest with no tool defaults, instead of @@ -515,7 +533,7 @@ class TestInstall: # load_config resolves it; config.yaml itself stays secret-free. from hermes_cli.config import get_config_path - raw = get_config_path().read_text() + raw = get_config_path().read_text(encoding="utf-8") assert "${MCP_DEMO_API_KEY}" in raw assert "secret-val" not in raw