modelcontextprotocol / modelcontextprotocol/typescript-sdk

Bug: StdioServerTransport doesn't handle stdin close/end — causes zombie process accumulation

Ouverte Adaptée aux débutants
#2,002 8 commentaires 2 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

bug fix proposed P1 ready for work
Langage dominant
TypeScript
Étoiles
13.4k
Forks
2.2k
Merge moyen
3 j 15 h
PR mergées (30 j)
4

Description

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-mcp processes, oldest consuming 26,807 CPU-seconds
  • 9 orphaned gbrain processes after a few session restarts
  • Machine became unresponsive; only manual taskkill cleared 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:

  1. On Windows, SIGTERM is not delivered to child processes when a parent exits. Orphaning is guaranteed on Windows regardless of signal handlers.
  2. 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/end event 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 using StdioServerTransport

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez dans src/server/stdio.ts et examinez StdioServerTransport.start() et close(), en vous concentrant sur les listeners data et error existants et sur leur nettoyage. Reproduisez une déconnexion du client si nécessaire, puis vérifiez que les événements close/end de stdin déclenchent l’arrêt et que les listeners sont supprimés lorsque le transport se ferme.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
typescript
Domaine
backend
Type d'issue
Bug
Difficulté
2/5
Temps estimé
1-3 heures
Activité
Active
Clarté
Clairement spécifiée
Accessibilité débutants
82/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.