Skip to content

jsonrpc2: server-closing error formats writeErr with %v, so errors.Is(err, io.EOF) is false for consumers #1098

Description

@nudzo

Summary

When a connection shuts down because its write side failed, internal/jsonrpc2/conn.go builds the error like this (line 674 at current HEAD, same in v1.6.1):

err = fmt.Errorf("%w: %v", ErrServerClosing, s.writeErr)

The write error — typically io.EOF when a stdio host closes the pipe — is formatted with %v, not %w, so it is not in the error chain: errors.Is(err, io.EOF) is false for every consumer.

ErrServerClosing itself lives in the internal/jsonrpc2 tree, so downstream code cannot name it either. The combination leaves message-text matching as the only way to classify this error, which is exactly what nobody should do.

Why it matters

A stdio MCP server that wants to exit 0 on a clean host disconnect ("the session ended") and exit 1 on a real failure cannot tell the two apart. Concretely: our CLI's MCP server received a request, the host closed stdin while the response was in flight, and the server exited 1 printing server is closing: EOF — reporting a crash where the session merely ended. (We observed this deterministically: send one initialize request through a pipe and close stdin immediately.)

We've since worked around it by classifying shutdown from our own transport observation rather than from the returned error, so this is a good-citizenship report rather than a blocker.

Suggested fix

One character: %v%w on the s.writeErr. That makes errors.Is(err, io.EOF) (and any other sentinel the underlying writer returns) work as intended.

Possibly worth considering alongside: mcp/transport.go internally checks errors.Is(err, jsonrpc2.ErrServerClosing), which downstreams cannot express — a public predicate (or re-exported sentinel) for "the server is closing" would remove the last reason to look at the message text.

Related but distinct: #1061 describes response loss in the same stdin-EOF-while-in-flight scenario; this issue is only about the wrapping of the returned error.

Versions

  • github.com/modelcontextprotocol/go-sdk v1.6.1 (latest release); still present at HEAD (internal/jsonrpc2/conn.go:674).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions