Understanding JSON-RPC — The Wire Protocol Behind MCP, ACP and Multi-Agent Systems
Introduction
When I started building MCP (Model Context Protocol) servers and ACP (Agent Communication Protocol) agents, I kept running into the same underlying mechanism: JSON-RPC 2.0. At first it looked like just a thin wrapper around JSON — but once I understood the spec properly I realised why it became the standard wire protocol for agent-to-tool and agent-to-agent communication. It is stateless, transport-agnostic, trivially debuggable, and adds just enough structure to distinguish a call from a fire-and-forget notification.
This article covers the full JSON-RPC 2.0 specification, implements a working server and client in Python 3.12, then looks at how MCP, ACP, and multi-agent frameworks rely on it in practice.
What is JSON-RPC?
JSON-RPC is a stateless, lightweight Remote Procedure Call (RPC) protocol defined by the JSON-RPC Working Group. The current version is 2.0 (2010, revised 2013).
Its design constraints are intentionally minimal:
Transport-agnostic — works over stdio, HTTP, WebSockets, Unix sockets, or in-process
Format — plain JSON (RFC 4627), no binary encoding
Direction — a client sends a request, a server returns a response; both roles can be held by the same process simultaneously
Versions —
"jsonrpc": "2.0"is always present; 1.0 had no version field
That last point matters a lot for agent systems: an LLM host can be both a JSON-RPC server (answering tool calls from an orchestrator) and a JSON-RPC client (calling out to MCP servers) at the same time, over the same connection.
The Specification
Request Object
A request has four possible members:
jsonrpc
Yes
Always the string "2.0"
method
Yes
String name of the method to invoke
params
No
Array (positional) or Object (named)
id
No
String, Number, or Null — omitting it makes it a Notification
Response Object
The server must reply to every request (non-Notification). Exactly one of result or error is set — never both.
Notification
A request without id. The server must not send a response. Fire-and-forget:
MCP uses notifications heavily — for progress updates, resource change events, and log messages — so the client never blocks waiting for an acknowledgement.
Error Object
When something goes wrong the response carries an error instead of a result:
Reserved error codes:
-32700
Parse error
Invalid JSON received
-32600
Invalid Request
Not a valid Request object
-32601
Method not found
Method does not exist
-32602
Invalid params
Invalid method parameters
-32603
Internal error
Internal JSON-RPC error
-32000 to -32099
Server error
Application-defined server errors
Batch Requests
A client can send an Array of Request objects in one call. The server responds with a matching Array (notifications get no slot in the response array):
Python 3.12 Implementation
Data Models
Minimal JSON-RPC Server
Registering Methods
Client
JSON-RPC in Modern Agent Systems
MCP — Model Context Protocol
MCP is Anthropic's open protocol for connecting LLMs to tools, resources, and prompts. Its wire format is JSON-RPC 2.0 over stdio (for local servers) or HTTP + SSE (for remote servers).
Every interaction is a JSON-RPC call:
MCP also uses notifications (no id) for:
notifications/progress— long-running tool keeps the host informednotifications/resources/list_changed— resource list changed, host should re-fetchnotifications/message— server-side log messages
The initialisation handshake is itself a JSON-RPC exchange:
Python MCP Server with JSON-RPC
Here is a minimal Python MCP server that uses the same JSON-RPC primitives manually (without an SDK), showing the protocol directly:
ACP — Agent Communication Protocol
ACP (from IBM Research) uses JSON-RPC 2.0 over HTTP for agent-to-agent calls. Where MCP connects an LLM host to tools, ACP connects agents to other agents. The framing is the same but the semantics differ:
Progress in ACP comes as a stream: the HTTP response carries Content-Type: text/event-stream and each SSE event is a JSON-RPC notification:
The caller never blocks — it receives events as they arrive, exactly like the notification stream in stdio MCP.
Multi-Agent Communication
In a multi-agent system (e.g. LangGraph, CrewAI, or a custom orchestrator) JSON-RPC becomes the inter-agent wire format. A typical topology:
Python implementation of a simple orchestrator that coordinates agents via JSON-RPC:
Batch Calls for Efficiency
When an orchestrator needs to query capabilities from multiple agents in one shot, batch requests cut round-trips:
Key Design Observations
Why Stdio for Local Servers?
MCP servers launched by Claude Desktop or VS Code Copilot run as child processes. Stdio is:
Zero configuration — no port allocation, no firewall rules
Scoped to the session — process dies with the host
Synchronous framing — newline-delimited JSON; no HTTP overhead
For remote servers (deployed APIs), MCP uses HTTP POST with a JSON-RPC body plus SSE for streaming notifications. The JSON-RPC payload is identical — only the transport changes, proving the protocol's transport-agnostic design.
Correlation via id
idThe id field is the only mechanism that correlates a response to its request. In an async server handling 50 concurrent tool calls, each response must carry back the correct id. Missing it means the client cannot match the response to the caller — this is the most common JSON-RPC implementation bug.
Notifications as Event Bus
Because notifications require no response, they behave like a pub/sub channel embedded in the same connection. MCP uses them for:
notifications/progress— real-time progress of long-running toolsnotifications/resources/updated— push-invalidate cachenotifications/message— server log forwarding to the host UI
This keeps the single stdio channel busy without ever blocking a pending request.
Summary
jsonrpc: "2.0"
Always present; distinguishes from 1.0
Request with id
Expects a response
Notification (no id)
No response — fire and forget
result xor error
Exactly one of them in every response
Error codes -32700 to -32603
Reserved; don't reuse for app errors
Batch
Array of requests → Array of responses (notifications excluded)
MCP
JSON-RPC 2.0 over stdio or HTTP+SSE; tools/call, tools/list, notifications
ACP
JSON-RPC 2.0 over HTTP+SSE; runs/create, agent-to-agent
Multi-agent
Orchestrator sends JSON-RPC to each agent as a child process or remote server
JSON-RPC 2.0 is small enough to read in one sitting and solid enough that two of the most significant AI agent protocols of 2025 (MCP and ACP) independently chose it as their wire format. Understanding the spec at the message level — not just through an SDK — makes debugging agent systems significantly easier when things go wrong on the wire.
Last updated