Roadmap / Theory

Model Context Protocol

JSON-RPC 2.0, stdio framing, MCP's stateless per-request metadata (revision 2026-07-28), discovery and a minimal tool server.

Every agent of section 16 needs tools, and every application used to wire up its tools in its own way. The Model Context Protocol (MCP), introduced by Anthropic in November 2024 as an open standard, defines one way for LLM (large language model) applications to talk to the programs that provide tools and data, in the same spirit as the Language Server Protocol did for programming languages in editors: one standard, instead of one connection per pair of applications.

This page builds MCP from the bottom: JSON-RPC 2.0 messages, the framing of the stdio transport, the per-request metadata of the current revision, and a minimal tool server. Each part has a small example of the messages involved.

The specification is versioned by date. This page follows the latest revision, 2026-07-28, and points out what changed from earlier ones.

Hosts, clients and servers

  • A host is the LLM application: a chat app, an IDE (integrated development environment), an agent.
  • A client is the connector inside the host that talks to one server.
  • A server is a program that offers capabilities: tools (functions the model can call), resources (data for the user or the model) and prompts (templated messages and workflows).

One host can connect to many servers, each through its own client, so a weather server, a database server and a file-system server can all serve the same chat.

JSON-RPC 2.0

All MCP messages follow JSON-RPC 2.0: a stateless, light-weight remote procedure call (RPC) protocol, that is, a standard way to ask another program to run a function and send back the result, with messages written in JSON (JavaScript Object Notation, a text format for structured data). There are three kinds:

  • A request has "jsonrpc": "2.0", an id, a method and optional params. MCP requires the id to be a string or an integer, never null.
  • A response has the same id and either a result or an error (an object with an integer code and a message).
  • A notification is a request without an id. The receiver must not answer it.

Example. A request, its response, a notification and an error response:

A request:
{"jsonrpc": "2.0",
 "id": 1,
 "method": "tools/list"}

Its response:
{"jsonrpc": "2.0",
 "id": 1,
 "result": {...}}

A notification (no id):
{"jsonrpc": "2.0",
 "method":
  "notifications/progress"}

An error response:
{"jsonrpc": "2.0",
 "id": 1,
 "error": {
  "code": -32601,
  "message":
   "Method not found"}}

The id is how a client matches a response to its request. The notification has no id: nobody answers it. A message with both a result and an error, or with "id": true, is invalid.

JSON-RPC reserves standard error codes: -32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal error. MCP adds its own in the range -32020 to -32099, such as -32022 for an unsupported protocol version. JSON-RPC batches (arrays of requests) were removed from MCP in revision 2025-06-18.

Transport: stdio

In the simplest transport the host launches the server as a subprocess and they exchange messages over its standard streams: the client writes to the server's stdin, the server writes to its stdout. Each message is one JSON object on one line: messages are delimited by newlines and must not contain embedded newlines, and nothing else may be written to stdout (logs go to stderr). Remote servers use the Streamable HTTP (Hypertext Transfer Protocol) transport instead, with the same messages.

Reading therefore means buffering bytes until a newline arrives, since one read may deliver half a message or several.

Example. Writing a message whose text contains a line break: JSON writes the break as the two characters \n, so the line itself never contains a real newline: {"a":"line1\nline2","id":1} followed by the line terminator.

Reading: the first read delivers {"id":1} + newline + {"id" (a message and half of the next). The reader returns the first message and keeps {"id" in its buffer. The second read delivers :2} + newline + {"id":3} + newline: the buffer plus the new text now holds two complete lines, and the reader returns both messages. A line that is not valid JSON is reported as a parse error instead of crashing the reader.

A stateless protocol

Up to revision 2025-11-25, a connection began with an initialize handshake: the client sent its protocol version and capabilities, the server answered with its own, and the client confirmed with a notifications/initialized notification. Revision 2026-07-28 removed the handshake and made MCP stateless: every request carries what the server needs in its params._meta field, under keys that all start with io.modelcontextprotocol/:

  • protocolVersion (required), for example "2026-07-28";
  • clientCapabilities (required);
  • clientInfo (recommended): the client's name and version.

A request missing a required field is malformed and gets error -32602. A request asking for a version the server does not support gets error -32022, whose data lists the supported versions and the requested one, so the client can retry with a common version. Servers must implement server/discover, which returns their supported versions, capabilities and identity in one call. Every result carries a resultType, "complete" for an ordinary result.

Example. A client asks for the tools of a server that supports only 2026-07-28. In the _meta of its request it puts the three keys (with the prefix io.modelcontextprotocol/):

  • A request with protocolVersion "2026-07-28" and clientCapabilities {}: the request may proceed.
  • A request with only protocolVersion, without clientCapabilities: error -32602, with the message "Missing required _meta field" followed by the full name of the missing key.
  • A request with protocolVersion "2025-01-01": error -32022, "Unsupported protocol version", whose data lists the supported versions ("2026-07-28") and the requested one ("2025-01-01").

Every request is checked on its own: the server does not remember anything about the client between requests.

Tools

A server that offers tools declares a tools capability and answers two methods:

  • tools/list returns the tools, each with a name, a description and an inputSchema: a JSON Schema (2020-12 by default) of its arguments (a description of the fields the arguments must have, and their types). The result also carries caching hints, ttlMs and cacheScope.
  • tools/call takes params.name and params.arguments and returns content, a list of items such as {"type": "text", "text": "..."}, and isError.

The specification separates two kinds of failure. Protocol errors (an unknown tool, a malformed request) are JSON-RPC errors, for example -32602. Tool execution errors (invalid input values, a failing API (application programming interface)) are ordinary results with isError: true and a text explaining what went wrong, because that text is what lets the model correct itself and try again.

Example. A server with a tool add (two required numbers a and b) and a tool boom that always fails.

  • tools/list: the tools add and boom, each with name, description and schema, plus ttlMs 300000 and cacheScope "public".
  • tools/call add with a=2, b=3: content with the text 5, isError false.
  • tools/call boom: content with the text of the exception, city not found, and isError true. The tool failed; the request did not.
  • tools/call nope: error -32602, "Unknown tool: nope". A protocol error.
  • A notification: no answer.
  • resources/list (not implemented): error -32601, "Method not found".

The input of a tool is checked against its schema. With a schema that requires a text location, allows an integer days, a units of "c" or "f", and forbids other fields, the arguments {"days": 2.5, "units": "k", "x": 1} give four problems, in this order: the missing location, days not an integer, units not one of the allowed values, and the unexpected x. Returned as a tool execution error, they tell the model exactly what to fix.

Security

Tools are arbitrary code execution. The specification asks hosts to obtain the user's explicit consent before invoking a tool or exposing user data to a server, and to treat tool descriptions as untrusted unless the server is trusted: a malicious server can write a description that is really a prompt injection (section 18).

References

Practice

0/5