Summary
The rmcp stdio transport closes the stream cleanly when it receives a malformed JSON line or a UTF-8 BOM-prefixed message, instead of replying with a JSON-RPC -32700 Parse Error response as required by JSON-RPC 2.0 §5.1.
This was found while writing integration tests for an MCP server using rmcp 1.5.
Reproduction
Send a malformed line followed by a valid initialize request to any rmcp stdio server:
echo 'not json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"x","version":"0"}}}' | <your-rmcp-stdio-server>
The same behavior occurs when the very first byte sequence is the UTF-8 BOM (EF BB BF) before an otherwise-valid JSON-RPC message — the BOM trips the parser and the stream is torn down.
Observed
Server logs:
Error reading from stream: serde error expected ident at line 1 column 2
connection closed: initialize request
Process exits with status 0. The client never receives a -32700 response and has no way to distinguish parse-side failure from a normal peer-initiated EOF.
Expected (per JSON-RPC 2.0 §5.1)
The server MUST emit:
{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}
…and remain available to process subsequent valid messages on the same session. Spec quote:
-32700 Parse error — Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
The id field is null because the malformed input means no request id could be extracted.
Why this matters
- Spec conformance. JSON-RPC 2.0 §5.1 requires the
-32700 response. The current behavior silently drops it.
- Client recovery. Clients that follow the spec expect to recover from a single bad line and continue the session (e.g. when a wrapper accidentally injects a stray log line, or when a tool emits a BOM). Today the entire connection is torn down with exit
0, indistinguishable from a clean shutdown.
- Diagnosability. A client that sees the stream close cleanly has no signal that the cause was a parse error on its side, only a generic "connection closed" — making it hard to surface actionable errors to users.
- Robustness against UTF-8 BOM. Some upstream tooling (Windows-origin scripts, certain editors) prepends a BOM. The line-delimited JSON reader treats the BOM as a parse failure rather than stripping or skipping it. Either tolerating the BOM or replying
-32700 (and continuing) would both be acceptable; silently closing the stream is not.
Suggested direction
In the stdio transport's read loop, when serde_json fails on an incoming line:
- Construct a
JsonRpcError with code: -32700, message: "Parse error", id: null.
- Send it through the existing sink to the peer.
- Continue the read loop instead of returning
None / breaking.
A separate consideration is whether to strip a leading UTF-8 BOM on the very first read, since per RFC 8259 §8.1, JSON parsers MAY ignore a BOM but MUST NOT add one — being lenient on input would prevent a subset of these reports.
Environment
rmcp 1.5
- Stdio transport
- Reproduces on macOS (Apple Silicon) and Linux
Summary
The
rmcpstdio transport closes the stream cleanly when it receives a malformed JSON line or a UTF-8 BOM-prefixed message, instead of replying with a JSON-RPC-32700 Parse Errorresponse as required by JSON-RPC 2.0 §5.1.This was found while writing integration tests for an MCP server using
rmcp1.5.Reproduction
Send a malformed line followed by a valid
initializerequest to any rmcp stdio server:The same behavior occurs when the very first byte sequence is the UTF-8 BOM (
EF BB BF) before an otherwise-valid JSON-RPC message — the BOM trips the parser and the stream is torn down.Observed
Server logs:
Process exits with status
0. The client never receives a-32700response and has no way to distinguish parse-side failure from a normal peer-initiated EOF.Expected (per JSON-RPC 2.0 §5.1)
The server MUST emit:
{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}…and remain available to process subsequent valid messages on the same session. Spec quote:
The
idfield isnullbecause the malformed input means no request id could be extracted.Why this matters
-32700response. The current behavior silently drops it.0, indistinguishable from a clean shutdown.-32700(and continuing) would both be acceptable; silently closing the stream is not.Suggested direction
In the stdio transport's read loop, when
serde_jsonfails on an incoming line:JsonRpcErrorwithcode: -32700,message: "Parse error",id: null.None/ breaking.A separate consideration is whether to strip a leading UTF-8 BOM on the very first read, since per RFC 8259 §8.1, JSON parsers MAY ignore a BOM but MUST NOT add one — being lenient on input would prevent a subset of these reports.
Environment
rmcp1.5