The quiet protocol that's suddenly everywhere
REST gets the attention, but a surprising amount of modern infrastructure runs on JSON-RPC 2.0 — a deliberately tiny remote-procedure-call protocol encoded in JSON. It is the wire format of the Model Context Protocol that AI agents use to call tools, the Language Server Protocol behind your editor's autocomplete, and the Ethereum and Web3 node APIs. When something needs "call a named method with parameters, get a result" without the ceremony of REST resources, JSON-RPC is the usual answer.
The entire spec fits on a page. Here's the whole thing.
A request is a method call
A JSON-RPC request is a JSON object with four members:
{
"jsonrpc": "2.0",
"method": "subtract",
"params": { "minuend": 42, "subtrahend": 23 },
"id": 1
}- jsonrpc — always the string
"2.0". It pins the protocol version. - method — the name of the procedure to invoke.
- params — arguments, either by-name (an object, as above) or by-position (an array). Optional if the method takes none.
- id — a client-chosen identifier the server must echo back, so responses can be matched to requests on a multiplexed connection.
Notice what's *not* here: no URL path, no HTTP verb, no status code. JSON-RPC is transport-agnostic — the same objects travel over HTTP, WebSockets, stdio, or a raw TCP socket. That is exactly why MCP can run a server over stdio locally and over HTTP remotely with no change to the message shape.
A response echoes the id
Success and failure share one rule: the response carries the same id as its request, and contains either result or error — never both.
{ "jsonrpc": "2.0", "result": 19, "id": 1 }An error replaces result with a structured error object:
{
"jsonrpc": "2.0",
"error": { "code": -32601, "message": "Method not found" },
"id": 1
}The error.code is an integer, and the spec reserves a range for protocol-level failures:
- -32700 Parse error — invalid JSON was received.
- -32600 Invalid Request — the JSON isn't a valid Request object.
- -32601 Method not found.
- -32602 Invalid params.
- -32603 Internal error.
- -32000 to -32099 reserved for implementation-defined server errors.
Application errors use codes outside the reserved range, and may attach an optional data field with details.
Notifications: fire-and-forget
A request with no `id` is a notification. The server must not reply to it. This is how you send events you don't need a result for — progress updates, log lines, "the file changed":
{ "jsonrpc": "2.0", "method": "log", "params": { "level": "info", "msg": "started" } }MCP uses notifications heavily for things like progress and cancellation, precisely because they need no round-trip.
Batching: many calls, one payload
A client can send an array of request objects to invoke several methods at once. The server returns an array of the corresponding responses (order not guaranteed — match by id), omitting any responses to notifications:
[
{ "jsonrpc": "2.0", "method": "sum", "params": [1, 2, 4], "id": "a" },
{ "jsonrpc": "2.0", "method": "notify_hello", "params": [7] },
{ "jsonrpc": "2.0", "method": "get_data", "id": "b" }
]Batching cuts round-trips on chatty connections — useful when a client needs to prime several tool results before acting.
Why agents and editors chose it
JSON-RPC's appeal is that it standardizes the *envelope* and nothing else. It doesn't dictate your methods, your transport, or your auth — it only says how a call, a result, and an error are framed. That minimalism is why it keeps getting picked for new protocols:
- MCP wraps tool discovery and invocation as JSON-RPC methods, so any language with a JSON library can host or call an MCP server. See build an MCP server.
- Language Server Protocol uses it so one language server can serve every editor.
- Ethereum JSON-RPC exposes node operations like
eth_getBalanceas methods over HTTP.
Debugging JSON-RPC
Because everything is JSON objects, the JSONKit toolkit maps directly onto debugging:
- Format a captured request or batch — nested
paramsare unreadable minified. - Diff a working call against a failing one to spot the parameter that changed.
- Lock your method's
paramsshape with a JSON Schema and validate it so-32602 Invalid paramsbecomes a test failure, not a production error.
For where JSON-RPC shows up in practice, see MCP explained and Web3 JSON-RPC & the ABI.