Follow-up to #353, which defined two halves and shipped only the first.
What shipped in v1.3.2
Detection. A dead stdio subprocess is now noticed two ways — the child closing its error stream, confirmed with a ping so a server that closes it while still running keeps serving, and a tool call failing with a closed transport or a broken pipe. The server is marked unready, so its tools stop being advertised to the model and stop resolving.
What did not
Restart. Marking a server unready is terminal: nothing re-initializes it, so recovery requires a configuration reload or a process restart. A crashed MCP server stays withdrawn for the life of the process even though respawning it is straightforward.
Why it was deferred
Real supervision means one goroutine owning the child — Start → Wait → decide. The current library calls cmd.Wait() itself from Close() and keeps cmd unexported, so the gateway cannot own that loop. Blocked on #360.
Scope
- Respawn an unready stdio server with exponential backoff.
- Crash-loop protection: reset the backoff on sustained uptime, not on spawn. A server that dies immediately every time must not be restarted at full rate forever.
- Bound the retry state so a permanently broken server settles rather than growing unboundedly.
- Recovery should not require an operator action, which is the current situation.
No new machinery is needed for the respawn itself: RegisterConfig already closes the old client and spawns fresh, InitializeAll re-handshakes and re-indexes, and the entry retains its configuration. What is missing is a supervisor that can decide when to invoke them.
Related
A restart loop would also make withdrawal recoverable for the HTTP transport (#358), where a refused connection is routinely transient and terminal withdrawal is therefore unsafe on its own.
Follow-up to #353, which defined two halves and shipped only the first.
What shipped in v1.3.2
Detection. A dead stdio subprocess is now noticed two ways — the child closing its error stream, confirmed with a ping so a server that closes it while still running keeps serving, and a tool call failing with a closed transport or a broken pipe. The server is marked unready, so its tools stop being advertised to the model and stop resolving.
What did not
Restart. Marking a server unready is terminal: nothing re-initializes it, so recovery requires a configuration reload or a process restart. A crashed MCP server stays withdrawn for the life of the process even though respawning it is straightforward.
Why it was deferred
Real supervision means one goroutine owning the child —
Start→Wait→ decide. The current library callscmd.Wait()itself fromClose()and keepscmdunexported, so the gateway cannot own that loop. Blocked on #360.Scope
No new machinery is needed for the respawn itself:
RegisterConfigalready closes the old client and spawns fresh,InitializeAllre-handshakes and re-indexes, and the entry retains its configuration. What is missing is a supervisor that can decide when to invoke them.Related
A restart loop would also make withdrawal recoverable for the HTTP transport (#358), where a refused connection is routinely transient and terminal withdrawal is therefore unsafe on its own.