A protocol-level reference for the Model Context Protocol (MCP): the wire model, the two current transports (stdio and Streamable HTTP), the stateless request model, header-based routing, discovery, caching, Multi Round-Trip Requests, subscriptions, the optional OAuth 2.1 authorization profile, and how the same protocol powers local AI clients (Claude Code, VS Code, Cursor) and server-hosted agents. Every protocol example is verified against the official MCP specification revision 2026-07-28.
2026-07-28.
The final specification shipped on July 28, 2026.
It replaces MCP's protocol-level sessions and initialisation handshake with a stateless,
self-describing request model. The changes most consequential to this paper are:
initialize / notifications/initialized handshake is removed. Protocol version and client capabilities now travel in _meta on every request; clients SHOULD also include their identity. Servers MUST implement server/discover, although calling it is optional.Mcp-Session-Id header and protocol-level session are removed. Any request can land on any server instance; cross-call application state is carried with explicit server-minted handles.MCP-Protocol-Version and Mcp-Method; Mcp-Name is additionally required for tools/call, resources/read, and prompts/get. Tool schemas can mirror selected primitive arguments into validated Mcp-Param-* headers.resultType: "input_required", and the client retries the original operation with inputResponses. All ordinary results now require resultType: "complete".server/discover, tools/list, prompts/list, resources/list, resources/templates/list, and resources/read results carry ttlMs and cacheScope; tool order SHOULD be deterministic. Tool schemas now accept the full JSON Schema 2020-12 vocabulary.2025-11-25.
MCP is an open, JSON-RPC 2.0–based protocol with a client–host–server architecture. A host (typically an LLM application or agent runtime) creates and manages one MCP client per server, and each client communicates with exactly one MCP server (a process that exposes tools, resources, and prompts). The protocol is transport-agnostic; the current revision (2026-07-28) standardises two transports: stdio and Streamable HTTP.
The host is the application that hosts the LLM and orchestrates tool use — Claude Code, VS Code (Copilot Chat), Cursor, or any custom agent runtime. The host creates one MCP client per server; each client speaks the protocol to exactly one server and keeps the servers isolated from one another.
A local subprocess or remote service that exposes tools, resources, and/or prompts. A host typically runs several servers concurrently — one client per server — and routes each operation to the client that owns it.
Either stdio (server runs as a child process; messages flow through stdin/stdout) or Streamable HTTP (server is a network endpoint reached over HTTP POST, with Server-Sent Events for streaming responses and subscribed notifications).
Three JSON-RPC 2.0 message types remain: requests (have an id and expect a response), responses (match a request by id), and notifications (have no id and receive no response). Their delivery is transport-specific.
Source: modelcontextprotocol.io · Specification 2026-07-28 · Architecture · Base Protocol · Versioning
stdio transportUnder the stdio transport, the client launches the server as a child process and exchanges newline-delimited JSON-RPC messages over the server's standard input and standard output. The server may write UTF-8 strings to standard error for logging. This is the default transport for local, single-user tools (filesystem access, local databases, local scripts).
\n and MUST NOT contain embedded newlines. stderr is the only channel available for free-form server logs.The client invokes the server's command with args and an optional env map. The server inherits the client's working directory unless the client overrides it. The stdio channel itself opens no network socket, though the server may of course reach network services of its own.
Each JSON-RPC message is one UTF-8 line terminated by \n. Embedded newlines are forbidden, so multi-line content must be JSON-escaped. There is no separate header frame; the line itself is the message.
The server MAY write any UTF-8 text to stderr; the client MAY capture, forward, or ignore it, and SHOULD NOT read stderr output as a sign of failure. This is the only out-of-band channel — anything written to stdout MUST be a valid JSON-RPC message.
The client first closes the server's input stream and waits for it to exit. If it remains alive, Unix clients send SIGTERM, wait again, and use SIGKILL only as the forced final fallback.
Source: modelcontextprotocol.io · Specification 2026-07-28 · stdio
Streamable HTTP transport
Streamable HTTP is the standardised transport for remote MCP servers. The server exposes one
POST-only endpoint. Every JSON-RPC request is a separate HTTP POST, and the response
is either a single JSON object or a Server-Sent Events (SSE) stream scoped to that request.
The protocol has no HTTP-level session and no standalone GET stream.
subscriptions/listen.The server exposes one path (commonly /mcp) that accepts POST. Every request includes Accept: application/json, text/event-stream, MCP-Protocol-Version, and Mcp-Method. Selected named operations also include Mcp-Name. A tool can mark safe primitive inputs with x-mcp-header; conforming HTTP clients mirror them as Mcp-Param-* headers. Sensitive values should never be marked this way.
A simple result uses Content-Type: application/json. A streaming result uses SSE for notifications related to that request — such as progress — followed by the final JSON-RPC response. Streams are not resumable: if one breaks, the client reissues the operation with a new JSON-RPC id.
Every request carries its protocol version and client capabilities in params._meta. Any instance can process it. Applications that need cross-call state return an explicit handle and accept that handle on later calls.
A client that wants tool-list, prompt-list, resource-list, or resource-update notifications sends subscriptions/listen with an explicit filter. Its POST response remains open as an SSE stream.
Sampling, elicitation, and roots are no longer separate requests sent by the server. The server returns resultType: "input_required"; the client gathers the requested input and retries the original method with a new JSON-RPC id.
Servers MUST validate a present Origin header, and local servers SHOULD bind only to 127.0.0.1. A protected server can use the OAuth 2.1 profile in §7 and validate a resource-bound Bearer token; deployments protected by mTLS, workload identity, or private-network policy need not use that profile.
2025-03-26 through 2025-11-25 used sessions, a standalone GET stream, direct server requests, and resumable SSE. None belongs to 2026-07-28. Compatibility clients may implement both models, but must not apply retired semantics to requests negotiated as 2026-07-28.
Source: modelcontextprotocol.io · Specification 2026-07-28 · Streamable HTTP · Multi Round-Trip Requests · Subscriptions
MCP no longer has a protocol-level connection lifecycle. Each request is self-describing and
independent: the client may first call server/discover, list the server's tools, and
invoke one of them. Every request repeats the protocol version and client capabilities in
_meta; no handshake or hidden session state is created.
tools/list or tools/call immediately and handle an unsupported-version error. Every blue request independently declares its version and client capabilities.Every request carries io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in params._meta. Clients SHOULD also send io.modelcontextprotocol/clientInfo; servers SHOULD identify themselves in each result's _meta.
Servers MUST implement server/discover. Clients MAY use it to retrieve supported versions, server capabilities, identity, and instructions before any other operation, but can instead call an operation directly and negotiate from UnsupportedProtocolVersionError.
server/discover, tools/list, prompts/list, resources/list, resources/templates/list, and resources/read return ttlMs and cacheScope on every complete result. Tool order SHOULD be deterministic so clients and upstream model prompts can be cached reliably.
The client sends tools/call with a name and arguments. An ordinary result has resultType: "complete". A server that needs elicitation, sampling, or roots returns resultType: "input_required", and the client retries the original operation with the requested input.
Request-scoped progress or log messages travel before the final response on that request's stream. Independent list-change and resource-update notifications require an opted-in subscriptions/listen request. Notifications have no id and receive no response.
Source: modelcontextprotocol.io · Specification 2026-07-28 · Versioning · Discovery · Caching · Tools
These representative exchanges show the required per-request metadata and the new result fields.
server/discover{
"jsonrpc": "2.0",
"id": 1,
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
}
Source: modelcontextprotocol.io · Specification 2026-07-28 · Discovery
server/discover response{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {
"resources": {},
"tools": { "listChanged": true }
},
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "ExampleServer",
"version": "1.0.0"
}
},
"instructions": "Weather and resource utilities",
"ttlMs": 3600000,
"cacheScope": "public"
}
}
Source: modelcontextprotocol.io · Specification 2026-07-28 · Discovery
tools/list// Request
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
}
// Response
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"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"]
}
}
],
"ttlMs": 300000,
"cacheScope": "public"
}
}
Source: modelcontextprotocol.io · Specification 2026-07-28 · Server / Tools
tools/call + response// Request
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "New York" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
}
// Response
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}
Source: modelcontextprotocol.io · Specification 2026-07-28 · Server / Tools
A local AI application (Claude Code, VS Code, Cursor, and others) acts as the MCP host. It reads a configuration file that lists MCP servers, creates one MCP client per entry, spawns stdio servers as child processes, and sends HTTP requests to remote servers. It combines their tools into one logical catalog, but may load tools on demand or expose a search step instead of placing every definition in every model turn.
The application loads a JSON config (location and root key vary by app; see snippets below) that lists every MCP server it should connect to. Servers are typically scoped at three levels: user-wide, workspace/project, and (in Claude Code) enterprise-managed.
The host creates one MCP client per configured server: each stdio server is a child process, and each remote server is reached over HTTP. Their tools enter one logical catalog. The host disambiguates collisions — often by prefixing names — and may defer large catalogs behind tool search.
A protected remote server may use the OAuth 2.1 flow in §7, a configured Bearer token, or another client-supported mechanism. Interactive clients generally keep OAuth credentials in secure credential storage and attach the resulting access token to each protected request.
Each client independently discovers or lists its own server, and the host routes tools/call to the client that owns the selected tool. Servers do not see one another, and no shared MCP session is created.
Claude Code reads .mcp.json at the project root (or ~/.claude.json for
user-wide servers). For the two transports this paper covers, the type field
accepts stdio and http — with streamable-http as an
alias for http. Claude Code also accepts sse — the legacy HTTP+SSE
transport, which remains in the specification in the Deprecated state — and an app-specific
ws (WebSocket) transport that sits outside the specification altogether.
{
"mcpServers": {
"github": {
"type": "streamable-http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${GITHUB_TOKEN}"
}
},
"my-db": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@bytebase/dbhub", "--dsn", "${DB_DSN}"],
"env": { "DB_TIMEOUT": "30000" }
}
}
}
Source: code.claude.com/docs/en/mcp · Connect Claude Code to tools via MCP
VS Code reads .vscode/mcp.json for workspace-scoped servers (or a user-profile
mcp.json for user-wide). The root key is servers (not
mcpServers); the type field accepts stdio,
http, and sse — the last being the legacy HTTP+SSE transport, which
the specification now marks Deprecated. VS Code tries the HTTP Stream transport first and falls
back to SSE only for a server that does not support it.
{
"servers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/"
},
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@microsoft/mcp-server-playwright"]
}
}
}
Source: code.visualstudio.com · Add and manage MCP servers · MCP configuration reference
Cursor reads ~/.cursor/mcp.json (global) or .cursor/mcp.json (project).
The root key is mcpServers. Remote entries declare only a url; Cursor
infers the HTTP transport when url is present.
{
"mcpServers": {
"github": {
"url": "https://api.githubcopilot.com/mcp/"
},
"filesystem": {
"command": "npx",
"args": ["-y", "mcp-server-filesystem", "/path/to/project"],
"env": { "LOG_LEVEL": "info" }
}
}
}
Source: cursor.com/docs/mcp · Model Context Protocol
mcpServers; VS Code uses servers. Claude Code accepts streamable-http as an alias for http; VS Code uses http; Cursor infers HTTP from the presence of url. All three accept the same core stdio fields (command, args, env).
In this pattern, the agent runtime acts as the MCP host: running in a container, a serverless function, or a long-lived service, it creates one MCP client per server. Local stdio servers are less common because the runtime may be ephemeral or may restrict child-process execution. The agent instead connects to one or more remote MCP servers over Streamable HTTP. Those servers may run in the same cluster as the agent or be operated by a third party.
Each request carries the protocol version and client capabilities needed to process it, and normally the client's identity as well. No MCP session affinity is required. When a tool needs cross-call application state, it returns an explicit handle that the model passes back as a later tool argument.
A hosted runtime may not permit child processes and may be cold-started per request, so remote HTTP services are usually easier to operate. A stdio-only server can be deployed behind a conforming Streamable HTTP front end when needed.
Internal services may use mTLS, workload identity, or short-lived tokens. Protected third-party servers commonly use the MCP OAuth 2.1 profile with resource-bound access tokens. Authentication choices remain deployment-specific.
The agent runtime aggregates or searches the available tool catalogs; the model or orchestration layer selects a tool. The host then routes that call to the client that owns the tool's server. It need not place every tool definition in every model request.
Source: modelcontextprotocol.io · Specification 2026-07-28 · Changelog (stateless protocol) · Tools · Stateful tools (explicit handles) · Authorization. The deployment commentary about ephemeral runtimes and trust domains is non-normative.
Authorization is optional in MCP. When an HTTP server is protected, implementations SHOULD follow the MCP OAuth 2.1 profile: Protected Resource Metadata (RFC 9728) locates the authorization server, PKCE protects the authorization code, Resource Indicators (RFC 8707) bind tokens to the MCP server, and issuer validation (RFC 9207) prevents authorization-server mix-up. Client ID Metadata Documents are preferred; Dynamic Client Registration is deprecated.
An unauthenticated MCP request receives 401 with a WWW-Authenticate challenge pointing at RFC 9728 metadata. The client fetches that document and then the selected authorization server's OAuth or OpenID Connect metadata.
The client obtains a client ID through a Client ID Metadata Document, pre-registration, or — only for backwards compatibility — Dynamic Client Registration. Persisted credentials are keyed by issuer and must not be reused with another authorization server.
The browser request includes a PKCE challenge and an RFC 8707 resource parameter. Before redeeming the returned code, the client validates a present iss against the issuer it recorded from validated metadata, then exchanges the code with the verifier and resource.
The client includes Authorization: Bearer … on every protected HTTP request. The server validates that the token was issued for its resource and has sufficient scopes. A 403 insufficient-scope challenge can trigger step-up authorization.
Source: modelcontextprotocol.io · Specification 2026-07-28 · Authorization · Client registration
| stdio | Streamable HTTP | |
|---|---|---|
| Process model | Server is a child process of the client | Server is an independent network service |
| Framing | Newline-delimited JSON-RPC on stdin/stdout | HTTP POST body (one JSON-RPC message); response is either JSON or SSE |
| Request metadata | Protocol version and client capabilities in every request's _meta |
The same _meta, plus required version and routing headers |
| Server-to-client interaction | MRTR results; subscribed notifications share stdout | MRTR results; notifications use request SSE or subscriptions/listen |
| Authentication | Normally inherits the OS user; credentials come from the environment | Deployment-specific; protected servers SHOULD use the MCP OAuth 2.1 profile |
| Concurrency | One client per server process | One server may serve many concurrent clients |
| Protocol sessions | None; process lifetime bounds the transport connection | None; each request is independent and may reach any instance |
| Logs | stderr | Server's own logging stack |
| Typical use | Local tools on a developer machine; CLI integrations | Remote / multi-tenant tools; hosted agent runtimes; vendor APIs |
| Spec section | Specification 2026-07-28 · stdio | Specification 2026-07-28 · Streamable HTTP |
Six factual observations drawn from revision 2026-07-28 and the current Tier 1 SDKs.
Every request declares its protocol version and client capabilities in _meta. There is no initialisation handshake or protocol-level session. Servers implement server/discover, but clients may call any supported RPC first.
Revision 2026-07-28 defines stdio and POST-only Streamable HTTP. The earlier two-endpoint HTTP+SSE transport remains available only during its formal deprecation window.
HTTP headers expose method, operation name, and selected tool arguments to gateways. Cacheable results carry freshness and scope hints. MRTR handles elicitation, sampling, and roots through retryable results; subscriptions/listen carries opted-in change notifications.
A local host reads a config file, creates one client per server, and merges their tools into one catalog. A hosted runtime does the same per replica and needs no session affinity: any request can land on any compatible instance, and tools needing cross-call state return explicit handles the model passes back as ordinary arguments.
Protected HTTP servers SHOULD use the MCP OAuth 2.1 profile: RFC 9728 discovery, PKCE, RFC 8707 resource indicators, and RFC 9207 issuer validation. Client ID Metadata Documents are preferred; Dynamic Client Registration is deprecated.
Python, TypeScript, Go, and C# support revision 2026-07-28. Python v2's MCPServer and TypeScript v2's McpServer hide JSON-RPC plumbing and derive or accept tool schemas from native type systems.
Sources: 2026-07-28 changelog · stdio · Streamable HTTP · MRTR · Subscriptions · Authorization · SDK docs cited in §8.
Terms used throughout this paper.
name, description, and a JSON Schema inputSchema. Invoked via tools/call.resources/list and fetched via resources/read.subscriptions/listen._meta — metadata carrying protocol version, client capabilities, and normally client identity on every request.Mcp-Method / Mcp-Name — required Streamable HTTP headers that mirror the RPC method and, for selected operations, the tool name, prompt name, or resource URI.x-mcp-header — tool-schema annotation that instructs Streamable HTTP clients to mirror a safe primitive argument into a validated Mcp-Param-* request header.resultType — required result discriminator: complete for an ordinary result or input_required for an MRTR interim result.input_required and the client retries the original method with requested input.subscriptions/listen response stream for list-change and resource-update notifications.ttlMs and cacheScope hints that tell clients how long and where it may be cached./.well-known/oauth-protected-resource that tells clients which authorization server protects a resource.MCP-specific definitions above are taken from the MCP Specification 2026-07-28. RFC numbers refer to the published IETF documents.