modelcontextprotocol / modelcontextprotocol/go-sdk
mcp: a malformed JSON frame terminates the stdio session
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 543
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 37
Description
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 jsonfollowed by a newline. - The session ends: no
-32700is 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at newIOConn and its streaming JSON decoder, then reproduce the behavior with the minimal transport-level example or conformance/everything-server. Done means malformed frames receive JSON-RPC -32700 errors while the following valid ping is still delivered, with tests covering the continued session.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100