json-rpcprotocolmcprpcapispec

JSON-RPC 2.0 Explained: The Protocol Behind MCP and the Blockchain

·8 min read·APIs

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:

json
{
  "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.

json
{ "jsonrpc": "2.0", "result": 19, "id": 1 }

An error replaces result with a structured error object:

json
{
  "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":

json
{ "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:

json
[
  { "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_getBalance as methods over HTTP.

Debugging JSON-RPC

Because everything is JSON objects, the JSONKit toolkit maps directly onto debugging:

  1. Format a captured request or batch — nested params are unreadable minified.
  2. Diff a working call against a failing one to spot the parameter that changed.
  3. Lock your method's params shape with a JSON Schema and validate it so -32602 Invalid params becomes a test failure, not a production error.

For where JSON-RPC shows up in practice, see MCP explained and Web3 JSON-RPC & the ABI.

Frequently asked questions

JSON-RPC 2.0 is a lightweight, transport-agnostic remote-procedure-call protocol that encodes calls as JSON objects. A request names a method and params and carries an id; the response echoes that id with either a result or a structured error.

REST models resources addressed by URLs and manipulated with HTTP verbs. JSON-RPC models *method calls* and doesn't care about the transport at all — the same message can travel over HTTP, WebSockets, or stdio. JSON-RPC is a better fit when you're calling named procedures rather than reading and writing resources.

A notification is a request without an id. The server processes it but must not send a response, making it ideal for fire-and-forget events like progress updates or log messages.

Codes from -32768 to -32000 are reserved by the spec: -32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal error. Codes outside that range are for your own application errors.

Because JSON-RPC standardizes only the message envelope, MCP can define tool discovery and invocation as methods while staying transport-agnostic — running over stdio for local servers and HTTP for remote ones with the same JSON shapes.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.