Skip to content

mcp: a malformed JSON frame terminates the stdio session #1209

Description

@blackwell-systems

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions