You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug: a request sent before initialize is answered with JSON-RPC error code 0 instead of -32600 #1367
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,"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()
gofunc() { _=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/mcpGuardLifecycle) and would like to delete the workaround once the SDK answers with -32600 itself.
MCP lifecycle only constrains the client: "The client SHOULD NOT send requests other than pings before the server has responded to the initialize request." It says nothing about the server's error code, and its "Error Handling" examples use -32602 for a version mismatch. (https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)
"The error codes from and including −32768 to −32000 are reserved for pre-defined errors."
−32600 Invalid Request — "The JSON sent is not a valid Request object."
−32000 to −32099 Server error — "Reserved for implementation-defined server-errors."
"The remainder of the space is available for application defined errors."
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.
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.
@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
Activity
guglielmo-san commentedon Oct 10, 2026
Hi @Goldziher, thank you for reporting this issue. Can you point where in the spec this behavior is requested?
Goldziher commentedon Oct 10, 2026
Hi @guglielmo-san, thanks for the quick response.
So this is not from the MCP spec, but rather from JSON-RPC. AI Research below:
initializerequest." It says nothing about the server's error code, and its "Error Handling" examples use-32602for a version mismatch. (https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)−32600 Invalid Request— "The JSON sent is not a valid Request object."−32000 to −32099 Server error— "Reserved for implementation-defined server-errors."So, strictly
0is permitted (it is in the "application defined" remainder), but it is the zero value — indistinguishable from "no code set" — and not a registered code.−32600is 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-formedtools/listsent beforeinitializeisn't, so it is a convention rather than a requirement.−32000..−32099(implementation-defined server errors).For my use case it would be best to use either
−32600or code in the−32000..−32099band. Currently I have a work around.talhayme commentedon Oct 11, 2026
A note on where the
0comes from, in case it helps decide this.toWireErrorininternal/jsonrpc2/messages.goonly sets a code when the error wraps aWireError, so any plainerrorreaches the wire ascode: 0. The pre-initialize refusal inmcp/server.goreturnsfmt.Errorf(...), which is one such case. The current value is pinned bymcp/testdata/conformance/server/lifecycle.txtarandTestStreamableServerTransport.On the spec question: I don't think either spec forbids
0. JSON-RPC 2.0 only requires an integer. The case for-32600is that0carries no meaning for a client.I have a small patch that returns
jsonrpc.CodeInvalidRequestat that one site and updates those tests;go test ./...passes. I left thetoWireErrordefault alone. Happy to open a PR if you want the change.AI disclosure: investigated and drafted with Claude Code; I reviewed it.
guglielmo-san commentedon Oct 12, 2026
@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
talhayme commentedon Oct 12, 2026
Thanks. Gated behind
MCPGODEBUG=nopreinitinvalidrequest=1in #1393, with the entry indocs/mcpgodebug.mdunder 1.9.0 and a test for both settings.AI disclosure: drafted with Claude Code; I reviewed it.
Goldziher commentedon Oct 12, 2026
thanks. i will cgeck.
mcp: answer a request sent before initialize with Invalid Request (mo…