fix(stdio): strip a leading UTF-8 byte order mark before parsing a message - #2790
fix(stdio): strip a leading UTF-8 byte order mark before parsing a message#2790errmakov wants to merge 1 commit into
Conversation
…ssage ReadBuffer skips lines that fail JSON.parse so that stray debug output on stdout does not break the transport. A message that a peer prefixed with a UTF-8 BOM (as some Windows tools and shell redirections do) failed to parse for the same reason, so it was dropped without an error and the pending request waited for its timeout. Strip a leading U+FEFF from each line before parsing, which RFC 8259 §8.1 allows. A U+FEFF inside the message is left alone. Refs modelcontextprotocol#2775 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P8hmy9JVNHqYDYEX5KBYye
🦋 Changeset detectedLatest commit: 7b48bd9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
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 |
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
BerkantACUN
left a comment
There was a problem hiding this comment.
Ran it rather than only reading it: npm i https://pkg.pr.new/@modelcontextprotocol/client@2790, a stdio server that writes EF BB BF before its tools/list response, 2 s request timeout.
| caller sees | |
|---|---|
@modelcontextprotocol/client@2.0.0 |
Request timed out after 2012 ms, no onerror |
| this PR | 1 tool in 4 ms |
The change is where it belongs — next to the strip, before the SyntaxError skip that was hiding the message — and the three unit cases cover the shapes that matter (split across chunks, CRLF, U+FEFF inside a message left alone). Nothing to add from my side.
For the record, the other two parts of #2775 are up as #2792 and #2793; they touch append()/clear() in the same file, not readMessage(), so there is no overlap with this.
Part of #2775: the byte order mark case from the reporter's follow-up. The issue's other two parts (carrying the transport error into the rejection, and resyncing after an oversized message) are left to the reporter, as noted on the issue.
Problem
When a stdio peer writes a UTF-8 byte order mark before a message (some Windows tools and shell redirections do),
ReadBuffer.readMessage()gets a line that starts with U+FEFF.JSON.parsethrows aSyntaxError, andReadBufferdeliberately skipsSyntaxErrorlines so stray debug output on stdout doesn't break the transport. The message is dropped without anonerror, and the pending request waits for its timeout.With a stdio server that writes a BOM before its
tools/listresponse and a 2 s request timeout:@modelcontextprotocol/client@2.0.0Request timed outafter 2002 ms, and noonerror@modelcontextprotocol/sdk@1.30.0onerrorgets theSyntaxErrorfromJSON.parse; the call still times out after 2001 msChange
Strip a leading U+FEFF from each line before parsing, next to the existing trailing
\rstrip. RFC 8259 §8.1 lets parsers ignore a BOM. A JSON-RPC message always starts with{, so this is a no-op for every line that already parsed; it only rescues lines that were being dropped. A U+FEFF inside a message is left alone.Tests
core-internal: a BOM-prefixed message parses; a BOM split across two chunks with a CRLF line ending parses; a U+FEFF inside a string value is preserved.client: a spawnednode -eprocess writes a BOM-prefixed message, andStdioClientTransportdelivers it.The first two unit tests and the client test fail on
mainand pass with the change.pnpm check:allpasses, and so do thecore-internal,clientandserversuites. In the full local run, two SSEprotocol:timeout:max-totale2e cases fail on untouchedmainas well, so they are unrelated.The
v1.xbackport is #2796 (a draft only because GitHub lets this account keep one non-draft PR here; it is complete).AI disclosure: this PR was written with the help of Claude Code.