Skip to content

StdioClientTransport.close() doesn't clean up listeners, causing EventEmitter memory leak warning #3070

Open

Description

@amd-clb

Bug: StdioClientTransport.close() doesn't clean up listeners, causing EventEmitter memory leak warning

Description

When using local (stdio) MCP servers, Node.js emits the following warning:

MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added to [WriteStream]. MaxListeners is 10. Use emitter.setMaxListeners() to increase limit

Root cause

In StdioClientTransport:

  1. start() attaches permanent listeners to the stdio pipes:
this._process.stdin?.on("error", ...)
this._process.stdout?.on("data", ...)
this._process.stdout?.on("error", ...)
this._process.stderr.pipe(this._stderrStream)  // pipe() internally adds a "drain" listener
  1. close() kills the process but doesn't remove any listeners:
async close(){
  // ... kills the process ...
  // NO removeListener / off / unpipe here
}

The pipe() call on stderr automatically adds a drain listener on the source stream to handle back-pressure. When the process is killed, this listener is orphaned.

Reproduction

  1. Configure any local (stdio) MCP server that produces stderr output
  2. Launch an MCP client (e.g., opencode TUI)
  3. Observe the warning in stderr

Minimal MCP servers that don't produce stderr output don't trigger the warning. Real MCP servers (gitlab, google-workspace, etc.) that log to stderr do trigger it.

Proposed fix

In close(), add proper cleanup:

this._process.stderr?.unpipe()
this._process.stdin?.removeAllListeners()
this._process.stdout?.removeAllListeners()
this._process.stderr?.removeAllListeners()

Or alternatively, raise the max listeners limit in start():

this._process.stderr?.setMaxListeners(20)

Impact

Activity

  1. amd-clb commented on Oct 22, 2026

    @amd-clb
    Author

    Cross-reference: see #780 for the same root cause reported from a different angle (JSON parse error after close). This issue documents the MaxListenersExceededWarning on drain listeners specifically.

  2. added
    v1Issues / PRs related to v1.x
    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes
    on Oct 23, 2026
  3. suryakant-source commented on Oct 24, 2026

    @suryakant-source

    I'll backport the fix to the v1.x line, mirroring the approach in #3073 (shared drain wait, settle-on-exit/close, close()-time listener detach and stderr unpipe). PR coming up against v1.x.

  4. suryakant-source commented on Oct 24, 2026

    @suryakant-source

    Update: the v1.x backport is ready (fix + six regression tests, ported from #3073's approach), but this repository allows contributors without write access only 1 open PR at a time and my #3091 is still open. I'll open the v1.x PR as soon as #3091 merges or closes; the branch is staged on my fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v1Issues / PRs related to v1.xv2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions