MCP under the hood: JSON-RPC, transports, and capability negotiation
The Model Context Protocol on the wire: the JSON-RPC 2.0 messages, the initialize handshake and capability negotiation, tools, resources and prompts, stdio vs Streamable HTTP, what every tool schema costs in context, and the security model you have to bring yourself.
The Model Context Protocol (MCP) is how most agent harnesses now plug in tools they didn’t write. amber speaks it too. Under the marketing it’s a small, readable protocol: JSON-RPC 2.0 over a pipe or HTTP, with a handshake that says who can do what. Here’s what actually goes over the wire.
JSON-RPC 2.0
Every MCP message is a JSON-RPC 2.0 object, one of three kinds:
- a request, with an
id, amethodandparams, which expects a response; - a response, with the same
idand either aresultor anerror; - a notification, with a
methodand noid, which expects no response.
Either side can send requests. The server can ask the client for things too.
The handshake
A session starts with the client announcing its protocol version and capabilities:
{"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2025-06-18",
"capabilities": {"roots": {"listChanged": true}, "sampling": {}},
"clientInfo": {"name": "my-harness", "version": "1.0.0"}}}
The server answers with its own version and capabilities, such as "tools": {"listChanged": true}. The client then sends the notifications/initialized notification, and normal traffic begins.
Capabilities are the contract. A feature not declared in the handshake must not be used, so a client that never offered sampling will never be asked to run a completion for the server.
What servers offer
- Tools: functions the model can call.
tools/listreturns each tool’s name, description andinputSchema(JSON Schema).tools/callruns one, and the result is acontentarray of text, images or embedded resources, withisErrorfor failures. Since the 2025-06-18 revision a tool can also declare anoutputSchemaand returnstructuredContent. - Resources: data the client can read, identified by URI: files, records, documents. Resources are listed and read (
resources/list,resources/read); they’re not executed. - Prompts: reusable prompt templates the user can invoke (
prompts/list,prompts/get).
And what clients can offer servers, if declared: roots, the filesystem or URI boundaries the server may work within; sampling, which lets the server ask the client’s model for a completion; and elicitation, which lets the server ask the user for input.
When the server’s tool set changes, it sends notifications/tools/list_changed, and the client re-lists.
Transports
- stdio: the client launches the server as a subprocess, and messages flow as newline-delimited JSON over stdin and stdout. It’s simple, local and the most common way to run a server.
- Streamable HTTP: for remote servers, one HTTP endpoint. The client POSTs messages, and the server can answer directly or open a Server-Sent Events stream for messages that follow. An
Mcp-Session-Idheader ties requests into one session. It replaced the earlier HTTP-plus-SSE transport in the 2025-03-26 revision, which also specified OAuth 2.1 for authorisation.
What it costs in context
Every tool’s name, description and schema has to be in the model’s context for the model to use it. Fifty tools at about 200 tokens each is 10K tokens on every request, before the conversation starts. MCP makes it easy to connect many servers and blow that budget without noticing. The fixes are the ones from the context management note:
- expose only the tools a task needs;
- keep descriptions short and specific;
- keep the tool block stable so the prefix cache can reuse it.
The security model you bring yourself
MCP defines the plumbing, not the trust. Three things to treat as hostile until shown otherwise:
- Tool descriptions are prompt input. A malicious or compromised server can put instructions in a description, and the model reads it as context. Review the servers you connect like dependencies.
- Tool results are untrusted data. A fetched web page or document can carry prompt injection. Don’t let results trigger destructive actions without a check.
- Calls have side effects. Writing files, sending messages or running commands should be confirmed or tightly scoped. Roots exist to bound what a server can reach; use them.
Rules I use
- Prefer stdio for local tools, and Streamable HTTP with OAuth for remote ones.
- Use structured output (
outputSchema) where a tool returns data, so results parse without guesswork. - Budget the tool list like any other part of the context.
- Gate side effects in the harness, not in the server’s goodwill.
Sources: the Model Context Protocol specification, revisions 2024-11-05, 2025-03-26 and 2025-06-18, and the JSON-RPC 2.0 specification.