Skip to content

Protocols and Frameworks

Model Context Protocol (MCP)

The USB-C of tools

You already know how an agent calls a tool. But every tool you wire up by hand is a one-off. MCP is the standard that turns that bespoke wiring into a plug: write a tool once as a server, and every MCP-capable agent can use it. This lesson is the protocol layer of the modern agent stack.

The problem: M times N bespoke integrations

Before MCP, connecting an AI app to an external system - Google Drive, Slack, GitHub, a Postgres database - meant writing a custom integration for that pair. With M AI applications and N tools or data sources, the ecosystem trends toward M times N one-off connectors: a combinatorial explosion nobody can maintain, that re-fragments every time a model or a tool changes.[1]

The collapses that surface to M plus N. A tool author writes one MCP server; a model-app author writes one MCP client. Then any client speaks to any server, with no bespoke glue.[1] This is the exact move the Language Server Protocol made for editors and compilers.

Tap a node to see what it does.

Four apps times four sources is 16 bespoke integrations. Route everything through the MCP standard and it collapses to M plus N: each app implements a client once, each source implements a server once.

The one-line analogy

The docs call MCP "a USB-C port for AI applications."[2] One standard connector on the model side, one on the tool side, instead of a drawer full of bespoke adapters. Anthropic open-sourced the protocol on 25 November 2024.[1]

Architecture: host, client, server

MCP defines three roles. Getting these straight is the whole mental model.[2]

  1. Host. The user-facing AI app that runs the LLM, enforces consent, and manages connections. Claude Desktop, Claude Code, Cursor, VS Code, ChatGPT.
  2. Client. A connector object inside the host. The host spins up one client per server, each holding a dedicated 1:1 connection.
  3. Server. A program that exposes capabilities. Local servers run as a subprocess; remote servers run on a platform. "Server" is a role, not a location.

The one-client-per-server rule is a security decision, not an accident: each connection is a clean trust boundary, so a misbehaving server is scoped to its own client and cannot silently reach into another.[2]

Host (the app + model)
Server (wraps a capability)
Real system (DB, API, files)

Tap a node to see what it does.

The host holds the model and one client per server. Each client owns a dedicated connection: local servers over stdio, remote servers over Streamable HTTP.

Two layers, cleanly split

MCP separates a data layer - a protocol defining lifecycle, primitives, and messages (the part developers care about) - from a swappable transport layer that just moves the bytes. The same JSON-RPC messages ride over stdio or HTTP unchanged.[2]

The primitives: what a server exposes

An MCP server offers three kinds of capability. The difference between them is who is in control: the model, the app, or the user.[2]

  1. Tools (model-controlled). Executable functions the model calls to take action or cause side effects: query a DB, open a PR, send a message. Each has a name, a description, and a JSON-Schema inputSchema. Methods: tools/list, tools/call.
  2. Resources (app-controlled). Read-only, URI-addressed context: file contents, a DB schema, logs, an API response. Data to load into context, not an action to run. Methods: resources/list, resources/read.
  3. Prompts (user-controlled). Reusable, parameterized templates the server author encodes: system prompts, few-shot flows, slash-command-style workflows the host surfaces to the user. Methods: prompts/list, prompts/get.

Why three, not one

Not every piece of context should be a tool the model can fire at will. Resources let the host inject data deterministically, and prompts let a server ship a vetted workflow. Splitting by who controls the call keeps the model out of decisions it should not own.

What makes MCP bidirectional, and richer than a plain tool API, is that the server can also call back into the client. The two to know:

Sampling (reverse)

The server asks the client's host LLM to run a completion, so a server can use model reasoning without shipping its own model keys. The host mediates and can require approval.[2] Method: sampling/createMessage.

Elicitation (reverse)

The server requests structured input from the human user mid-operation: a form, a confirmation. Added in the 2025-06-18 revision. Method: elicitation/create.

Mental model: Tools are model-controlled (the LLM decides to call them), Resources are app-controlled (the host decides what to load), Prompts are user-controlled (the user picks a template). Sampling and Elicitation invert that direction, letting the server reach back toward the client's model and user.

Transports: how the bytes move

The data layer is transport-agnostic; MCP ships two transports.[3]

stdio - local

The client launches the server as a subprocess and talks over stdin/stdout with newline-delimited JSON-RPC. stderr is free for logs. Zero network overhead; ideal for local tools. Clients SHOULD support it whenever possible.

Streamable HTTP - remote

One HTTP endpoint handling POST (each message) and GET (an SSE stream for server-initiated messages). Carries an MCP-Session-Id header and supports Last-Event-ID resumability. MCP recommends OAuth 2.1 for auth.

The original 2024-11-05 spec used a two-endpoint HTTP+SSE transport. The 2025-03-26 revision replaced it with single-endpoint Streamable HTTP - fixing the pain of long-lived connections, load-balancing, and resumability - leaving HTTP+SSE as legacy back-compat only.[3]

What a small MCP server actually looks like

Concretely: a tiny weather server exposes one tool, one resource, and one prompt, then runs over stdio. Below is the server, followed by the JSON-RPC the host and server exchange on the wire.[2]

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

# TOOL - model-callable action, described for the LLM
@mcp.tool()
def get_current(location: str, units: str = "metric") -> str:
    """Get current weather for any location worldwide."""
    return fetch_weather(location, units)

# RESOURCE - read-only, URI-addressed context
@mcp.resource("weather://cities")
def known_cities() -> str:
    """The cities this server can report on."""
    return "\n".join(CITY_INDEX)

# PROMPT - reusable, user-picked template
@mcp.prompt()
def trip_brief(city: str) -> str:
    return f"Summarize today's weather in {city} for a traveler."

if __name__ == "__main__":
    mcp.run(transport="stdio")   # local subprocess
weather_server.py: one tool, one resource, one prompt, run over stdio.
// client → server
{"jsonrpc":"2.0","id":1,"method":"initialize",
 "params":{"protocolVersion":"2025-06-18",
           "capabilities":{"elicitation":{}},
           "clientInfo":{"name":"example-client","version":"1.0.0"}}}

// server → client - declares what it supports
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18",
  "capabilities":{"tools":{"listChanged":true},"resources":{}},
  "serverInfo":{"name":"weather","version":"1.0.0"}}}

// client → server - no id: it's a notification
{"jsonrpc":"2.0","method":"notifications/initialized"}
Step 1: the initialize handshake and capability negotiation.
// client → server - what can you do?
{"jsonrpc":"2.0","id":2,"method":"tools/list"}

// server → client - schema handed to the LLM as a function (abridged)
{"jsonrpc":"2.0","id":2,"result":{"tools":[
  {"name":"get_current",
   "description":"Get current weather for any location worldwide",
   "inputSchema":{"type":"object",
     "properties":{"location":{"type":"string"}},
     "required":["location"]}}]}}

// client → server - the LLM chose to call it
{"jsonrpc":"2.0","id":3,"method":"tools/call",
 "params":{"name":"get_current","arguments":{"location":"San Francisco"}}}

// server → client - content injected back into the conversation
{"jsonrpc":"2.0","id":3,"result":{"content":[
  {"type":"text","text":"68°F, partly cloudy..."}]}}
Step 2: discover the tools, then call one over the wire.

The host merges tools from all connected servers into one registry and hands their schemas to the LLM as available functions.[2] If the server declared "listChanged": true at init, it can later emit notifications/tools/list_changed to keep a live agent's toolset current without polling - that is paying off.

Ecosystem and adoption (2025 to 2026)

MCP went from an Anthropic proposal to the industry's de-facto tool-connection standard in about a year.

  1. OpenAI adopted MCP in March 2025 across the Agents SDK, the Responses API, and the ChatGPT desktop app. A direct competitor endorsing Anthropic's protocol signaled real consolidation.[5] First-class client support now spans Claude, ChatGPT, Gemini, Cursor, Microsoft Copilot and VS Code, and Amazon Bedrock.[4]
  2. The registry hit about 2,000 entries by the one-year mark (Nov 2025, a roughly 407% jump since its September launch), with thousands of active servers and a fast-growing contributor base.[4]
  3. Governance went vendor-neutral. On 9 December 2025 Anthropic donated MCP to the Linux Foundation's Agentic AI Foundation - co-founded with Block and OpenAI, backed by Google, Microsoft, AWS, Cloudflare, and Bloomberg - so no single vendor owns the standard.[6]

The context bill comes due

Once an agent connects to many servers, the bottleneck stops being capability and becomes context: hundreds of tool definitions and every intermediate result flow through the model. Anthropic's code execution with MCP pattern - expose servers as code APIs the agent discovers on demand (progressive disclosure) - reports cutting a representative workflow from 150,000 to 2,000 tokens (98.7%).[7]

Security: MCP's power is also its danger

MCP mixes tools and data from many sources into one model context - the same property that makes it useful makes it dangerous. The security literature converges on a handful of named failure modes.[8]

Tool poisoning

A malicious server hides instructions inside a tool's description or schema - text the model reads but the user rarely does. Distinct from ordinary injection because it lives in config-looking metadata reviews overlook, and a tool need only be poisoned once to hit every session.[9]

Rug pulls and shadowing

Tools can change definitions after you approve them - approval is not durable. And with several servers loaded, a malicious one can intercept or override calls meant for a trusted server.[8]

The framing that ties it together is Simon Willison's : an agent is exploitable for data exfiltration the moment it has all three of access to private data, exposure to untrusted content, and the ability to communicate externally.[10] MCP's mix-and-match tools make it easy to assemble all three by accident.

Private data access
Untrusted content
External comms
Any one or two legs are survivable; all three together is the exact condition for data theft. The official GitHub MCP server alone completes it: private-repo access, public issues (untrusted), and opening PRs (exfiltration).

Real exploits, not theory

Documented 2025 lethal-trifecta attacks hit the GitHub MCP server[11] and Supabase MCP, where an injected instruction in a support ticket could leak an entire SQL database.[12] Defenses that recur: pin trusted servers (do not let users freely mix arbitrary third-party servers into a private-data context), require human approval for consequential tools, validate the Origin header and bind local servers to 127.0.0.1, and never assemble all three trifecta legs in one context.

MCP is not A2A

Two protocols anchor the agent stack, and they solve different problems. MCP is vertical - an agent reaching down to tools and data. Google's A2A (April 2025) is horizontal - an agent talking sideways to another agent.[13]

MCPA2A
AxisVertical: agent to tools/dataHorizontal: agent to agent
Core jobGive one model access to tools, resources, promptsLet agents discover, delegate to, and coordinate with each other
Key abstractionsHost/client/server; Tools, Resources, Prompts, SamplingAgent Cards, Tasks, Artifacts, status updates
RelationshipComplementary, not competing - they composeComplementary, not competing - they compose
MCP is vertical (agent to its tools); A2A is horizontal (agent to peer agent). They are a stack, not a choice.

The canonical composition: an orchestrator uses A2A to delegate a subtask to a specialist agent, and that specialist uses MCP to reach the database or API it needs. MCP for data access, A2A for coordination. The next lesson builds out the horizontal side.[13]

Check yourself

Name the three MCP roles and the one-client-per-server rule. Why does that rule matter?

What security property does it buy? Try to state it, then check.

Match each MCP concept to what it is.

drop here

model-controlled action with side effects

drop here

app-controlled readable data by URI

drop here

user-controlled reusable template

drop here

transport for a local child process

drop here

the underlying message format

In MCP, what is the difference between a Tool and a Resource?

Why does MCP build on JSON-RPC with a runtime discovery step instead of a fixed, compiled-in tool list?

Lock it in

  • MCP collapses M times N into M plus N. Write a tool once as a server, a model app once as a client - USB-C for AI applications.
  • Host, client, server. The host runs the LLM and spins up one client per server; each connection is a clean trust boundary.
  • Primitives split by control. Tools (model-controlled actions), Resources (app-controlled context), Prompts (user-controlled templates), plus Sampling and Elicitation calling back into the client.
  • Two transports. stdio for local subprocesses; single-endpoint Streamable HTTP (OAuth 2.1) for remote servers.
  • Security is the headline risk. Tool poisoning, rug pulls, and the lethal trifecta make untrusted servers dangerous - pin what you trust and gate consequential tools.
  • MCP is not A2A. MCP is vertical (agent to tools), A2A is horizontal (agent to agent); they compose.

Primary source

MCP Architecture - modelcontextprotocol.io docs

The authoritative walkthrough of participants, the data vs transport layers, the primitives, lifecycle and capability negotiation, and a full JSON-RPC trace. Read this one end-to-end.

Sources

  1. 1.Anthropic, Introducing the Model Context Protocol
  2. 2.MCP Architecture, modelcontextprotocol.io docs
  3. 3.MCP Specification, Transports (2025-11-25)
  4. 4.Model Context Protocol, First anniversary (Nov 2025)
  5. 5.OpenAI Agents SDK, Model Context Protocol
  6. 6.Anthropic, Donating MCP and establishing the Agentic AI Foundation
  7. 7.Anthropic, Code execution with MCP
  8. 8.Simon Willison, MCP has prompt injection security problems
  9. 9.OWASP MCP Top 10, Tool Poisoning (MCP03:2025)
  10. 10.Simon Willison, The lethal trifecta for AI agents
  11. 11.Simon Willison, GitHub MCP exploited via prompt injection
  12. 12.Simon Willison, Supabase MCP lethal trifecta
  13. 13.Descope, MCP vs A2A