An open protocol that turns N integrations × M applications into N + M: a server exposes tools, resources and prompts once, and any compatible host can use them. Written against the 2026-07-28 revision, the current one at the time of writing.
Before MCP, every assistant that needed your issue tracker, your database and your file system implemented three bespoke connectors, and every vendor reimplemented the same integrations for every assistant. The cost grows as the product of both sides.
Each server implements the protocol once; any compatible host gets every capability it is allowed to see.
2. Host, client, server
Role
Responsibility
Examples
Host
The application the user interacts with. Owns consent, the model, and the conversation. Creates one client per server.
Desktop assistant, IDE, chat app
Client
The connector inside the host. Speaks JSON-RPC to exactly one server and isolates it.
The host's internal MCP module
Server
Exposes capabilities: tools, resources, prompts. May be local (a process) or remote (an HTTP service).
Filesystem, Git, database, ticketing
The wire format is JSON-RPC 2.0: tools/call, resources/read, prompts/get, server/discover, plus notifications. Requests carry a protocol version and client capabilities in _meta, so no handshake is needed.
3. Primitives: tools, resources, prompts
Primitive
What it is
Who controls it
Typical use
tools
Functions the model may call, with JSON Schema input and optional output schema
Model
Query a database, open a ticket, run a query
resources
Read-only context addressed by URI
Application / user
Files, records, dashboards
prompts
Reusable templates with arguments
User
Slash commands, workflows
Elicitation
Server asks for missing input
Server-initiated
Confirm a parameter, pick an option
The distinction is about control, not shape. A resource is context the user or the app chooses to attach; a tool is an action the model chooses to take. Putting a dangerous write behind a resource URI does not make it safe.
4. The stateless lifecycle (2026-07-28)
The current revision removed protocol-level sessions and the initialize / notifications/initialized handshake. Every request is self-describing:
Servers must implement it: advertises supported versions, capabilities and identity. Clients may call it first or use it to probe over STDIO.
Protocol version
Sent on every request in _meta; a mismatch returns UnsupportedProtocolVersionError.
resultType
Required on every result: "complete" or "input_required". Older servers that omit it are treated as "complete".
Cross-call state
No session id. Servers mint explicit handles and pass them as ordinary tool arguments.
Caching
List and read results carry ttlMs and cacheScope (public / private).
Change notifications
One subscriptions/listen stream, opted in per type (toolsListChanged, resourcesListChanged…).
Logging
Per request via io.modelcontextprotocol/logLevel; logging/setLevel was removed. Servers must not emit notifications/message unless the request asked for it.
Why it matters operationally: a stateless server can sit behind a plain load balancer, scale horizontally, and be retried safely. The trade-off is that anything stateful must be modelled explicitly.
5. Transports
Transport
Shape
Use
Status
stdio
Newline-delimited JSON over stdin/stdout of a child process
Local servers shipped with the host
Active
Streamable HTTP
POST for requests, optional long-lived POST-response stream for notifications
Remote servers, multi-tenant
Active
HTTP + SSE
Separate GET stream
Legacy servers
Deprecated
In 2026-07-28 Streamable HTTP lost Mcp-Session-Id, the standalone GET endpoint and SSE resumability: a broken response stream loses the in-flight request and the client must re-issue it with a new request id. Standard headers Mcp-Method and Mcp-Name are required on POST.
6. Multi round-trip requests
Server-initiated requests (sampling/createMessage, roots/list, elicitation/create) were replaced by a client-driven pattern. The server answers with an interim result instead of calling back:
Benefit. The server no longer needs a channel to call the client, so it can run behind a request/response gateway, and the retry carries the answer explicitly — which makes the interaction auditable.
7. Authorization
Remote servers use OAuth 2.1. The MCP-specific requirements are the ones implementers get wrong:
Requirement
Rule
Resource indicators
The token is requested for the MCP server's canonical URI (resource parameter), so a token minted for one server cannot be replayed against another.
Issuer validation
Authorization servers should return iss (RFC 9207) and clients must compare it to the recorded issuer before redeeming the code.
Client registration
Client ID Metadata Documents are preferred; dynamic client registration (RFC 7591) is deprecated but still supported for compatibility.
Credential binding
Credentials are bound to the issuing authorization server: key them by issuer, never reuse across servers, re-register on change.
Application type
Clients must send an appropriate application_type during registration to avoid redirect URI conflicts.
Consent
Hosts obtain explicit user consent before exposing data to a server or invoking a tool.
8. Extensions: tasks, skills, apps
Extensions are opt-in, negotiated through the extensions field of ClientCapabilities and ServerCapabilities. Three are notable in the current revision:
Extension
Adds
Use case
io.modelcontextprotocol/tasks
Asynchronous execution with tasks/get polling, tasks/update for client input and durable handles
Long migrations, report generation
Skills over MCP
Structured instructions for agent workflows, discovered through the protocol
Packaging know-how next to the tools
MCP Apps
Interactive UI (charts, forms, players) rendered inline in the conversation
Dashboards, confirmations
9. Security model
Risk
How it happens
Control
Tool poisoning
A malicious server hides instructions in tool descriptions or annotations
Trust servers explicitly; treat descriptions as untrusted; review on change
Prompt injection through resources
Retrieved content tells the model to call a destructive tool
Never let resource text authorise a tool; keep it in an untrusted block
Confused deputy
A server uses its own broad credentials for a narrow user request
Pin versions, hash tool definitions, alert on change
Tools are arbitrary code execution. The specification says it plainly: hosts must obtain explicit consent before invoking a tool, and tool annotations are untrusted unless they come from a trusted server.
10. Writing a server
# conceptual shape of a minimal server (any SDK)
server = Server("orders")
@server.tool(
name="get_order_status",
description="Return status for one order id. Use search_orders when the user describes the order instead.",
input_schema={"order_id": "string:^[A-Z]{2}-\\d{6}$"},
output_schema={"status": "placed|shipped|delivered|cancelled", "eta_days": "integer"})
def get_order_status(order_id, ctx):
if not ctx.scopes.has("orders:read"):
raise McpError(-32021, "missing scope orders:read")
row = db.orders.find(order_id) # short, indexed query
if not row:
raise McpError(-32602, "not_found: call search_orders first")
return {"status": row.status, "eta_days": row.eta_days}
server.run(transport="streamable-http", auth=oauth21(resource=ISSUER_URI))
Rule
Why
Implement server/discover
Required by the current revision; clients may call it before anything else
Return deterministic tools/list order
Enables client caching and improves prompt cache hit rates
Set ttlMs and cacheScope
Lets hosts cache lists and reads safely
Keep tool results small
Tool output is the main driver of agent context growth
Structured, actionable errors
The model can correct a bad call if the error says how
Idempotency keys on writes
Clients retry; a duplicate write is a data bug
11. Migrating from 2025-11-25
Removed or changed
Replace with
initialize handshake, Mcp-Session-Id
Per-request _meta with version and capabilities; server/discover