fix(mcp): reconnect stale server entries in register_mcp_servers (#37768) - #37899
fix(mcp): reconnect stale server entries in register_mcp_servers (#37768)#37899Tranquil-Flow wants to merge 2 commits into
Conversation
…sResearch#37768) When an MCP server entry exists in _servers with session=None (e.g. after a transport disconnect), register_mcp_servers() would skip it as 'already connected', leaving tool handlers that permanently return 'not connected' even though hermes mcp test shows the server is reachable. Now servers with a null session are treated as needing reconnection, matching the same code path as newly-discovered servers. Servers with an active session are still skipped (idempotent). Fixes NousResearch#37768
|
Thanks for the fix — the root cause analysis is correct and the test coverage is good. Two concerns before this is ready to merge: 1. TOCTOU race on The
Mitigation: take a snapshot of the session under 2. Old background task not cancelled When a stale entry is detected, the old Suggested addition before the # Cancel lingering tasks for stale entries
for k in list(_servers.keys()):
srv = _servers[k]
if getattr(srv, "session", None) is None:
old_task = getattr(srv, "_task", None)
if old_task and not old_task.done():
old_task.cancel()
del _servers[k]This cleans up the stale entry before the skip-condition check, so the TOCTOU window is eliminated and resources are released. 3. Minor: test for mixed-state scenario The current tests cover stale-only and healthy-only. A test with both stale and healthy servers in the same config would verify that only the stale one is retried. |
|
Thanks for the thorough review! All three concerns are valid. Addressed as follows: 1. TOCTOU race + 2. Old task not cancelled These share a root cause: a stale The old task could then reconnect independently (setting Fix (in for k in list(_servers.keys()):
srv = _servers[k]
if getattr(srv, "session", None) is None:
old_task = getattr(srv, "_task", None)
if old_task is not None and not old_task.done():
old_task.cancel()
del _servers[k]
3. Mixed-state test Added All 9 tests in |
…er_mcp_servers Cancel lingering MCPServerTask._task for entries with session=None before launching a new _discover_and_register_server. Without this, the old task (still parked in backoff/reconnect_event) races with the new discovery call: both set _servers[name] and the old task is orphaned, leaking an asyncio Task. Also add test_mixed_stale_and_healthy_servers to verify only the stale server is retried when both healthy and stale servers share the same config. Addresses review on NousResearch#37899 (TOCTOU race + orphaned task + missing mixed-state test).
register_mcp_servers now nudges cached entries whose session is None via _signal_reconnect, so a new agent session recovers a parked server immediately instead of waiting up to _PARKED_RETRY_INTERVAL for the next self-probe (#50170). Gate-check idea credit: @izumi0uu (#50184), @LeonSGP43 (#37772), @Tranquil-Flow (#37899).
register_mcp_servers now nudges cached entries whose session is None via _signal_reconnect, so a new agent session recovers a parked server immediately instead of waiting up to _PARKED_RETRY_INTERVAL for the next self-probe (#50170). Gate-check idea credit: @izumi0uu (#50184), @LeonSGP43 (#37772), @Tranquil-Flow (#37899).
|
Closing with credit — your version of the register-gate fix (cancel the old task before rediscovery) was the safest of the pre-park proposals. Post-#59222 the run task never dies (parks + self-probes), so tearing it down would fight the recovery design; PR #59331 (merged) instead wakes the parked task in place at session startup. Credited alongside #37772 (earliest) in the salvage. Thanks! |
register_mcp_servers now nudges cached entries whose session is None via _signal_reconnect, so a new agent session recovers a parked server immediately instead of waiting up to _PARKED_RETRY_INTERVAL for the next self-probe (NousResearch#50170). Gate-check idea credit: @izumi0uu (NousResearch#50184), @LeonSGP43 (NousResearch#37772), @Tranquil-Flow (NousResearch#37899).
register_mcp_servers now nudges cached entries whose session is None via _signal_reconnect, so a new agent session recovers a parked server immediately instead of waiting up to _PARKED_RETRY_INTERVAL for the next self-probe (NousResearch#50170). Gate-check idea credit: @izumi0uu (NousResearch#50184), @LeonSGP43 (NousResearch#37772), @Tranquil-Flow (NousResearch#37899).
register_mcp_servers now nudges cached entries whose session is None via _signal_reconnect, so a new agent session recovers a parked server immediately instead of waiting up to _PARKED_RETRY_INTERVAL for the next self-probe (NousResearch#50170). Gate-check idea credit: @izumi0uu (NousResearch#50184), @LeonSGP43 (NousResearch#37772), @Tranquil-Flow (NousResearch#37899).
register_mcp_servers now nudges cached entries whose session is None via _signal_reconnect, so a new agent session recovers a parked server immediately instead of waiting up to _PARKED_RETRY_INTERVAL for the next self-probe (NousResearch#50170). Gate-check idea credit: @izumi0uu (NousResearch#50184), @LeonSGP43 (NousResearch#37772), @Tranquil-Flow (NousResearch#37899).
What does this PR do?
register_mcp_servers()skips servers that already exist in the_serversdict. After a transport disconnect, MCP servers remain in_serverswithsession=None. The stale entry blocks reconnection, so tool handlers permanently return "not connected" until the agent restarts.This PR changes the skip condition to allow reconnection when
session is None(stale entry) while still skipping servers with an active session.Note: An existing PR (#37772) also addresses this issue. This PR differs by:
session is Nonecheck rather than unconditionally reconnecting all known serversRelated Issue
Fixes #37768
Type of Change
Changes Made
tools/mcp_tool.py— Changed the server-skip condition inregister_mcp_servers(): now allows reconnection whengetattr(_servers[k], "session", None) is None(stale entry), while still honoringenabled: falseand skipping servers with active sessionstests/tools/test_mcp_tool.py— Added 2 new tests (test_reconnects_stale_server_with_null_session,test_skips_healthy_server_with_active_session) and updated existingtest_skips_already_connected_serversto use a mock session objectHow to Test
python3 -m pytest tests/tools/test_mcp_tool.py::TestRegisterMcpServers -v -o 'addopts='All 8 tests pass in TestRegisterMcpServers. Fail-without-fix verified: reverting the condition change in
tools/mcp_tool.pycausestest_reconnects_stale_server_with_null_sessionto fail.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping