From 96e2d74825a82339347ffdb398034a8c93a892b7 Mon Sep 17 00:00:00 2001 From: yoni Date: Tue, 22 Sep 2026 14:08:59 +0000 Subject: [PATCH] fix(mcp): replace terminally dead sessions --- strix/tools/mcp/registry.py | 2 ++ tests/test_mcp_client.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/strix/tools/mcp/registry.py b/strix/tools/mcp/registry.py index 6e035753..65652ab1 100644 --- a/strix/tools/mcp/registry.py +++ b/strix/tools/mcp/registry.py @@ -143,8 +143,10 @@ class McpConnectionEntry: self._status_sink() def _on_dead(self) -> None: + self.session = None self._catalog = None self._catalog_task = None + self._retry_after = time.monotonic() + _RETRY_DELAY_SECONDS self._set_state("unavailable") async def ensure_connected(self) -> SupervisedMcpSession: diff --git a/tests/test_mcp_client.py b/tests/test_mcp_client.py index 2e554ea9..be944d3c 100644 --- a/tests/test_mcp_client.py +++ b/tests/test_mcp_client.py @@ -44,6 +44,7 @@ from strix.tools.mcp import ( search_mcp_tools, ) from strix.tools.mcp import client as mcp_client +from strix.tools.mcp import registry as mcp_registry_mod from strix.tools.mcp import session as mcp_session_mod @@ -578,6 +579,36 @@ async def test_registered_entry_connects_and_lists_once_for_concurrent_catalog_r await registry.close() +@pytest.mark.asyncio +async def test_registered_entry_replaces_a_terminally_dead_session( + monkeypatch: pytest.MonkeyPatch, +) -> None: + config = _config("docs", ["fetch_page"]) + original = SupervisedMcpSession.adopt( + FakeMCPServer("docs", [_mcp_tool("fetch_page")]), + name="docs", + config=config, + ) + replacement_server = FakeMCPServer("docs", [_mcp_tool("fetch_page")]) + monkeypatch.setattr( + mcp_client, + "_build_server", + lambda _config: _built_server(replacement_server), + ) + monkeypatch.setattr(mcp_registry_mod, "_RETRY_DELAY_SECONDS", 0) + registry = McpRegistry() + entry = registry.add(name="docs", session=original, config=config) + + original._mark_dead() + + assert entry.session is None + replacement = await entry.ensure_connected() + assert replacement is not original + assert replacement.server is replacement_server + assert entry.state == "connected" + await registry.close() + + @pytest.mark.asyncio async def test_registry_warmup_bounds_parallel_connections( monkeypatch: pytest.MonkeyPatch,