fix: correct inverted liveness check in _stdio_children_dead - #95012
RafaelGali-Dextra wants to merge 2 commits into
Conversation
_stdio_children_dead() is meant to return True only when every stdio child the MCP server spawned has exited (used to fast-fail a tool call against a known-dead process instead of waiting out the full timeout, see NousResearch#81995). The loop had the return values inverted: as soon as it found a LIVE pid (psutil.pid_exists() True), it returned True ("all dead") instead of False ("at least one still alive"). The correct False branch was placed after an unconditional return and was dead code, unreachable. Net effect: any stdio MCP server with a captured child PID fails its very first real tool call with: MCP stdio subprocess for '<name>' has exited; failing the call fast instead of waiting 300s even though the subprocess is alive and would have answered normally. Connection/listing (hermes mcp test, tool discovery) is unaffected since it does not go through this fast-fail path, which is why the server appears healthy right up until the first tool call. Fix: return False when a live pid is found (mirrors the docstring: "True when every stdio child we spawned has exited"), remove the unreachable duplicate return. Reproduced with sooperset/mcp-atlassian (Jira) run over stdio via uvx: every jira_* tool call failed immediately post-connect until this fix was applied; tool calls succeeded normally afterward.
Regression coverage for the inverted-liveness bug fixed in the previous commit: a live child pid must yield False (not dead), all children exited must yield True, and no captured pids must yield False (unknown -> don't fast-fail).
|
Independent reproduction, different platform/servers than the reports above: hit this today on macOS via two unrelated stdio servers — Confirmed root cause matches this PR: Applied the same fix locally (swap the inverted |
|
Closing — the |
Summary
MCPServerTask._stdio_children_dead()is meant to returnTrueonly when every stdio child the MCP server spawned has exited, so the fast-fail path (#81995) can fail a tool call immediately against a known-dead process instead of waiting out the full tool timeout.The loop had its return values inverted: as soon as it found a live pid (
psutil.pid_exists()True), it returnedTrue("all dead") instead ofFalse("at least one still alive"). The intendedFalsebranch sat right after an unconditionalreturn, so it was unreachable dead code.Impact
Any stdio MCP server with a captured child PID fails its very first real tool call with:
even though the subprocess is alive and would answer normally. Connection/tool-discovery (
hermes mcp test, initial tool list) is unaffected because it doesn't go through this fast-fail path — which is why the server looks perfectly healthy right up until the first tool call, making this look like an auth/connectivity problem rather than a liveness-check bug.Reproduced end-to-end with
sooperset/mcp-atlassian(Jira) run over stdio viauvx: everyjira_*tool call failed immediately post-connect until this fix was applied (verified against the live Jira REST API in parallel — credentials/auth were fine the whole time). Tool calls succeed normally after the fix.Changes
tools/mcp_tool.py: fix the inverted return value, drop the unreachable duplicatereturn.tests/tools/test_mcp_tool.py: addTestStdioChildrenDeadcovering the three states (child alive, all children exited, no pids captured).Test Plan
pytest tests/tools/test_mcp_tool.py -k TestStdioChildrenDead -v(3 passed)pytest tests/tools/test_mcp_tool.py -q(101 passed)