Skip to content

leak(http): per-session logger piped into process-lifetime shared transport, never detached on stop() #404

Description

@debugmcpdev

Problem

In Streamable HTTP mode, every MCP session constructs a full DebugMcpServer (src/cli/http-command.ts:140-144), whose DI container calls createLogger('debug-mcp', …) (src/container/dependencies.ts:67). Each such winston logger is piped into the process-lifetime shared file transport (src/utils/logger.ts:49 cache; :208 attach), and:

  • DebugMcpServer.stop() (src/server.ts:2858-2869) never closes or detaches its logger;
  • nothing in src/ ever calls logger.close() — by design, since closing would kill the shared transport for all loggers (logger.ts:44-48).

Node's pipe() attaches unpipe/drain/error/close listeners on the destination that close over the source, so the shared transport accumulates one listener set + retained logger per HTTP session, forever. The HTTP stale-session reaper (MCP_HTTP_STALE_SESSION_MS) tears down the DebugMcpServer but cannot reclaim this. Under sessionful churn (the exact scenario the reaper exists for — see #337) this is a slow leak, and past ~10 sessions winston/stream maxListeners warnings become plausible.

Proposal

  • Give DebugMcpServer.stop() (or the DI container's disposal path) a way to detach its logger from the shared transport without closing the transport: logger.remove(transport) / logger.unpipe(transport) on the logger side leaves the shared transport alive for others.
  • Add a unit test: create + stop N servers against one shared transport, assert the transport's listener counts don't grow.

Note (no action, recording while in the area)

The deprecated SSE transport's session map (src/cli/sse-command.ts:45) has no stale reaper — entries are removed only via transport/req close events (:96-120). HTTP mode got a reaper for exactly this gap. Since SSE is deprecated and shares one DebugMcpServer, the exposure is just the transport entry; not worth fixing separately, but worth knowing if a leak report ever comes in against SSE mode.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions