fix(server): handle stdout errors in StdioServerTransport (v1.x backport of #1568) - #2579
Draft
ondraulehla wants to merge 1 commit into
Draft
fix(server): handle stdout errors in StdioServerTransport (v1.x backport of #1568)#2579ondraulehla wants to merge 1 commit into
ondraulehla wants to merge 1 commit into
Conversation
A stdio client that disconnects makes the server's next write fail with EPIPE, and nothing listened for 'error' on stdout, so that became an unhandled 'error' event and took the whole Node process down. Listen for it, report it through onerror and close the transport instead. send() also waited for a 'drain' event that a destroyed stream never emits. It now settles from an error listener armed before the write, removes both listeners on every exit path, and rejects after close() instead of writing to a stream the transport has released. close() is idempotent, so an error-triggered close followed by an explicit one fires onclose once. Backport of modelcontextprotocol#1568 to the v1.x line.
🦋 Changeset detectedLatest commit: f316cc2 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 |
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backport of #1568 to
v1.x.StdioServerTransportthere attaches an'error'listener tostdinbut not tostdout, so a stdio client that goes away turns the server's next write into an unhandled'error'event.Motivation and Context
#1564 reported this as a fatal process crash and #1568 fixed it on
mainin March, but the port never happened, so the released@modelcontextprotocol/sdk@1.30.0still ships the old shape:dist/cjs/server/stdio.jsregisters only thestdinlistener, andsend()resolves on'drain'with no failure path.I reproduced it against the published package rather than a synthetic script. A server built on
sdk@1.30.0whose client stops reading and closes the pipe dies withUnhandled 'error' eventand exit code 1; the same server built from this branch keeps running with nothing on stderr. For scale, the v1 package sits at roughly 47M weekly downloads against 230k for the v2 server package, so the line without the fix is the one nearly everyone runs.The
send()half matters while the server is alive: a destroyed stream never drains, so a backpressured write that fails leaves the promise pending instead of rejecting. That is the same defect I reported for the client transport in #2552.This is a faithful port rather than a new design. The
_closedfield, the_onstdouterrorhandler, thestdoutlistener instart(), the idempotency guard and listener removal inclose(), and thesettledflag insend()are the same code as the merged v2 version, adapted to the v1 file layout and its plainErrorusage.How Has This Been Tested?
test/server/stdio.test.ts. Against the currentv1.xsource all five fail, includingshould reject send() when stdout errors before drain, which times out instead of rejecting. With this change all ten tests in that file pass.stdouterror the transport closes,send()rejects withStdioServerTransport is closed, and no'error'or'drain'listeners are left on the stream.npm run typecheckandnpm run lintare clean, changeset included.Breaking Changes
None for a working client. Two failures that used to be silent now surface: a failed write rejects
send()instead of leaving the promise pending, andsend()afterclose()rejects instead of writing to a stream the transport has released. Both match the v2 behaviour from #1568. On astdouterror the transport closes itself and leaves the exit decision to the application, as onmain.Types of changes
Checklist