Model Context Protocol (MCP)

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.

Contents
  1. The problem it solves
  2. Host, client, server
  3. Primitives: tools, resources, prompts
  4. The stateless lifecycle (2026-07-28)
  5. Transports
  6. Multi round-trip requests
  7. Authorization
  8. Extensions: tasks, skills, apps
  9. Security model
  10. Writing a server
  11. Migrating from 2025-11-25
  12. Review checklist
  13. Sources

1. The problem it solves

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.

WITHOUT MCP — N × M adapters app 1app 2app 3 system Asystem Bsystem Csystem D WITH MCP — N + M host MCP client server Aserver Bserver Cserver D
Each server implements the protocol once; any compatible host gets every capability it is allowed to see.

2. Host, client, server

RoleResponsibilityExamples
HostThe application the user interacts with. Owns consent, the model, and the conversation. Creates one client per server.Desktop assistant, IDE, chat app
ClientThe connector inside the host. Speaks JSON-RPC to exactly one server and isolates it.The host's internal MCP module
ServerExposes 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

PrimitiveWhat it isWho controls itTypical use
toolsFunctions the model may call, with JSON Schema input and optional output schemaModelQuery a database, open a ticket, run a query
resourcesRead-only context addressed by URIApplication / userFiles, records, dashboards
promptsReusable templates with argumentsUserSlash commands, workflows
ElicitationServer asks for missing inputServer-initiatedConfirm 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:

{
  "jsonrpc": "2.0",
  "id": 17,
  "method": "tools/call",
  "params": {
    "name": "get_order_status",
    "arguments": { "order_id": "CO-104422" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": { "extensions": [] },
      "io.modelcontextprotocol/clientInfo": { "name": "desktop-host", "version": "3.4.1" }
    }
  }
}
ElementBehaviour in 2026-07-28
server/discoverServers must implement it: advertises supported versions, capabilities and identity. Clients may call it first or use it to probe over STDIO.
Protocol versionSent on every request in _meta; a mismatch returns UnsupportedProtocolVersionError.
resultTypeRequired on every result: "complete" or "input_required". Older servers that omit it are treated as "complete".
Cross-call stateNo session id. Servers mint explicit handles and pass them as ordinary tool arguments.
CachingList and read results carry ttlMs and cacheScope (public / private).
Change notificationsOne subscriptions/listen stream, opted in per type (toolsListChanged, resourcesListChanged…).
LoggingPer 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

TransportShapeUseStatus
stdioNewline-delimited JSON over stdin/stdout of a child processLocal servers shipped with the hostActive
Streamable HTTPPOST for requests, optional long-lived POST-response stream for notificationsRemote servers, multi-tenantActive
HTTP + SSESeparate GET streamLegacy serversDeprecated

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:

client → tools/call { name: "deploy", arguments: {...} }
server → { "resultType": "input_required",
           "inputRequests": [ { "type": "elicitation", "message": "Target environment?",
                                "requestedSchema": { "environment": { "enum": ["staging","prod"] } } } ],
           "requestState": "opaque-token" }

client → tools/call { name: "deploy", arguments: {...},
                      inputResponses: [...], requestState: "opaque-token" }
server → { "resultType": "complete", "content": [...] }
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:

RequirementRule
Resource indicatorsThe 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 validationAuthorization servers should return iss (RFC 9207) and clients must compare it to the recorded issuer before redeeming the code.
Client registrationClient ID Metadata Documents are preferred; dynamic client registration (RFC 7591) is deprecated but still supported for compatibility.
Credential bindingCredentials are bound to the issuing authorization server: key them by issuer, never reuse across servers, re-register on change.
Application typeClients must send an appropriate application_type during registration to avoid redirect URI conflicts.
ConsentHosts 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:

ExtensionAddsUse case
io.modelcontextprotocol/tasksAsynchronous execution with tasks/get polling, tasks/update for client input and durable handlesLong migrations, report generation
Skills over MCPStructured instructions for agent workflows, discovered through the protocolPackaging know-how next to the tools
MCP AppsInteractive UI (charts, forms, players) rendered inline in the conversationDashboards, confirmations

9. Security model

RiskHow it happensControl
Tool poisoningA malicious server hides instructions in tool descriptions or annotationsTrust servers explicitly; treat descriptions as untrusted; review on change
Prompt injection through resourcesRetrieved content tells the model to call a destructive toolNever let resource text authorise a tool; keep it in an untrusted block
Confused deputyA server uses its own broad credentials for a narrow user requestPer-user tokens, resource indicators, per-tool scopes
Over-broad scopesOne token grants every toolScope per tool family; deny by default
Data exfiltrationA tool sends context to an external endpointEgress allow-list, review of server endpoints
Silent changeServer updates tool behaviour after approvalPin 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))
RuleWhy
Implement server/discoverRequired by the current revision; clients may call it before anything else
Return deterministic tools/list orderEnables client caching and improves prompt cache hit rates
Set ttlMs and cacheScopeLets hosts cache lists and reads safely
Keep tool results smallTool output is the main driver of agent context growth
Structured, actionable errorsThe model can correct a bad call if the error says how
Idempotency keys on writesClients retry; a duplicate write is a data bug

11. Migrating from 2025-11-25

Removed or changedReplace with
initialize handshake, Mcp-Session-IdPer-request _meta with version and capabilities; server/discover
ping, logging/setLevel, notifications/roots/list_changedHealth via the transport; per-request logLevel
HTTP GET stream, resources/subscribesubscriptions/listen
SSE resumability (Last-Event-ID)Re-issue the request with a new id
Server-initiated sampling / roots / elicitationMulti round-trip requests (input_required)
Blocking tasks/resultTasks extension with tasks/get and tasks/update
Roots, Sampling, Logging as featuresDeprecated — pass paths as arguments, call the LLM provider directly, log to stderr or OpenTelemetry
Dynamic client registrationClient ID Metadata Documents (DCR kept for compatibility)

12. Review checklist

13. Sources