modelcontextprotocol / modelcontextprotocol/go-sdk

mcp: a malformed JSON frame terminates the stdio session

Open
#1,209 0 comments 0 reactions 0 assignees View on GitHub

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

  1. Start any stdio server (e.g. conformance/everything-server).
  2. Complete the initialize handshake.
  3. Send a syntactically invalid frame, e.g. {bad json followed by a newline.
  4. 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.