Skip to content

bug: a request sent before initialize is answered with JSON-RPC error code 0 instead of -32600 #1367

Closed

Description

@Goldziher

Summary

A request sent before initialize is answered with a JSON-RPC error whose code is 0. 0 is not a JSON-RPC 2.0 error code; the spec requires -32600 (Invalid Request) for a request that cannot be served.

Version

github.com/modelcontextprotocol/go-sdk v1.8.0 (also observed on main).

Reproduction

Run a server over a raw stdio transport (IOTransport) and, before sending initialize, send:

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}

Observed response:

{"jsonrpc":"2.0","id":1,"error":{"code":0,"message":"method \"tools/list\" is invalid during session initialization"}}

Expected response (JSON-RPC 2.0):

{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"method \"tools/list\" is invalid during session initialization"}}

The message text is correct; only the code is wrong.

Minimal program

srv := mcp.NewServer(&mcp.Implementation{Name: "t", Version: "0"}, nil)
c2s, serverIn := io.Pipe()
serverOut, cFromS := io.Pipe()
go func() { _ = srv.Run(ctx, &mcp.IOTransport{Reader: c2s, Writer: cFromS}) }()
fmt.Fprintln(serverIn, `{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}`)
// read the first line of serverOut

Notes

  • ping is correctly allowed before initialize.
  • 0 is especially awkward for clients: it is indistinguishable from "no code set" and is not one of the standard codes (-32700, -32600, -32601, -32602, -32603).

Impact

Consumers must wrap the transport to rewrite code: 0 to -32600 before forwarding to clients. We do exactly that in ai-rulez (internal/mcp GuardLifecycle) and would like to delete the workaround once the SDK answers with -32600 itself.

Activity

  1. guglielmo-san commented on Oct 10, 2026

    @guglielmo-san
    Contributor

    Hi @Goldziher, thank you for reporting this issue. Can you point where in the spec this behavior is requested?

  2. Goldziher commented on Oct 10, 2026

    @Goldziher
    Author

    2. Hi @Goldziher, thank you for reporting this issue. Can you point where in the spec this behavior is requested?

    Hi @guglielmo-san, thanks for the quick response.

    So this is not from the MCP spec, but rather from JSON-RPC. AI Research below:


    So, strictly

    • 0 is permitted (it is in the "application defined" remainder), but it is the zero value — indistinguishable from "no code set" — and not a registered code.
    • −32600 is the idiomatic choice but not a spec MUST: its JSON-RPC meaning is "the JSON sent is not a valid Request object", which a well-formed tools/list sent before initialize isn't, so it is a convention rather than a requirement.
    • The reserved band for a lifecycle/server-state error like "not initialized" is −32000..−32099 (implementation-defined server errors).

    For my use case it would be best to use either −32600 or code in the −32000..−32099 band. Currently I have a work around.

  3. talhayme commented on Oct 11, 2026

    @talhayme
    Contributor

    A note on where the 0 comes from, in case it helps decide this. toWireError in internal/jsonrpc2/messages.go only sets a code when the error wraps a WireError, so any plain error reaches the wire as code: 0. The pre-initialize refusal in mcp/server.go returns fmt.Errorf(...), which is one such case. The current value is pinned by mcp/testdata/conformance/server/lifecycle.txtar and TestStreamableServerTransport.

    On the spec question: I don't think either spec forbids 0. JSON-RPC 2.0 only requires an integer. The case for -32600 is that 0 carries no meaning for a client.

    I have a small patch that returns jsonrpc.CodeInvalidRequest at that one site and updates those tests; go test ./... passes. I left the toWireError default alone. Happy to open a PR if you want the change.

    AI disclosure: investigated and drafted with Claude Code; I reviewed it.

  4. guglielmo-san commented on Oct 12, 2026

    @guglielmo-san
    Contributor

    @talhayme this change would have to be gated with an MCPGODEBUG variable as it represent a behavioral change.
    @Goldziher did you try to use the new ProtocolVersion(20260728)? In that implementation all the error code should be explicit

  5. talhayme commented on Oct 12, 2026

    @talhayme
    Contributor

    Thanks. Gated behind MCPGODEBUG=nopreinitinvalidrequest=1 in #1393, with the entry in docs/mcpgodebug.md under 1.9.0 and a test for both settings.

    AI disclosure: drafted with Claude Code; I reviewed it.

  6. Goldziher commented on Oct 12, 2026

    @Goldziher
    Author

    thanks. i will cgeck.

  7. added a commit that references this issue on Oct 12, 2026
    b5d319b
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