From c9adbaff5efdf836505d67068fdab4c88828ad80 Mon Sep 17 00:00:00 2001 From: HexLab98 Date: Sun, 5 Jul 2026 18:36:43 +0700 Subject: [PATCH] test(mcp): cover probe capability + config gating for prompts/resources Assert the "Test server" probe skips prompts/list when tools.prompts is false, skips both families when the server advertises neither capability (the Unreal MCP server case), probes both when advertised and enabled, and falls back to the legacy always-try behaviour when no capability info was captured. --- tests/hermes_cli/test_mcp_config.py | 93 +++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/hermes_cli/test_mcp_config.py b/tests/hermes_cli/test_mcp_config.py index 3986fe71200..1f9c2a95203 100644 --- a/tests/hermes_cli/test_mcp_config.py +++ b/tests/hermes_cli/test_mcp_config.py @@ -576,6 +576,99 @@ class TestProbeEnvResolution: assert seen["config"]["headers"]["Authorization"] == "Bearer jwt-token-xyz" +class TestProbeCapabilityGating: + """The ``details`` probe must not fire prompts/list or resources/list at + servers that either disabled them in config or never advertised them. + + Regression for the Unreal MCP server case: it answers + ``Call to unknown method "prompts/list"``, so an unconditional probe logged + a hard error and ``tools.prompts: false`` (the documented workaround) had no + effect because the probe never consulted config or capabilities. + """ + + class _FakeTool: + name = "do_thing" + description = "a tool" + + class _Caps: + def __init__(self, prompts=None, resources=None): + self.prompts = prompts + self.resources = resources + + class _InitResult: + def __init__(self, caps): + self.capabilities = caps + + def _make_server(self, called, caps): + outer = self + + class _Result(list): + @property + def prompts(self): + return self + + @property + def resources(self): + return self + + class _Session: + async def list_prompts(self_inner): + called.append("prompts") + return _Result() + + async def list_resources(self_inner): + called.append("resources") + return _Result() + + class _FakeServer: + _tools = [outer._FakeTool()] + session = _Session() + initialize_result = outer._InitResult(caps) + + async def shutdown(self_inner): + return None + + return _FakeServer() + + def _run_probe(self, monkeypatch, config, caps): + import hermes_cli.mcp_config as mc + + called: list[str] = [] + + async def _fake_connect(name, cfg): + return self._make_server(called, caps) + + monkeypatch.setattr("tools.mcp_tool._connect_server", _fake_connect) + details: dict = {} + mc._probe_single_server("srv", config, details=details) + return called, details + + def test_config_disables_prompts_probe(self, monkeypatch): + # Server advertises both, but user turned prompts off. + caps = self._Caps(prompts=object(), resources=object()) + called, details = self._run_probe( + monkeypatch, {"url": "http://x/mcp", "tools": {"prompts": False}}, caps + ) + assert "prompts" not in called + assert "resources" in called + + def test_unadvertised_capability_not_probed(self, monkeypatch): + # Unreal case: no prompts capability advertised → never call it. + caps = self._Caps(prompts=None, resources=None) + called, _ = self._run_probe(monkeypatch, {"url": "http://x/mcp"}, caps) + assert called == [] + + def test_advertised_and_enabled_is_probed(self, monkeypatch): + caps = self._Caps(prompts=object(), resources=object()) + called, details = self._run_probe(monkeypatch, {"url": "http://x/mcp"}, caps) + assert set(called) == {"prompts", "resources"} + + def test_missing_capability_info_falls_back_to_probe(self, monkeypatch): + # No initialize_result captured → preserve legacy always-try behaviour. + called, _ = self._run_probe(monkeypatch, {"url": "http://x/mcp"}, None) + assert set(called) == {"prompts", "resources"} + + class TestStripBearerPrefix: """Pasted tokens that already include ``Bearer `` would otherwise produce ``Bearer Bearer `` once the header template adds its own prefix."""