Getting Started2026-07-204 min read

JSON-RPC in MCP: Message Format Deep Dive

MCP doesn't invent its own wire protocol — it's built directly on JSON-RPC 2.0. Here's exactly which JSON-RPC message types MCP uses, the real method names, and where MCP's own semantics layer on top.

Code sharing sectionMessage format examples

MCP doesn't invent a new wire protocol. It's built directly on top of JSON-RPC 2.0, a lightweight, transport-agnostic remote procedure call spec that predates MCP by well over a decade. Understanding where JSON-RPC ends and MCP-specific semantics begin makes the whole protocol click into place.

The Three JSON-RPC Message Types MCP Uses

JSON-RPC 2.0 defines three message shapes, and MCP uses all three:

  • Request — has an id, expects a response. Used for anything the caller needs an answer to: tools/call, resources/read, initialize.
  • Response — matches a request's id, contains either a result or an error object, never both.
  • Notification — no id field at all, fire-and-forget. MCP uses these for things like notifications/tools/list_changed, telling a client the available tool set just changed without expecting anything back.

A Real Request/Response Pair

// Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": { "query": "MCP servers" }
  }
}

// Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{ "type": "text", "text": "Found 3 results..." }],
    "isError": false
  }
}

Note the matching id: 1 on both sides — that's pure JSON-RPC 2.0, not an MCP invention. What's MCP-specific is the shape of params for a tools/call method, and the content/isError shape of the result.

The Real MCP Method Namespace

MCP organizes its methods into clear namespaces, each corresponding to one of the protocol's core primitives:

  • Lifecycle: initialize, notifications/initialized
  • Tools: tools/list, tools/call, notifications/tools/list_changed
  • Resources: resources/list, resources/read, resources/subscribe, notifications/resources/updated
  • Prompts: prompts/list, prompts/get
  • Sampling (server asking the client's LLM to generate something): sampling/createMessage

Every one of these is a plain JSON-RPC method name — the string after "method": — and any generic JSON-RPC 2.0 parser can correctly parse the message envelope. What a generic parser can't do is understand what tools/call's specific params shape means, or what to do with the result — that semantic layer is MCP's actual contribution on top of the wire format.

Errors Follow the JSON-RPC Error Object Shape

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": { "details": "missing required argument: query" }
  }
}

Codes like -32602 (Invalid params) and -32601 (Method not found) come straight from the JSON-RPC 2.0 spec's reserved error code range, not from MCP. MCP layers its own tool-level error signaling on top via the isError: true field inside a successful result — a distinction worth internalizing: a JSON-RPC-level error means something went wrong with the protocol call itself (bad params, unknown method), while a tool-level error (isError: true in the result) means the call succeeded at the protocol layer but the tool itself failed (e.g., a database query that errored).

Why Building on JSON-RPC Was the Right Call

Reusing an existing, boring, well-understood RPC format instead of inventing a new one is a deliberate, sensible design choice: it means MCP inherits two decades of tooling, debugging familiarity, and transport flexibility (JSON-RPC doesn't care if it's carried over stdio, HTTP, or WebSockets) for free, and lets MCP's actual innovation focus entirely on the primitives — tools, resources, prompts, sampling — layered on top, rather than reinventing message framing.

Join the Discussion

Code Snippets (0)

No code snippets shared yet. Be the first to contribute!