First Principles · 14 min mission
MCP Protocol Deep Dive: The Standard Behind Every Server
Read the Model Context Protocol itself — architecture, primitives, transports, and auth — so every tool's MCP screen becomes a thin skin over machinery you understand.
On this page
The Model Context Protocol (MCP) is an open JSON-RPC 2.0 standard for connecting AI applications to external tools and data. The final 2026-07-28 revision changes the protocol's center of gravity: the core is stateless, every request declares its protocol context, and a server never initiates a JSON-RPC request.
This guide explains the modern wire model first, then marks the legacy session model explicitly. That boundary matters because many clients and servers still support 2025-era peers during migration.
| Role | What it owns | Security boundary |
|---|---|---|
| Host | The AI application that creates clients, presents consent, coordinates the model, and aggregates approved context | Keeps the full conversation and cross-server context; each server receives only what the host sends |
| Client | The MCP-speaking component inside the host | Adds protocol version, client capabilities, and optional client identity to every request |
| Server | A focused local process or remote service that exposes tools, resources, and prompts | Returns capabilities and identity as data, but self-reported identity is never a trust signal |
Modern core vs. legacy session protocol
2026-07-28
Stateless core. No mandatory initialize request, initialized notification, or Mcp-Session-Id. Each request carries version and client capabilities in metadata. Servers only answer requests or emit allowed notifications.
2025-11-25 and earlier
Connection-scoped session. The client initializes once, capabilities are negotiated for the session, and servers may initiate requests such as sampling or elicitation. Kept for compatibility, not as the modern design target.
The stable server surface
The server-facing features remain tools, resources, and prompts. Their control model still tells you who chooses the capability: the model selects tools, the application reads resources, and a person selects prompts. The modern revision adds cache semantics and deterministic-list guidance without changing those basic roles.
| Feature | Key methods | Control model | Result rule |
|---|---|---|---|
| Tools | tools/list, tools/call | Model-controlled action | A business failure is a readable tool result; malformed protocol input is a JSON-RPC error |
| Resources | resources/list, resources/templates/list, resources/read | Application-controlled context | Text or base64 data addressed by URI; resource-not-found now uses Invalid Params (-32602) |
| Prompts | prompts/list, prompts/get | User-controlled template | Returns messages for explicit selection, often through a menu or slash command |
Discovery and per-request negotiation
A 2026-era server must implement server/discover. It reports supported versions, server capabilities, optional instructions, and self-reported server identity. Calling it is optional for clients: a client may send a normal method immediately and handle an UnsupportedProtocolVersion error instead.
On stdio, server/discover is also the clean compatibility probe because there is no HTTP status code to classify the peer. A dual-era client sends discovery first; if the response shows a legacy peer, it falls back to the older initialize flow.
| Metadata key | Status | Purpose |
|---|---|---|
| io.modelcontextprotocol/protocolVersion | Required | Selects the protocol revision for this request |
| io.modelcontextprotocol/clientCapabilities | Required | Declares the client features available during this request |
| io.modelcontextprotocol/clientInfo | Recommended | Names the client for display and diagnostics |
| io.modelcontextprotocol/logLevel | Optional | Opts this request into protocol log notifications at the requested level |
Results and multi-round-trip input
Every result now has a required resultType. Ordinary work returns complete. Work that needs more input returns input_required with one or more inputRequests. The client obtains the requested user input, model output, or roots data, then retries the original method with inputResponses.
This multi-round-trip pattern replaces server-initiated JSON-RPC requests. A server no longer sends sampling/createMessage, elicitation/create, or roots/list as a request to the client. The original method remains the unit of work across each retry.
One multi-round-trip operation
Client calls the original method
The request carries the current protocol version and the client capabilities needed for any additional input.
Server returns input_required
The result contains inputRequests describing what is missing. The server may include requestState; the client must echo that value byte-for-byte on the retry.
Client resolves the inputs
The host asks the person, calls its model, or supplies an allowed path according to the declared request type and its own policy.
Client retries the original method
The client uses a fresh JSON-RPC request ID and includes only this round's inputResponses. The server either requests another round or returns a complete result.
Transports
MCP still standardizes stdio and Streamable HTTP. The JSON-RPC semantics are the same, but framing, cancellation, and the metadata mirror differ by transport.
The two standard transports
stdio
A host launches a local subprocess. Requests arrive as newline-delimited UTF-8 JSON-RPC on stdin; responses leave on stdout. Log only to stderr. Cancellation uses notifications/cancelled, and process exit ends the connection.
Streamable HTTP
Each message is an HTTP POST to one MCP endpoint. A response is JSON or a request-scoped SSE stream. There is no protocol session ID, no GET event stream, and no SSE replay in the 2026 revision.
| Removed or changed | Modern rule |
|---|---|
| Mcp-Session-Id | Removed. Put durable state behind explicit server-minted handles passed as normal parameters. |
| GET notification stream | Replaced by subscriptions/listen, a long-lived POST-response stream for selected change notifications. |
| Last-Event-ID replay | Removed. If a response stream breaks, reconcile the operation first; retry with a new JSON-RPC request ID only when application-level idempotency or deduplication prevents duplicate side effects. |
| Transport-only protocol context | The JSON body is authoritative, but routing headers are mandatory: MCP-Protocol-Version on every POST, Mcp-Method on every request, and Mcp-Name on tools/call, resources/read, and prompts/get. |
Subscriptions and durable tasks
Clients that want server-side change events call subscriptions/listen and opt into specific event families such as tool-list, prompt-list, resource-list, or resource-subscription changes. Request-scoped progress and log notifications remain on the response stream of the request they belong to.
Durable work is no longer an experimental core primitive. It is the official io.modelcontextprotocol/tasks extension. Its lifecycle uses tasks/get for polling, tasks/update for client input, and tasks/cancel for cancellation. There is no tasks/list method in the redesigned extension.
| Area | 2026 status | Implementation guidance |
|---|---|---|
| Tools, resources, prompts | Core | Safe foundation for new servers |
| Elicitation | Core, via multi-round-trip input | Use form mode for non-secret fields and URL mode for out-of-band auth or payment |
| Tasks | Official extension | Negotiate io.modelcontextprotocol/tasks before returning task handles |
| Roots and Sampling | Deprecated | Use explicit path/resource parameters and direct model-provider integration in new designs |
| Protocol Logging | Deprecated | Use stderr for stdio and OpenTelemetry for deployed services |
Authorization for HTTP servers
Authorization remains optional and applies to HTTP transports. The MCP server is the OAuth resource server; the client is the OAuth client; the authorization server may be separate. Use Protected Resource Metadata, authorization-server discovery, PKCE, audience binding through the resource parameter, and bearer tokens only in the Authorization header.
The 2026 revision prefers Client ID Metadata Documents. Dynamic Client Registration is deprecated for backwards compatibility. Authorization servers should return an issuer identifier as defined by RFC 9207; clients must validate a present issuer before redeeming the code and must key stored client credentials by that issuer.
Migrate a 2025-era implementation deliberately
Upgrade the SDK before rewriting the wire layer
The stable TypeScript and Python v2 SDKs implement the 2026 protocol and carry compatibility support. Use their migration guides instead of recreating version negotiation by hand.
Remove connection-scoped protocol state
Delete assumptions around initialize, initialized, Mcp-Session-Id, GET event streams, and Last-Event-ID. Keep business state behind explicit handles or durable storage.
Adopt discovery, metadata, and result envelopes
Implement server/discover, ensure each request is evaluated against its protocol metadata, and return complete or input_required results.
Move notifications and long-running work
Use subscriptions/listen for change events and negotiate the tasks extension for durable operations.
Retire deprecated features from new designs
Replace roots, sampling, protocol logging, and Dynamic Client Registration while preserving only the compatibility paths your deployed clients need.
Knowledge check
A new 2026-07-28 server needs the user to approve a write while handling tools/call. What should it do?
Reach the end and this star joins your charted sky.