JSON-RPC 2.0
JSON-RPC 2.0 is a small convention for calling a method by name with JSON arguments and getting a JSON result back. It defines four message shapes and a set of reserved error codes, and nothing else. Both of Connect's agent endpoints — /mcp and /a2a — speak it.
Four shapes, and that is the whole protocol#
- Request —
jsonrpc,method, optionalparams, and anidthe caller chooses. Theidis how a reply is matched to its question. - Response (success) —
jsonrpc, the sameid, andresult. - Response (error) —
jsonrpc, the sameid, anderrorwith acode, amessageand optionaldata. - Notification — a request with no
id. No reply is sent, ever. A caller that waits for one waits forever.
params may be an object or an array in the specification. Both of Connect's endpoints require an object and answer -32602 for anything else, because named parameters survive a schema change and positional ones do not.
What JSON-RPC deliberately leaves out is as important as what it defines: no transport, no authentication, no discovery, no schema for params, no versioning of methods. Everything a real protocol needs is layered on top, which is why MCP and A2A can share this envelope and still be different protocols.
The reserved error codes#
| Code | Name | When you see it |
|---|---|---|
| -32700 | Parse error | The body was not valid JSON |
| -32600 | Invalid request | Valid JSON, wrong shape — a batch where one message was expected |
| -32601 | Method not found | The method name is not implemented |
| -32602 | Invalid params | The method exists; the arguments are wrong |
| -32603 | Internal error | The server failed for its own reasons |
| -32000 to -32099 | Server-defined | Reserved for the layer above: MCP's -32020, A2A's -32004 |
The server-defined band is where the protocols above put their own meanings. MCP uses -32020 when the MCP-Protocol-Version, Mcp-Method or Mcp-Name header disagrees with the body. A2A uses -32004 for an operation it does not support — a skill that is not on the card, streaming when the card declares none, or the authenticated extended card that does not exist.
One distinction is worth internalising because it decides how a client should react. A JSON-RPC error means the *call* did not happen. A successful result that reports a problem — MCP's isError: true — means the call happened and the answer is bad news. The first is retried or fixed; the second is read.
Does Connect use JSON-RPC?#
Yes, on both agent endpoints, and nowhere else. POST /mcp and POST /a2a are JSON-RPC. The public HTTP API is ordinary REST over JSON and is described by OpenAPI — it is not JSON-RPC and does not use these codes.
Two deliberate narrowings apply to both endpoints. Batching is refused: send one message per request, and a JSON array comes back as -32600 with 400. And params must be an object.
Connect also maps codes onto HTTP status rather than answering 200 for everything, because the transport in front of an endpoint — a proxy, a gateway, a metrics collector — can only see the status line.
| Situation | HTTP | JSON-RPC code |
|---|---|---|
| Malformed JSON | 400 | -32700 |
| A batch, or the wrong top-level shape | 400 | -32600 |
| Header disagrees with the body (MCP) | 400 | -32020 |
| Unknown method | 404 | -32601 |
| Unknown tool, resource or prompt | 200 | -32601 |
| Bad arguments | 200 | -32602 |
| Body over the size ceiling | 413 | -32600 |
| Rate limit reached | 429 | -32603 |
| A notification | 202 | no body at all |
The split on -32601 is intentional. An unknown *method* is a routing failure a proxy should be able to see, so it gets 404. An unknown tool name is an application-level answer to a well-formed call, so it stays 200 and the model reads the message.
Writing a client that does not surprise you#
- Generate a fresh
idper request and match replies by it. Reusing one across concurrent calls makes two answers indistinguishable. - Treat a missing
idas intentional. If you send a notification, do not wait; if you receive202, that is the whole answer. - Read
error.datawhen it is present — Connect uses it to list the supported protocol versions when a client asks for one that is not. - Do not parse
error.messagefor meaning. The code is the contract; the message is for a human reading a log. - Retry on
429with a backoff, and on-32603at most once. Never retry-32602: the arguments will be just as wrong the second time.
Questions#
Why does Connect refuse JSON-RPC batches when the specification allows them?
Because a batch makes every limit ambiguous — rate, body size, partial failure — for a saving that matters only to chatty clients. One message per request keeps error handling honest, and neither MCP nor A2A needs batching to work.
Is the public API JSON-RPC too?
No. The public API is REST over JSON with ordinary HTTP status codes and its own error shape. JSON-RPC here is confined to the two agent endpoints.
What does a code in the -32000 range mean?
It is defined by the protocol above JSON-RPC, not by JSON-RPC. Look it up in MCP's or A2A's specification — here, -32020 is an MCP header mismatch and -32004 is an A2A unsupported operation.