JSON-RPC & the MCP Wire Protocol

How MCP uses JSON-RPC 2.0 under the hood — tools/list, tools/call, and how to poke your server directly to see what's happening.

March 7, 20263 min read2 / 7

MCP doesn't use REST — that surprised me at first. It uses JSON-RPC 2.0 — a lightweight protocol for calling named methods on a remote process.

The difference:

  • REST is resource-oriented: PATCH /issues/123, GET /users
  • RPC is function-oriented: "call this method with these arguments"

JSON-RPC 2.0 is simple — every message is a JSON object with a method name, optional params, and an id for matching responses.


The MCP Handshake

When Claude Desktop connects to your MCP server, it goes through a standard sequence:

  1. initialize — client and server exchange versions and capabilities
  2. tools/list — client asks "what tools do you have?"
  3. Server returns the full catalogue: names, descriptions, input schemas
  4. From here, the LLM can call tools/call anytime the user's request matches a tool

The tools/list call is the critical handshake. Claude Desktop calls it at startup, and the result is what the LLM sees when deciding which tool to use.

JSON-RPC 2.0 — MCP Handshake & Tool Call Flow ExpandJSON-RPC 2.0 — MCP Handshake & Tool Call Flow


What tools/list Returns

A typical response looks like:

JSON
{ "jsonrpc": "2.0", "id": 1, "result": { "tools": [ { "name": "add", "title": "Add Numbers", "description": "Add two numbers together.", "inputSchema": { "type": "object", "properties": { "a": { "type": "number", "description": "First number" }, "b": { "type": "number", "description": "Second number" } }, "required": ["a", "b"] } } ] } }

The MCP SDK generates this from your Zod schema automatically.


Calling a Tool with tools/call

JSON
{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "add", "arguments": { "a": 2, "b": 3 } } }

The arguments field contains the actual input values matching your Zod schema. The server validates them, runs your handler, and returns:

JSON
{ "jsonrpc": "2.0", "id": 2, "result": { "content": [{ "type": "text", "text": "5" }] } }

Testing Your Server Directly

I find this invaluable during development: you don't need Claude Desktop to test your server. Pipe JSON-RPC messages directly via stdin:

Bash
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node dist/index.js

Or use the MCP Inspector — a GUI tool that connects to any MCP server and lets you browse tools, call them manually, and see the raw protocol messages.

This is useful during development before you wire up a full client. You can verify your tool names, schemas, and return values without going through Claude Desktop's restart cycle.


The Token Cost of Tools

Here's something that bit me early: every tool you expose consumes input tokens — its name, description, and full input schema all get sent to the LLM with every request. Claude can start to degrade with more than ~40 tools in context.

Practical implication: don't expose your entire server's surface area at once. Enable only the tools relevant to the current task, and disable the rest. Some clients (like VS Code Agent Mode) let you toggle individual tools per session.

Further Reading

Practice what you just read.

Explore the JSON-RPC Protocol
1 exercise

Enjoyed this? Get more like it.

Deep dives on system design, React, web development, and personal finance — straight to your inbox. Free, always.