Model Context Protocol 2026-07-28
MCP Host
Server stdio
Server http
Client

One protocol,
multiple tools.

The Model Context Protocol replaces a pile of bespoke plugin systems with a single JSON-RPC contract between an AI application and the tools it is allowed to reach. This page covers the theory, the wire format, and the commands.

Layer 1
JSON-RPC 2.0
Layer 2
2 transports
Primitives
tools / res / prompts
Discovery
server/discover
01 / ARCHITECTURE

Three components, one direction

MCP is a client–server protocol. The host is the AI application you talk to. It spawns one client per server. Each client owns exactly one connection. That 1:1 mapping is the reason permission, logging, and trust stay tractable.

MCP Host

The AI application. Manages the conversation, decides which clients exist, and surfaces tools to the user.
spawns

Client 01

One connection. Tools list, calls, roots.

Client 02

Another connection, another server, isolated.
connects

Server · local

Subprocess, stdio transport, inherits your OS identity.

Server · remote

HTTP endpoint, Streamable HTTP transport, OAuth boundary.
HOST · VS Code, Claude Code, Claude Desktop

Coordinates clients. Owns the user session. Decides which tools appear and whether a call needs approval.

CLIENT · protocol-level, 1:1

A connection object, not a separate product. It exists so a host can hold N independent sessions without them sharing state.

SERVER · any program

Exposes tools, resources, and prompts. "Server" describes the role, not the location — local and remote are both servers.

The direction rule. Clients send requests and notifications to servers. Servers send responses and notifications to clients. No other combination is legal: a server may not open a JSON-RPC request against a client, and a client may not send a bare response. This is what makes servers safe to run — they cannot drive the conversation, they can only answer.
Data layer inner

Discovery, capability negotiation, primitives, notifications. Identical on every transport. Encoded as JSON-RPC 2.0, UTF-8.

Transport layer outer

How bytes are framed and delivered, where request metadata rides, and how a pending request is abandoned. A transport is only a binding.

02 / TRANSPORTS

Define your requirements

A transport defines framing, metadata, and cancellation. It does not define meaning. Pick stdio for anything local, Streamable HTTP for anything shared.

Property stdio Streamable HTTP
Framing Newline-delimited JSON on a client-launched subprocess One HTTP POST per message to a single MCP endpoint
Reply shape Same stdout stream JSON object, or a request-scoped SSE stream
Typical scope One server, one client, one machine Many clients, many machines
Version header Not applicable MCP-Protocol-Version
Cancellation notifications/cancelled Close the response stream
Auth Process boundary. Inherits your OS identity. OAuth 2.1, resource metadata per RFC 9728
stdio · one line per message
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"add"}]}}
// one JSON object per line, stdin → stdout, UTF-8. stdout is protocol.
// Anything you print to stdout for humans corrupts the stream — log to stderr.
streamable http · one endpoint
POST /mcp HTTP/1.1
Accept: application/json, text/event-stream
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add","arguments":{"a":1,"b":2}}}
A local stdio server runs with your permissions. It is a subprocess you approved. Review the command before trusting it — the trust prompt is the only gate between an arbitrary program and your filesystem, network, and credentials.
03 / LIFECYCLE

Discovery, then traffic

In revision 2026-07-28 the initialize handshake is gone. Discovery is a single mandatory RPC, and the protocol version is declared per request rather than pinned to a session.

discovery · server/discover
// client → server. Mandatory RPC. Optional to call: any request may be sent directly.
{ "jsonrpc": "2.0", "id": 1, "method": "server/discover" }

// server → client. Illustrative shape, trimmed to the fields that matter.
{
  "protocolVersions": ["2026-07-28"],
  "capabilities": { "tools": { "listChanged": true } },
  "serverInfo": { "name": "demo", "version": "1.0.0" }
}
per-request metadata · _meta
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28" },
  "params": { "name": "add", "arguments": { "a": 1, "b": 2 } }
}
// The server accepts or rejects each request independently.
// Mismatch → UnsupportedProtocolVersionError listing supported versions.
Message Direction Has id Expects
Request client → server yes Exactly one result or error
Response server → client echoed Nothing — it closes the request
Notification either no Nothing. Fire and forget.
Code Meaning
-32700 Parse error — invalid JSON
-32600 Invalid request
-32601 Method not found
-32602 Invalid params — schema violation
-32603 Internal error
Backward compatibility

Revisions 2025-11-25 and earlier used an initialize handshake that opened a connection-scoped session and let servers send requests. Modern peers detect the counterpart's era and fall back. Implement both if you must serve old hosts.

Cancellation is transport-specific, and that is the only difference. Over stdio the client sends notifications/cancelled. Over Streamable HTTP it closes the response stream. The protocol rules afterwards are identical everywhere.
04 / PRIMITIVES

Who gets to decide?

Three server primitives, split by who controls them. That split is the design: a model may only reach for what is model-controlled, and the user controls prompts.

Primitive Controlled by Methods Reads / writes
Tools model tools/list tools/call Writes. Databases, APIs, files.
Resources application resources/list resources/read resources/templates/list Reads. Docs, schemas, calendars.
Prompts user prompts/list prompts/get Templates the user picks explicitly.
tools · list and call
{ "method": "tools/call", "params": {
    "name": "searchFlights",
    "arguments": { "origin": "NYC", "destination": "BCN", "date": "2026-08-14" }
} }

// A tool is a JSON Schema plus a description. The description is the
// only thing that tells the model when to reach for it.
{ "name": "searchFlights",
  "description": "Search for available flights",
  "inputSchema": { "type": "object", "properties": {
      "origin":      { "type": "string", "description": "Departure city" },
      "destination": { "type": "string", "description": "Arrival city" },
      "date":        { "type": "string", "format": "date" } },
    "required": ["origin","destination","date"] } }
resources · uri and template
// Direct resource — a fixed URI with a declared MIME type.
calendar://events/2026

// Resource template — discovered via resources/templates/list.
travel://activities/{city}/{category}
travel://activities/barcelona/museums

Client features: asking the user

ELICITATION form + url

A server can pause mid-call and ask the user for input it could not know. Form mode collects structured data; URL mode sends the user out of band, so the data never touches the client. Uses the MRTR pattern — the server returns InputRequiredResult, the client retries with the collected answers.

SAMPLING deprecated

Servers could request an LLM completion through the client, keeping permissions and user control where they belong. Deprecated in 2026-07-28. It stays in the spec for at least twelve months while you migrate.

ROOTS deprecated

Clients used to declare which directories a server should focus on. Also deprecated in 2026-07-28. Do not build new servers on it; use elicitation or explicit arguments.

05 / OPERATIONS

Running and Inspection

Build with the SDK, debug with the Inspector, register with a config file. Python needs 3.10+.

  1. Install the SDK
    python · cli extra adds mcp dev | run | install
    # uv (recommended)
    uv add "mcp[cli]"
    
    # or plain pip
    pip install "mcp[cli]"
    
    # one-off, no project
    uv run --with "mcp[cli]" mcp dev server.py
    
    # typescript
    npm install @modelcontextprotocol/sdk
  2. Write a server — no schema, no request parsing, no protocol boilerplate
    python · server.py
    from mcp.server import MCPServer
    
    mcp = MCPServer("Demo")
    
    @mcp.tool()
    def add(a: int, b: int) -> int:
        """Add two numbers."""
        return a + b
    
    @mcp.resource("greeting://{name}")
    def greeting(name: str) -> str:
        """Greet someone by name."""
        return f"Hello, {name}!"
  3. Debug with the Inspector
    inspector
    # launches the MCP Inspector against a local script
    uv run mcp dev server.py
    
    # or attach the standalone inspector to any command
    npx -y @modelcontextprotocol/inspector uvx mcp-server-fetch
    
    # call add with a=1 b=2 → you get 3 back
  4. Serve over HTTP, then drive it from a client
    streamable http · transport
    # serve; default port 8000, endpoint /mcp
    uv run mcp run server.py --transport streamable-http
    python · client.py
    import asyncio
    from mcp import Client
    
    async def main() -> None:
        async with Client("http://localhost:8000/mcp") as client:
            result = await client.call_tool("add", {"a": 1, "b": 2})
            print(result.structured_content)
    
    asyncio.run(main())
  5. Register with a host — pick the file your host actually reads
Host File Top-level key
VS Code — user profile %APPDATA%\Code\User\mcp.json servers
VS Code — workspace .vscode/mcp.json deprecated servers
VS Code — portable .mcp.json mcpServers
Copilot CLI / Agent Host ~/.copilot/mcp-config.json mcpServers
Claude Desktop (Windows) %APPDATA%\Claude\claude_desktop_config.json mcpServers
Cursor ~/.cursor/mcp.json mcpServers
Codex CLI ~/.codex/config.toml [mcp_servers.NAME]
mcp.json · local + remote in one file
{
  "servers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@microsoft/mcp-server-playwright"]
    },
    "sentry": {
      "type": "http",
      "url": "https://mcp.sentry.dev/mcp"
    }
  }
}
from the terminal, no file editing
code --add-mcp "{\"name\":\"my-server\",\"command\":\"uvx\",\"args\":[\"my-mcp-server\"]}"
Never commit a secret. Config files land in git. Use ${input:apiKey} input variables or an environment file, and keep the local stdio command reviewable — anyone who reads the file can run it.
Symptom Cause Fix
Spawn ENOENT Executable not found on PATH Absolute path, or a full package name after -y
Server starts, no tools Protocol chatter printed to stdout Move logging to stderr
Method not found Client speaks an older revision Check server/discover output; support both eras
HTTP hangs Wrong port, or POST without an SSE-capable Accept Hit /mcp directly; send the MCP-Protocol-Version header