Technology · Protocol

JSON-RPC

JSON-RPC is the transport under the Model Context Protocol surfaces I build and consume.

FAFO Core · FAFOHive · Agent SwarmThe envelope under the MCP tool surfaces
JSON-RPC 2.0one envelope
↓
Tool gatewaygenerated routes
Memory servicestateless requests
Agent Swarmbetween agents
  • 22custom error codes a client can branch on
  • 3MCP servers mapped by the read-only explorer

Routing generated from YAML

In FAFO Core and Function Forge I expected the number of tools to grow faster than anyone could hand-wire gateway routes. So I made JSON-RPC 2.0, the envelope of the Model Context Protocol, something the gateway serves from generated code. The gateway is an Axum server that speaks MCP over HTTP with the .well-known MCP configuration, protected-resource metadata and session headers.

At build time a generator turns a YAML trio per tool into handlers and routing, so a new function becomes callable without anyone editing gateway code, and the gateway and its catalog cannot drift apart.

One tool, three files
function
module
metadata
Gateway routes, generated at build time

the new tool takes its slot with no gateway code edited

Proportions are illustrative.

Function Forge is itself an MCP tool on this envelope. An agent calls create_function, and the generator emits a new crate and wires it into the gateway, the control plane and the completion service in one reversible operation. After a rebuild and a restart I authorise, the new function answers tools/call with execute, status and control actions. See Tools that build tools, under governance.

A typed error space

The gateway defines its own error codes, so a failure reaches the client as a typed JSON-RPC error. Clients and agents branch on the kind of failure instead of parsing a message string.

An error as text
Client
parses a sentence and guesses
An error with a code
code
retry
re-authorise
give up
The branches are illustrative.

A transport that survives restarts

In FAFOHive the first transport was a streaming server-sent-events session, and it broke every time a container restarted. Agents lost their connection mid-task. I replaced it with stateless JSON-RPC over HTTPS.

Streaming session
Stateless requests
one POST each, a timeout per tool, nothing to resume
The dark bar is a container restart.

I also built a read-only explorer that discovers the catalogs on my MCP servers through the standard listing calls. It handles JSON and SSE responses and pagination, never calls a tool and treats every tool description it reads as untrusted text, so I can inspect several hundred tools with no side effects.

Envelopes between agents

Agent Swarm agents had to message one another and start work on a second machine, and I wanted one envelope for both. Agent-to-agent messages are JSON-RPC 2.0 envelopes aligned with the Agent Client Protocol, carried over Redis streams with response correlation and a durable journal. The message types live in a crate with no I/O and no business logic, so producers and consumers compile against one definition.

Request, matched to its response
Notify, one way
Broadcast to every member
place_agent: start an agent on another host
Cross-host requests travel over a signed, deduplicated relay that rejects forged origins.

With the envelope out of the way, the attention goes to routing, identity and failure handling. The services use standard Rust and Python JSON-RPC stacks; the generated routing, the error space and the transport choices are mine.

I treat the wire envelope as plumbing that should be generated, typed and boring.