Every time Claude calls a tool through an MCP server, it sends a JSON-RPC message. JSON-RPC 2.0 is the wire protocol that powers the entire Model Context Protocol — every tool call, every resource read, every initialization handshake. If you want to build MCP servers, debug them, or just understand what's actually happening under the hood, you need to understand JSON-RPC. This guide walks through the format, the fields, the error codes, and the real messages Claude sends — no prior protocol knowledge required.
What Is JSON-RPC?
JSON-RPC is a remote procedure call (RPC) protocol encoded in JSON. That's a mouthful, so here's what it means in practice: one program wants to call a function on another program, possibly running in a different process or on a different machine. JSON-RPC defines a standard way to format that call as a JSON object and format the result as another JSON object.
Version 2.0 — published in 2010 — is the current stable spec and the one MCP mandates. It's deliberately minimal. There's no authentication layer, no built-in transport, no discovery mechanism. It's just a message format. That's a feature: JSON-RPC works over stdio, over HTTP, over WebSockets, or any other transport you can shove bytes through.
MCP chose JSON-RPC specifically because it maps cleanly to the tool-call model. Claude wants to "call a function named read_file with argument path=/home/user/doc.txt." That's exactly what JSON-RPC is designed to express. Compare this to REST, which is organized around HTTP verbs and resource URLs — a fundamentally different mental model that doesn't fit tool invocation as naturally.
Anatomy of a JSON-RPC Request
A JSON-RPC request is a JSON object with exactly four fields:
jsonrpc— always the string"2.0". If this field is missing or wrong, the message is invalid.id— a unique identifier for this request. Can be a string, integer, or null. The response will echo this ID so you can match it to the original request.method— the name of the function to call. In MCP this is something like"tools/call"or"initialize".params— an object (or array) of arguments for the method. Optional — some methods take no parameters.
Here's what a real tools/call request looks like when Claude asks an MCP server to read a file:
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": {
"path": "/home/user/project/README.md"
}
}
}
tools/call JSON-RPC request. The id is 42 — the client will use this to match the response. The params.name identifies which tool to run; params.arguments carries the tool's input.The id is critical. Because MCP can pipeline multiple requests — sending the next one before the previous response arrives — the ID is how the client knows which response belongs to which request. If your server returns responses out of order (which is allowed), the client sorts them out by ID.
Anatomy of a JSON-RPC Response
A successful response has three fields:
jsonrpc— again, always"2.0".id— the same value that was in the request. This is how the client matches the response to the original call.result— the return value. Its shape depends on the method. Fortools/call, this is an object with acontentarray.
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [
{
"type": "text",
"text": "# My Project\n\nThis is the README for my project..."
}
],
"isError": false
}
}
tools/call response. The id matches the request (42). The result.content array holds the tool's output — in this case, plain text.A response object must have either result or error — never both, never neither. If the method succeeded, you get result. If it failed, you get error.
Error Objects and Standard Error Codes
When something goes wrong, the response replaces result with an error object containing three fields:
code— a numeric error code. Negative integers in a defined range are reserved by the JSON-RPC spec; others are application-defined.message— a short human-readable string describing the error.data— optional. Any additional context: stack traces, validation details, etc.
{
"jsonrpc": "2.0",
"id": 42,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"details": "Required field 'path' is missing from arguments"
}
}
}
-32602 means the client sent invalid parameters — in this case, a missing required field. The data field adds human-readable detail.The JSON-RPC 2.0 spec reserves five standard error codes that every implementation should respect:
-32700— Parse error. The server received JSON it couldn't parse. Your request was malformed.-32600— Invalid Request. The JSON was valid but doesn't conform to the JSON-RPC spec (missingjsonrpcfield, wrong type onid, etc.).-32601— Method not found. The method name in the request doesn't exist on this server.-32602— Invalid params. The method exists but the parameters are wrong — missing fields, wrong types, out-of-range values.-32603— Internal error. The server tried to execute the method but something went wrong internally.
MCP implementations can define their own error codes in the range -32099 to -32000 for server-specific errors. When you see an unfamiliar code in that range, check the specific server's documentation.
Notifications: Fire and Forget
Not every message needs a response. JSON-RPC supports notifications — messages that look like requests but have no id field. The receiver must process the notification but must not send a response.
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "task-001",
"progress": 45,
"total": 100,
"message": "Processing files..."
}
}
id field. The server sends this to report progress on a long-running operation. The client updates its UI but doesn't send any response.In MCP, notifications are used heavily for progress reporting on long operations, for servers to signal capability changes, and for the client to acknowledge certain events. If you're seeing messages from your MCP server that don't get responses and you're wondering why — they're notifications. That's intentional.
How Claude Actually Uses JSON-RPC
When you connect an MCP server to Claude Desktop, a specific sequence of JSON-RPC messages happens before Claude can use any tools. Understanding this flow helps enormously when things break.
The handshake goes like this:
- The host starts your MCP server process and opens a connection.
- The client sends an
initializerequest, advertising which protocol version and capabilities it supports. - Your server responds with its own capabilities — which tools it has, whether it supports resources, etc.
- The client sends an
initializednotification (no response expected). - Now the session is live. Claude can start making tool calls.
The MCP methods Claude's client actually sends include:
initialize— opens the session, negotiates capabilities.tools/list— fetches the list of available tools and their schemas.tools/call— invokes a specific tool with arguments.resources/list— fetches available resources (if the server supports them).resources/read— reads the content of a specific resource.prompts/list— fetches available prompt templates.prompts/get— retrieves a specific prompt template with arguments filled in.
Each of these methods has a defined request shape and a defined response shape. Understanding the three MCP primitives — tools, resources, and prompts — tells you what each group of methods is for. Understanding MCP tool schemas tells you how the tools/list response is structured.
Why This Matters When Things Break
Most MCP server problems surface at the JSON-RPC layer. Here's a quick diagnostic map:
- Server not responding at all — the process probably crashed before it could complete the
initializehandshake. Check your server's stderr output. - Getting
-32601Method not found — you're calling a method the server hasn't implemented. Check if it's a version mismatch (see the MCP version history for when certain methods were added). - Getting
-32602Invalid params — your tool arguments don't match the schema. Look at the tool definition fromtools/listand validate your input against the JSON Schema in theinputSchemafield. - Getting
-32603Internal error — the server received your request fine but crashed while running it. Check server logs. - Responses arriving out of order or with wrong IDs — the server is not properly echoing the request
idin its responses. This is a server bug.
If you have access to the raw transport (for stdio-based servers, you can often pipe stderr to a log file), the JSON-RPC messages are your best debugging tool. Every field tells you something.
Batching and What MCP Does Not Use
JSON-RPC 2.0 defines a batching mechanism where you can send an array of requests and get back an array of responses. MCP explicitly does not use this feature. Every message in MCP is a single JSON object — no arrays at the top level (unless they're inside params or result). If you see batching code in a JSON-RPC library you're using, you can safely ignore it for MCP purposes.
MCP also doesn't use positional parameters (where params is a JSON array instead of an object). MCP always uses named parameters — params is always a JSON object with named keys. Some JSON-RPC libraries default to positional parameters, which will break MCP communication if you're not careful.
Frequently Asked Questions
MCP uses JSON-RPC 2.0, the current stable version of the spec. Every message must include the field "jsonrpc": "2.0" — if it doesn't, the message is invalid and the receiver should respond with a parse error.
A request includes an "id" field and expects a response. A notification omits the "id" field entirely — it's fire-and-forget. The receiver must not send a response to a notification. In MCP, notifications are used for progress updates and server-side events.
Start by checking the error code in the response. -32700 means your JSON is malformed. -32601 means the method name doesn't exist on the server. -32602 means the params object is wrong — check your input schema. Enable verbose logging in your MCP client to see the raw JSON-RPC messages going over the wire.
MCP uses JSON-RPC 2.0, not REST. REST is a design style built around HTTP verbs and resource URLs. JSON-RPC is a lightweight remote procedure call protocol — you call named methods with parameters and get back results. MCP chose JSON-RPC because it maps cleanly to tool calls (call this function, get this result) and works over any transport, not just HTTP.