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.
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 aresultor anerrorobject, never both. - Notification — no
idfield at all, fire-and-forget. MCP uses these for things likenotifications/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!