modelcontextprotocol / modelcontextprotocol/typescript-sdk
Bug: StdioServerTransport doesn't handle stdin close/end — causes zombie process accumulation
Nessuno ha ancora preso questa issue.
- Lingua principale
- TypeScript
- Stelle
- 13.4k
- Fork
- 2.2k
- Merge medio
- 3g 15h
- PR unite (30g)
- 4
Descrizione
Summary
StdioServerTransport does not listen for stdin close or end events. When an MCP client closes its window or restarts, it drops its end of the stdio pipe — but the server process never detects this and keeps running indefinitely. Over time, with multiple client sessions, this causes unbounded zombie process accumulation.
The bug
In src/server/stdio.ts, the start() method attaches listeners for data and error, but not for close or end:
// src/server/stdio.ts — start() method
this._stdin.on('data', this._ondata);
this._stdin.on('error', this._onerror);
// ← no 'close' or 'end' listener
When the MCP client (e.g. Claude Code) closes its window, it closes its end of the stdin pipe. Node.js fires stdin.on('close') and stdin.on('end') on the server side — but since nothing is listening, the server process keeps running forever.
Real-world impact
Tested with Claude Code (3 concurrent windows) using two MCP servers built on this SDK:
- After one day of normal use: 37 orphaned
google-tools-mcpprocesses, oldest consuming 26,807 CPU-seconds - 9 orphaned
gbrainprocesses after a few session restarts - Machine became unresponsive; only manual
taskkillcleared them
This is reproducible: close any Claude Code window and the MCP server process it spawned will remain running until the machine reboots.
Why SIGTERM doesn't help
The common suggestion is "handle SIGTERM" — but this doesn't solve it:
- On Windows,
SIGTERMis not delivered to child processes when a parent exits. Orphaning is guaranteed on Windows regardless of signal handlers. - On all platforms, when a stdio MCP client closes its connection (not the whole process), no signal is sent at all — only the pipe closes. The stdin
close/endevent is the only reliable cross-platform shutdown signal.
Note: PR #1568 addressed stdout EPIPE errors (client abruptly disconnecting mid-write), but left stdin disconnection unhandled.
The fix
In StdioServerTransport.start(), add two listeners:
async start(): Promise<void> {
if (this._started) {
throw new Error("StdioServerTransport already started! ...");
}
this._started = true;
this._stdin.on("data", this._ondata);
this._stdin.on("error", this._onerror);
// Exit when the MCP client closes the pipe (window closed, session restarted, etc.)
// This is the only reliable cross-platform shutdown signal for stdio servers.
this._stdin.on("close", () => this.onclose?.());
this._stdin.on("end", () => this.onclose?.());
}
And in close(), clean them up:
async close(): Promise<void> {
this._stdin.off("data", this._ondata);
this._stdin.off("error", this._onerror);
this._stdin.off("close", this._onclose); // add cleanup
this._stdin.off("end", this._onend); // add cleanup
// ... rest of existing close() logic
}
Impact
This is a universal issue — every stdio MCP server built on this SDK is affected. Fixing it here protects all downstream servers without requiring changes in each one individually. The fix is two lines in start().
Environment
- SDK version: 1.29.0 (latest)
- OS: Windows 11 (worst case — SIGTERM not delivered), but reproducible on all platforms
- MCP client: Claude Code
- Affected servers confirmed:
google-tools-mcp,gbrain, and any other server usingStdioServerTransport
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Inizia in src/server/stdio.ts e ispeziona StdioServerTransport.start() e close(), concentrandoti sui listener esistenti di data ed error e sulla loro pulizia. Riproduci una disconnessione del client se necessario, quindi verifica che gli eventi close/end di stdin attivino lo shutdown e che i listener vengano rimossi quando il transport viene chiuso.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- typescript
- Ambito
- backend
- Tipo di issue
- Bug
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Stato di attività
- Attiva
- Chiarezza
- Specificata chiaramente
- Idoneità per principianti
- 82/100