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 Grouparrow-up-right. 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:

Field
Required
Description

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:

Code
Name
Meaning

-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 informed

  • notifications/resources/list_changed — resource list changed, host should re-fetch

  • notifications/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

The 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 tools

  • notifications/resources/updated — push-invalidate cache

  • notifications/message — server log forwarding to the host UI

This keeps the single stdio channel busy without ever blocking a pending request.


Summary

Concept
Key point

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