kb://library/mcp2026-06-16

Model Context Protocol (MCP) — the "USB-C for AI" tool & context protocol

mcpprotocoljson-rpcagentstoolsinteroperabilitya2alinux-foundationinfrastructure

Model Context Protocol (MCP)

What it is (intuition first)

An LLM is a brain in a jar. It is brilliant at producing text but, by itself, it cannot read your files, query your database, hit an API, or remember anything between runs. To do useful work it needs hands — tools and data that live outside the model.

Before MCP, every one of those hands was wired by hand. If you had M AI applications (Claude Desktop, your IDE, a chat agent, an internal copilot) and N systems to connect them to (GitHub, Postgres, Slack, Google Drive, your CRM), you faced the classic M×N integration problem: up to M×N bespoke, brittle, one-off connectors, each re-inventing auth, schema, and error handling. Add one new tool and you re-implement it for every app.

The Model Context Protocol (MCP) is the fix, and the canonical pitch is "USB-C for AI." A laptop doesn't need a different physical port for every brand of monitor, drive, or dock — it exposes one standard port and any compliant peripheral plugs in. MCP is that standard port for AI: build a tool/data source once as an MCP server, and any MCP-capable application (client/host) can plug into it. M×N collapses to M+N — M clients and N servers, each implemented against one shared contract.

The spec itself says MCP "takes some inspiration from the Language Server Protocol (LSP)," and that's the cleanest mental model for an engineer. LSP let one language implementation (say, the Rust analyzer) light up autocomplete/go-to-definition across every editor that speaks LSP, instead of N editors × M languages of integration glue. MCP does the same trick for "context and tools" instead of "language features."

Introduced by Anthropic and open-sourced on 2024-11-25 (authors David Soria Parra and Justin Spahr-Summers), MCP went from a developer-preview novelty to de-facto industry infrastructure in about a year, and in December 2025 was donated to the Linux Foundation under the new Agentic AI Foundation. By mid-2026 the public registries track on the order of 10,000+ MCP servers (see caveats — the "18k+"/"97M installs" figures floating around mix several definitions).

If you have read [[agent-memory-systems]], think of MCP as the plumbing that lets the agent's brain reach memory, tools, and the outside world over one wire — MCP is transport and contract, not cognition.

Why it matters

  1. It removes per-integration glue as a category of work. A team ships one MCP server for their product; every MCP-speaking agent in the world can now use it. This is the same network effect that made HTTP, USB, and LSP win — value compounds with adoption.
  1. It decouples model from tools. The host app and the model can be swapped without touching the servers, and a server can be upgraded without touching clients, because the contract (JSON-RPC + capability negotiation) is stable and versioned. This is why MCP showed up in agentic coding tools (Zed, Replit, Sourcegraph, Codeium were early adopters) almost immediately — it let them expose context to whatever model the user chose.
  1. It is genuinely vendor-neutral now. MCP was Anthropic's, but A2A was Google's, and the fragmentation risk was real. The 2025 donation put MCP — alongside goose (Block) and AGENTS.md (OpenAI) — under a neutral directed fund with Anthropic, Block, OpenAI, Google, Microsoft, AWS, Cloudflare, and Bloomberg behind it. A protocol owned by one frontier lab is a strategic liability for everyone else; a protocol owned by a foundation is safe to build a company on. That governance move is why MCP is safe to bet infrastructure on.
  1. It standardized the agent↔tool boundary, which is one of the two boundaries any serious agent stack needs. The other is agent↔agent (A2A). MCP is the tool layer of the modern agent stack — see "How it connects to OpenAlice" for how we use both.

How it works (real mechanics)

The three roles

The spec defines exactly three:

  • Host — the LLM application that the user runs (Claude Desktop, an IDE, a chat agent, OpenAlice). It owns the model and the user-consent surface.
  • Client — a connector inside the host, one per server. The host runs N clients to talk to N servers. (People sloppily say "the host is the client"; the spec separates them.)
  • Server — an external process exposing context and capabilities (your GitHub server, your Postgres server).
        ┌──────────────────── HOST (LLM app) ────────────────────┐
        │   model + consent UI                                    │
        │   ┌──────────┐   ┌──────────┐   ┌──────────┐            │
        │   │ Client A │   │ Client B │   │ Client C │            │
        └───┴────┬─────┴───┴────┬─────┴───┴────┬─────┴────────────┘
                 │ JSON-RPC     │ JSON-RPC     │ JSON-RPC
            ┌────▼────┐    ┌────▼────┐    ┌────▼────┐
            │Server A │    │Server B │    │Server C │
            │(GitHub) │    │(Postgres)│   │(files)  │
            └─────────┘    └─────────┘    └─────────┘

The wire format: JSON-RPC 2.0

Every message is a JSON-RPC 2.0 object, UTF-8 encoded, over a stateful connection. There are exactly three message shapes:

  • Request — has id + method + params; expects a response.
  • Response — has the matching id + either result or error.
  • Notification — has method (+ optional params) but no `id`; fire-and-forget, no response.
// Request                          // Response                        // Notification
{                                   {                                  {
  "jsonrpc": "2.0",                   "jsonrpc": "2.0",                  "jsonrpc": "2.0",
  "id": 7,                            "id": 7,                          "method": "notifications/initialized"
  "method": "tools/call",            "result": { ... }                }
  "params": { ... }                 }                                  // (no id ⇒ no reply expected)
}

Errors reuse JSON-RPC's code/message/data shape (-32602 "Invalid params", etc.).

The handshake (lifecycle)

The connection has three phases — Initialization → Operation → Shutdown — and the init handshake is mandatory and ordered:

Client ──── initialize (request)   ────────►  Server     # client states protocolVersion + capabilities
Client ◄─── InitializeResult       ──────────             # server states ITS protocolVersion + capabilities
Client ──── notifications/initialized ─────►  Server     # client confirms; ONLY now does normal traffic start

The initialize request carries the client's protocolVersion (a date string like "2025-06-18", not semver), its capabilities, and clientInfo:

{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "roots": {"listChanged": true}, "sampling": {}, "elicitation": {} },
    "clientInfo": { "name": "OpenAlice", "version": "1.0.0" }
  } }

The server replies with its capabilities (tools, resources, prompts, logging, completions, …). Version negotiation rule: if the server supports the requested version it MUST echo it back; otherwise it MUST reply with a version it supports (SHOULD be its latest), and if the client can't speak that, the client SHOULD disconnect. Until the server has answered initialize, the client SHOULD send nothing but ping.

The six primitives (this is the heart of MCP)

Capabilities split into what servers offer the model, and what clients offer back to servers. Knowing which is which is the single most clarifying fact about MCP:

Server → client/model (the "tools and data"):

PrimitiveWhat it isWho decides to use it
ToolsFunctions the model can execute (arbitrary code/side effects). Listed via tools/list, invoked via tools/call.Model-controlled
ResourcesReadable context/data (a file, a row, a doc) addressed by URI. resources/list, resources/read, optional subscribe.Application-controlled
PromptsTemplated message/workflow snippets the user can invoke (e.g. slash-commands). prompts/list, prompts/get.User-controlled

Client → server (server-initiated, the under-appreciated half):

PrimitiveWhat it lets a server do
SamplingAsk the host's model to generate — recursive/agentic LLM calls without the server shipping its own model or keys.
RootsAsk the client what filesystem/URI boundaries it's allowed to operate within.
ElicitationAsk the user for more info mid-operation (e.g. a missing parameter, a confirmation).

listChanged / subscribe sub-capabilities let either side push update notifications (notifications/tools/list_changed, etc.) so a long-lived session stays fresh.

A tool-call round-trip, in pseudocode from the host's side:

session = mcp_connect(server)                  # spawn subprocess or open HTTP endpoint
init = session.request("initialize", {
    "protocolVersion": "2025-06-18",
    "capabilities": {...}, "clientInfo": {...},
})
assert init.protocolVersion in SUPPORTED        # else disconnect
session.notify("notifications/initialized")     # handshake done

tools = session.request("tools/list").tools     # discover
# → feed tool JSON-schemas to the model as available functions

# model decides to call a tool; host MUST get user consent first (security principle #3)
if user_approves(call):
    result = session.request("tools/call", {
        "name": call.name, "arguments": call.args,
    })
    feed_back_to_model(result.content)          # text / image / resource blocks

Transports — two standard, pluggable

MCP is transport-agnostic; the spec defines two and lets you add custom ones:

  1. stdio (preferred for local). The client launches the server as a subprocess and exchanges newline-delimited JSON-RPC over stdin/stdout. The rules are strict and worth memorizing because they bite everyone: messages MUST NOT contain embedded newlines, the server MUST NOT write anything to `stdout` that isn't a valid MCP message (use stderr for logs), and the client must not write junk to stdin. One stray print() to stdout and the stream desyncs.
  1. Streamable HTTP (for remote/multi-client; introduced 2025-03-26, replacing the old HTTP+SSE transport). A single MCP endpoint path serves both POST and GET. The client POSTs each JSON-RPC message; the server either returns one JSON object (Content-Type: application/json) or upgrades to an SSE stream (text/event-stream) to push multiple messages and server-initiated requests before the final response. A GET opens a server→client SSE channel. Statefulness is carried by the `Mcp-Session-Id` header (assigned in the InitializeResult, echoed on every subsequent request); 404 means "session gone, re-initialize." Resumability uses SSE event `id`s + the `Last-Event-ID` header for per-stream replay after a drop. HTTP clients also send `MCP-Protocol-Version: <version>` on every request.

Two HTTP security MUSTs the spec calls out by name: validate the `Origin` header (DNS-rebinding defense) and, when local, bind to 127.0.0.1, not 0.0.0.0.

Key ideas & tradeoffs

  • LSP-for-tools is the whole insight. MCP isn't a clever model technique; it's a standardization play. Its value is almost entirely network-effect — which is also its fragility (a fragmented or forked ecosystem destroys the value). The Linux Foundation donation exists precisely to protect the network effect.
  • Stateful by design — and that's now the pain point. JSON-RPC sessions with capability negotiation are clean for a desktop app talking to a local subprocess. They fight horizontal scaling: sticky sessions vs. load balancers, no cheap way for a crawler to learn what a server does without a full handshake. The 2026 roadmap's #1 priority is exactly this — evolving toward statelessness and adding discoverable `.well-known` metadata endpoints.
  • The model controls tools; the human controls consent. MCP deliberately separates who chooses (tools = model-controlled, resources = app-controlled, prompts = user-controlled). The spec is blunt that MCP "cannot enforce security at the protocol level" — every primitive that does something dangerous (tool calls, sampling, data exposure) carries a MUST get explicit user consent in the spec's Security & Trust section. Tool *descriptions/annotations are explicitly "untrusted unless from a trusted server."**
  • Sampling is the elegant bit people miss. A server can ask the host to run the model on its behalf. That means an MCP server can be agentic (recursive LLM reasoning) without shipping a model or holding API keys — the host owns the model, the keys, and the consent. This is what makes MCP servers cheap to write and safe(r) to run.
  • MCP ≠ A2A; they're complementary. Crisp dividing line that the ecosystem has now settled on: MCP is agent↔tool; A2A is agent↔agent. "Each agent uses MCP to connect to its tools, and agents use A2A to communicate with each other." Both now sit under the same Linux Foundation umbrella (AAIF), which is actively working to make them interoperate cleanly rather than compete.

Honest caveats & open questions

  • The "18k+ / 97M" numbers are mushy — be careful quoting them. Counts vary wildly by methodology. Combining the four canonical public registries (PulseMCP, the official MCP registry, Smithery, mcp.so) gave ~9,400 distinct servers by April 2026; Anthropic's Dec-2025 update cited >10,000 active public servers; "13,000+" appears when you add private/community servers; and "97M installs" is an install/usage metric, not a server count. Treat "tens of thousands of servers, ~100M cumulative installs" as the honest shape and don't over-precise it.
  • Security is the genuinely unsolved part. Because tools are "arbitrary code execution" and descriptions are untrusted, MCP has a real attack surface: prompt-injection via tool results, malicious/typosquatted servers in registries, over-broad consent, token theft, DNS-rebinding against local servers. The protocol provides principles and a couple of MUSTs, but enforcement is left to implementers — which means quality varies enormously across the long tail of community servers. Authorization (SSO, audit trails, gateways) was not a hard 2026 commitment — it's in "On the Horizon," i.e. still maturing.
  • Statefulness vs. serverless is unresolved today. Until the stateless-session work lands, running MCP at scale behind a load balancer involves workarounds. If you're building remote MCP infra in 2026, you're building on a moving target here.
  • A2A is *absent* from MCP's own 2026 roadmap. Despite the marketing about complementarity, the official MCP roadmap doesn't mention A2A at all — convergence is a foundation-level aspiration, not yet a protocol-level guarantee. Don't assume seamless MCP↔A2A interop exists today.
  • Versioning churn. Transport changed twice in ~18 months (HTTP+SSE → Streamable HTTP), the protocol version is a date you must negotiate, and backwards-compat is a documented dance. SDKs hide most of this, but hand-rolled clients/servers eat the breakage.

How it connects to OpenAlice

OpenAlice runs both boundaries of the modern agent stack, and the distinction above is exactly the line we drew:

  • MCP (agent ↔ tools/context): Atlas exposes its code/knowledge graph to agents over an MCP (JSON-RPC) endpoint at atlas.blal.pro/mcpatlas.code.search, atlas.code.callers/callees, atlas.repo.features/health, atlas.context, atlas.board.*. When an OpenAlice agent "queries Atlas before grepping," it is acting as an MCP host with Atlas as the server — Atlas is literally a USB-C peripheral for every agent in the org. The same pattern is how this knowledge library itself is reached. The lab's standard practice (one atlas.context call replacing the git-log/board/claims ritual) is just good MCP-server design: rich, discoverable, single-round-trip tools.
  • A2A (agent ↔ agent): the lab's a2a chat (town-square / atlas-bugs / atlas-requests rooms, the chat_post/chat_read tools) is the other boundary — agents coordinating with each other and with NAO, not pulling tools. That maps cleanly onto the industry-settled split: MCP for tools, A2A for inter-agent talk. OpenAlice running both in production is a small mirror of the AAIF thesis — the two protocols are complementary layers of one stack.
  • Consent gates line up with our HITL discipline. MCP's "tools = arbitrary code, require explicit user consent, descriptions are untrusted" maps directly onto OpenAlice's approval-protocol / safety-gate / standing-approval work (the B2 approval protocol, denylist-floor gates). MCP states the principle; our gate layer is one concrete enforcement of it — exactly the "MCP can't enforce at the protocol level, implementers SHOULD build robust consent flows" gap, filled.
  • Practical takeaway for builders here: when wiring a new capability for Alice or a lab service, ask which boundary it is. Tool/data for one agent → ship an MCP server (stdio for local, Streamable HTTP for shared), and lean on Atlas as the reference example. Cross-agent coordination → it belongs on a2a, not as an MCP tool.

Related reading in this library: [[agent-memory-systems]] (what the tools/memory plug into), [[model-routing]] and [[fusion-and-llm-councils]] (multi-model orchestration that sits above the MCP tool layer), [[autoresearch]] and [[graphrag]] (retrieval/agent workflows that increasingly expose themselves as MCP servers), and [[test-time-compute-reasoning]] (the reasoning loop that decides which MCP tools to call).