Describe the bug
The stdio transport ends the entire session on a single syntactically malformed JSON-RPC frame, instead of replying with a JSON-RPC parse error (-32700) and continuing. Any client that sends one malformed frame terminates the session.
Root cause: newIOConn reads from a single streaming json.Decoder over stdin. A malformed frame makes Decode return a *json.SyntaxError; the read goroutine returns on that error, ending the connection. A streaming decoder also cannot resynchronize after a syntax error, since its buffered stream state is poisoned.
This is the transport-level cousin of #976 (empty-method requests), fixed in #1000. mark3labs/mcp-go handles the same input correctly: it reads newline-delimited frames, unmarshals each independently, and on a bad frame replies -32700 and keeps serving.
To Reproduce
- Start any stdio server (e.g.
conformance/everything-server).
- Complete the initialize handshake.
- Send a syntactically invalid frame, e.g.
{bad json followed by a newline.
- The session ends: no
-32700 is returned, and later valid requests get no response.
Minimal transport-level repro:
tr := newIOConn(rwc{rc: io.NopCloser(strings.NewReader(
"{bad\n" + `{"jsonrpc":"2.0","id":1,"method":"ping"}` + "\n"))})
_, err := tr.Read(context.Background())
// returns the decode error; the following valid "ping" is never read.
Expected behavior
Per JSON-RPC 2.0, a malformed frame should be answered with a -32700 parse error (id: null) and the session should continue: the read loop resynchronizes to the next frame and delivers the following valid request. This matches mark3labs/mcp-go and the recoverable-decode direction of #1000.
Additional context
Tested v1.4.1 through v1.7.0 (latest); behavior is unchanged. Surfaced by an MCP conformance study run against a downstream server (blackwell-systems/agent-lsp#14); because the behavior lives in the shared stdio transport, every server built on the SDK's stdio transport exhibits it.
I have a fix ready (reply -32700, resync to the next newline-delimited frame, terminate only on a genuine EOF/I/O error, with tests) and will open a PR referencing this issue.
Describe the bug
The stdio transport ends the entire session on a single syntactically malformed JSON-RPC frame, instead of replying with a JSON-RPC parse error (-32700) and continuing. Any client that sends one malformed frame terminates the session.
Root cause:
newIOConnreads from a single streamingjson.Decoderover stdin. A malformed frame makesDecodereturn a*json.SyntaxError; the read goroutine returns on that error, ending the connection. A streaming decoder also cannot resynchronize after a syntax error, since its buffered stream state is poisoned.This is the transport-level cousin of #976 (empty-method requests), fixed in #1000.
mark3labs/mcp-gohandles the same input correctly: it reads newline-delimited frames, unmarshals each independently, and on a bad frame replies-32700and keeps serving.To Reproduce
conformance/everything-server).{bad jsonfollowed by a newline.-32700is returned, and later valid requests get no response.Minimal transport-level repro:
Expected behavior
Per JSON-RPC 2.0, a malformed frame should be answered with a
-32700parse error (id: null) and the session should continue: the read loop resynchronizes to the next frame and delivers the following valid request. This matchesmark3labs/mcp-goand the recoverable-decode direction of #1000.Additional context
Tested v1.4.1 through v1.7.0 (latest); behavior is unchanged. Surfaced by an MCP conformance study run against a downstream server (
blackwell-systems/agent-lsp#14); because the behavior lives in the shared stdio transport, every server built on the SDK's stdio transport exhibits it.I have a fix ready (reply
-32700, resync to the next newline-delimited frame, terminate only on a genuine EOF/I/O error, with tests) and will open a PR referencing this issue.