Skip to content

[v1.x] fix(server): close StdioServerTransport when stdin ends or closes - #2911

Open
maxymlyskov wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
maxymlyskov:fix/v1-stdio-server-close-on-stdin-end
Open

maxymlyskov wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
maxymlyskov:fix/v1-stdio-server-close-on-stdin-end

Conversation

@maxymlyskov

Copy link
Copy Markdown

Backport of #2494 for #2002 on v1.x.

On v1.x, StdioServerTransport.start() listens for stdin data and error and nothing else (src/server/stdio.ts#L53-L63). So when the client hangs up the pipe, the transport never closes and onclose never fires. A server that frees a timer or pool in onclose keeps running with no parent. #2002 has 6 of them piling up in ~17 hours (all PPID=1) and another report of 7 in 5 days on 1.29.0, and its triage comment reproduced the same code on v1.x.

Driver: a real child server holding a setInterval it clears in onclose. It completes initialize, then child.stdin.end(), no signal:

### v1.x c2ce0031 (Windows 11, Node v22.12.0)
RESULT: server pid 22588 STILL RUNNING 10009 ms after stdin EOF, onclose never fired -> SIGKILL
### this branch (Windows 11, Node v22.12.0)
  [child stderr] server: onclose fired (#1), keep-alive released
RESULT: server exited code=0 10 ms after stdin EOF
### v1.x c2ce0031 (WSL2 Linux, Node v22.22.0)
RESULT: server pid 496 STILL RUNNING 10011 ms after stdin EOF, onclose never fired -> SIGKILL
### this branch (WSL2 Linux, Node v22.22.0)
  [child stderr] server: onclose fired (#1), keep-alive released
RESULT: server exited code=0 7 ms after stdin EOF

The fix is the stdin half of #2494: end and close listeners that close the transport, removed again in close(), a _closed guard (today a second close() fires onclose twice), and a setImmediate close when stdin already ended or was destroyed before start().

One change a v1 user can notice: requests still in flight at stdin EOF are aborted and their responses aren't written. Same as main, and the changeset says so.

Left out: stdout error handling, send() rejecting after close and the swallow-listener sweep. On v1.x that's open #2579, which edits this file and adds its own _closed guard, so whichever lands second gets a small rebase.

Tests

On untouched v1.x, 8 of the 9 fail and the listener test passes on both:

     × server process should exit on its own when the client closes stdin 8497ms
   × should close and fire onclose when stdin ends (client hung up) 5011ms
   × should close and fire onclose when stdin closes 5010ms
   × should fire onclose when stdin was already destroyed before start() 5012ms
   × should fire onclose when stdin had already ended before start() 5014ms
   × should fire onclose assigned after start() when stdin was already dead 5005ms
   × should not fire onclose twice when close() is called after stdin ends 5014ms
   ✓ should remove its stdin end and close listeners on close 1ms
   × should still deliver messages that arrived before stdin ended 5009ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 8 ⎯⎯⎯⎯⎯⎯⎯
AssertionError: expected 'zombie' to be +0 // Object.is equality
Error: Test timed out in 5000ms.
 Test Files  2 failed (2)
      Tests  8 failed | 1 passed | 8 skipped (17)
  • npm test: 1818 passed (Windows 11 / Node 22.12)
  • npm run test:e2e: 1114 passed, 1 failed. transport:stdio:shutdown-escalation fails the same way on untouched v1.x (Windows delivers no catchable SIGTERM). On WSL2 Linux / Node 22.22: 1115 passed.
  • npm run typecheck, npm run lint: clean

Changeset included.

StdioServerTransport listened only for 'data' and 'error' on stdin, so a
server whose client hung up its end of the pipe never closed: onclose never
fired, and a server holding a keep-alive handle kept running with no parent
(modelcontextprotocol#2002).

Close the transport on stdin 'end' and 'close', also when the stream had
already ended or been destroyed before start(), and fire onclose once however
many of those arrive. This ports the stdin part of modelcontextprotocol#2494 (6a05402) and its
tests; stdout error handling stays with modelcontextprotocol#2579.
@maxymlyskov
maxymlyskov requested a review from a team as a code owner September 30, 2026 21:57
@changeset-bot

changeset-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5fec54b

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@modelcontextprotocol/sdk@2911

commit: 5fec54b

@claude claude Bot added the v1 Issues / PRs related to v1.x label Oct 1, 2026

This branch has not been deployed

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

Labels

v1 Issues / PRs related to v1.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant