Skip to content

StdioClientTransport.close() does not kill the process tree, leaving orphan processes #2023

@JoannaaKL

Description

@JoannaaKL

Problem

When StdioClientTransport.close() is called, only the direct child process is signaled. If the MCP server was started via a wrapper command (e.g. npx, uvx, python -m), the wrapper's child processes (the actual server) become orphans and continue running indefinitely.

Root cause

The close() method uses processToClose.kill('SIGTERM') / processToClose.kill('SIGKILL'), which calls Node's ChildProcess.kill(). This only signals the direct child PID — not its descendants.

This affects all platforms:

  • macOS/Linux: No process group kill (process.kill(-pid, signal)) is used, so children of the direct process are not signaled.
  • Windows: ChildProcess.kill() does not kill the process tree at all. Only taskkill /T /F /PID <pid> can do that.

Reproduction

  1. Start an MCP server via a wrapper (e.g. npx @some/mcp-server)
  2. Call transport.close()
  3. Observe that npx is killed but the underlying node process remains running

Possible solutions

Option A: Kill the process tree in close() — use pgrep -P (Unix) or taskkill /T /F (Windows) to enumerate and signal all descendants before/instead of ChildProcess.kill().

Option B: Spawn with detached: true and use process.kill(-pid, signal) to kill the process group. However, this has trade-offs: if the parent crashes, detached children become permanent orphans with no automatic cleanup.

Option C: Expose a hook or option so consumers can provide their own process cleanup logic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions