You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add ways for server processes to self-terminate when host is gone #208
While working on VS Code and LSP we have learned that it is quite hard to prevent leaking processes, e.g stdio servers might be lingering around long after the host is gone. This can be due to various reasons and an effective way we have found is to start a server with the PID of its host. With that the server periodically checks for the PID and terminates if it is gone, like so https://github.com/microsoft/vscode-languageserver-node/blob/main/server/src/node/main.ts#L51
Having something like this in the SDK would benefit MCP servers and help to keep process-bleed in check
I'd like to take this one. Planning to add a parent PID check similar to the VS Code LSP approach mentioned — server periodically checks if the host PID is alive and gracefully terminates if it's gone. Will submit a PR soon.
Three PRs have tried to land here (#1572, #1815, #1712) and the issue is still
labelled needs decision, so rather than propose a fourth implementation I want
to offer data on the specific choice that seems to be blocking it.
The choice is parent-PID polling vs. stdin EOF.#1712 takes the VS Code LSP
approach — process.kill(hostPid, 0) on a 3s interval — which is a reasonable
reference and works. Two properties of it are worth being explicit about before
it becomes the SDK's default.
Up front: my own measurements come from the client side of this problem —
supervising spawned children rather than a server watching its host. The
mechanism comparison transfers; the implementation doesn't, so I'm offering this
as evidence about the two detection strategies, not as a proposal to adopt
anything of mine.
1. PID reuse turns a transient failure into a permanent one. If the host dies
and its PID is recycled before the next tick, kill(pid, 0) succeeds against an
unrelated process and the watchdog concludes the host is alive — not for one
interval, but for the rest of the server's life. The window is small; the
consequence is unbounded. Any poll-the-PID design needs a second signal — process
start time, or a token passed at spawn — to be sound. Worth deciding deliberately
rather than inheriting, since the LSP reference has the same property.
2. Detection latency is the poll interval. At the 3s default, a host that dies
at t=0 leaves the server running until t≤3s. That is fine if the goal is eventual
cleanup. It is less fine if the motivation is the accumulation described in #2002, where short-lived sessions churn.
stdin EOF has neither property. The kernel closes the pipe when the host dies
— by any means, including SIGKILL — so detection is immediate, and no PID is ever
consulted, so recycling cannot produce a false "alive".
I built a harness for the client-side version of this and ran the EOF approach
against the most hostile case I could construct: the parent SIGKILLed in the same tick as the last spawn, so no userspace cleanup can possibly run. 0 of 48
leaked across 12 trials. Buffered writes still arrive, because Node fires 'end' only after the stream drains — a registration written microseconds before
death is processed before the reap. That is macOS / Node 24 only; I have not run
it on Linux or Windows.
Which raises a scoping question. If #2002 lands stdin close/end handling
server-side, the ordinary case here is already covered — the server exits when the
host drops the pipe, with no watchdog involved. The watchdog's remaining value is
then the narrower case where stdin is not the host's pipe: servers launched with
stdin redirected, or behind a wrapper that holds the pipe open.
That is a real gap and worth solving. But it is a much smaller one than "servers
should self-terminate when the host is gone", and it may be why this issue keeps
stalling — three PRs have scoped it as a general mechanism while the general case
is being solved in another thread.
If it would help whoever picks this up, the harness is public. It runs a matrix
over spawn shape × cleanup strategy × how the parent dies, and includes
no-cleanup control scenarios that must always orphan — so a broken detector
aborts the run instead of silently reporting everything as clean: https://github.com/ettalin/subreap
Happy to run any proposed implementation through it.
Activity
Merge pull request modelcontextprotocol#208 from olaservo/update-readme
feat(server): add host process watchdog to StdioServerTransport
windro-xdd commentedon Mar 29, 2026
I'd like to take this one. Planning to add a parent PID check similar to the VS Code LSP approach mentioned — server periodically checks if the host PID is alive and gracefully terminates if it's gone. Will submit a PR soon.
feat(server): add host process watchdog to StdioServerTransport
ettalin commentedon Sep 3, 2026
Three PRs have tried to land here (#1572, #1815, #1712) and the issue is still
labelled
needs decision, so rather than propose a fourth implementation I wantto offer data on the specific choice that seems to be blocking it.
The choice is parent-PID polling vs. stdin EOF. #1712 takes the VS Code LSP
approach —
process.kill(hostPid, 0)on a 3s interval — which is a reasonablereference and works. Two properties of it are worth being explicit about before
it becomes the SDK's default.
Up front: my own measurements come from the client side of this problem —
supervising spawned children rather than a server watching its host. The
mechanism comparison transfers; the implementation doesn't, so I'm offering this
as evidence about the two detection strategies, not as a proposal to adopt
anything of mine.
1. PID reuse turns a transient failure into a permanent one. If the host dies
and its PID is recycled before the next tick,
kill(pid, 0)succeeds against anunrelated process and the watchdog concludes the host is alive — not for one
interval, but for the rest of the server's life. The window is small; the
consequence is unbounded. Any poll-the-PID design needs a second signal — process
start time, or a token passed at spawn — to be sound. Worth deciding deliberately
rather than inheriting, since the LSP reference has the same property.
2. Detection latency is the poll interval. At the 3s default, a host that dies
at t=0 leaves the server running until t≤3s. That is fine if the goal is eventual
cleanup. It is less fine if the motivation is the accumulation described in
#2002, where short-lived sessions churn.
stdin EOF has neither property. The kernel closes the pipe when the host dies
— by any means, including SIGKILL — so detection is immediate, and no PID is ever
consulted, so recycling cannot produce a false "alive".
I built a harness for the client-side version of this and ran the EOF approach
against the most hostile case I could construct: the parent SIGKILLed in the
same tick as the last spawn, so no userspace cleanup can possibly run. 0 of 48
leaked across 12 trials. Buffered writes still arrive, because Node fires
'end'only after the stream drains — a registration written microseconds beforedeath is processed before the reap. That is macOS / Node 24 only; I have not run
it on Linux or Windows.
Which raises a scoping question. If #2002 lands stdin close/end handling
server-side, the ordinary case here is already covered — the server exits when the
host drops the pipe, with no watchdog involved. The watchdog's remaining value is
then the narrower case where stdin is not the host's pipe: servers launched with
stdin redirected, or behind a wrapper that holds the pipe open.
That is a real gap and worth solving. But it is a much smaller one than "servers
should self-terminate when the host is gone", and it may be why this issue keeps
stalling — three PRs have scoped it as a general mechanism while the general case
is being solved in another thread.
If it would help whoever picks this up, the harness is public. It runs a matrix
over spawn shape × cleanup strategy × how the parent dies, and includes
no-cleanup control scenarios that must always orphan — so a broken detector
aborts the run instead of silently reporting everything as clean:
https://github.com/ettalin/subreap
Happy to run any proposed implementation through it.