---
title: "Model Context Protocol (MCP)"
description: "Protocol for connecting AI agents to external tools, data sources, and services"
url: "https://agentsurface.dev/docs/protocols/mcp"
lastVerified: 2026-09-25
lastModified: 2026-09-25T05:46:01.000Z
---



## Summary [#summary]

MCP defines how agents discover and use tools, read resources, and invoke prompts. It is the baseline protocol for connecting AI applications to external systems. Current spec revision: `2026-07-28`, with a stateless core, no initialization handshake, and no protocol sessions. All Tier 1 SDKs - TypeScript, Python, Go, and C# - support it. Transport options are stdio for local processes and Streamable HTTP for remote servers. Protected HTTP servers use OAuth-based authorization discovery.

```
Agent → connects via stdio/HTTP → MCP Server
  ├─ discovers tools
  ├─ reads resources (documents, data)
  ├─ invokes prompts (structured workflows)
  └─ negotiates optional extensions such as async Tasks
```

* Three core primitives: tools (model-controlled), resources (application-controlled), prompts (user-controlled)
* Stateless JSON-RPC core over stdio or Streamable HTTP
* Async Tasks are the official `io.modelcontextprotocol/tasks` extension, not a core primitive
* Authorization for protected HTTP servers via OAuth protected-resource metadata and authorization-server discovery
* Server advertises capabilities; agent selects which to use
* MCP Apps - the first official MCP extension (SEP-1865, Stable `2026-01-26`) - add interactive UI surfaces inside compatible clients (ChatGPT, Claude, VS Code, Goose, Postman); see [MCP Apps](/docs/agentic-ui/mcp-apps)
* Agent Skills complement MCP by packaging reusable instructions, scripts, references, and assets

MCP defines a standard wire format for an agent to discover what a server can do, call tools, read resources, use structured prompts, and negotiate extensions. Implement MCP first when you are exposing remote tools, data, or workflows to external agents. For page-scoped browser tools, see [WebMCP](/docs/protocols/webmcp).

## Origin and Governance [#origin-and-governance]

Anthropic published the initial MCP specification in November 2024. Since then it has become the main cross-vendor protocol surface for connecting agents to tools, resources, prompts, and workflow primitives. Stewardship now sits with the &#x2A;*Agentic AI Foundation (AAIF)** under the Linux Foundation (founded 2025-12-09, with MCP, goose, and AGENTS.md as anchor projects); the MCP steering group persists under that umbrella.

The current specification revision is [`2026-07-28`](https://modelcontextprotocol.io/specification/2026-07-28/). It shipped on schedule, and all Tier 1 SDKs support it. Its predecessor, [`2025-11-25`](https://modelcontextprotocol.io/specification/2025-11-25/), remains relevant only for compatibility with clients that have not migrated. Current semantics come first below; legacy examples are labeled.

<Callout type="info">
  Revision `2026-07-28` was the largest change since MCP launched. It introduced a **stateless
  protocol core** (removing the `initialize` / `notifications/initialized` handshake and the
  `Mcp-Session-Id` header), an optional `server/discover` method, a single `subscriptions/listen`
  stream in place of the HTTP GET endpoint and `resources/subscribe`, multi round-trip requests in
  place of server-initiated `roots/list`, `sampling/createMessage`, and `elicitation/create`, a
  formal extensions framework, OAuth 2.0 / OIDC hardening, a deprecation lifecycle with a 12-month
  minimum window, JSON Schema 2020-12 tool schemas, and W3C Trace Context. Build new servers against
  `2026-07-28`; add predecessor compatibility only when a supported client requires it.
</Callout>

The [MCP roadmap update (2026-08-22)](https://blog.modelcontextprotocol.io/posts/mcp-roadmap/) sets direction for what lands next: server-initiated events (webhooks and channels, replacing polling) alongside maturing the Tasks extension; a unified HTTP-native transport that extends Streamable HTTP to local servers currently reached over stdio; agent identity work (DPoP adoption and Workload Identity Federation for delegation without long-lived tokens); standardized tool-result handling and progressive discovery so a server can expose a small entry point and reveal more of a large tool catalogue as a conversation narrows; and SDK ergonomics and spec-conformance work. None of this is shipped spec behavior yet; build against shipped features only.

## Core Primitives and Extensions [#core-primitives-and-extensions]

MCP exposes three core primitives plus negotiated extensions. Each primitive has a different controller - the entity that decides when and how it is used.

### Tools (Model-Controlled) [#tools-model-controlled]

Tools are functions the model calls autonomously to take action or retrieve information. The model receives a list of available tools, selects which to call based on its reasoning, provides arguments, and receives the result.

```json
{
  "type": "tool",
  "name": "billing_create_invoice",
  "description": "Creates a new invoice for a customer. Call this when the user wants to bill a customer for services. Requires a valid customer_id - use billing_list_customers first if you only have a name.",
  "inputSchema": {
    "type": "object",
    "required": ["customer_id", "amount_cents", "currency"],
    "properties": {
      "customer_id": {
        "type": "string",
        "format": "uuid",
        "description": "UUID of the customer to invoice"
      },
      "amount_cents": {
        "type": "integer",
        "minimum": 1,
        "description": "Invoice amount in cents"
      },
      "currency": {
        "type": "string",
        "enum": ["usd", "eur", "gbp"],
        "description": "ISO 4217 currency code"
      },
      "due_date": {
        "type": "string",
        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
        "description": "Due date in YYYY-MM-DD format"
      }
    }
  }
}
```

The `description` field is the primary routing signal. It should answer: when to call this tool, when not to, what it returns, and what preconditions are required. See [Server Architecture](../mcp-servers/architecture) for patterns on writing effective tool descriptions.

### Resources (Application-Controlled) [#resources-application-controlled]

Resources expose data that the application makes available for an agent to read. Unlike tools, resources are not called autonomously - the application controls which resources are exposed and when. Resources are identified by URI and can represent files, database records, API responses, or any other data source.

```json
{
  "uri": "file:///workspace/reports/q4-2025.pdf",
  "name": "Q4 2025 Financial Report",
  "description": "Quarterly financial report including revenue, expenses, and projections",
  "mimeType": "application/pdf"
}
```

Resource URIs follow standard URI syntax. Custom URI schemes are common for application-specific resources:

```json
{
  "uri": "billing://customers/cus_01HV3K8MNP/invoices",
  "name": "Alice Chen — Invoice History",
  "mimeType": "application/json"
}
```

Servers that implement resource subscriptions notify clients when resource content changes. In `2025-11-25`, declare this capability at server construction time (see [Capability Declaration](#capability-declaration)); in the current `2026-07-28` revision, `resources/subscribe` is removed and a client opts into `resourceSubscriptions` notifications on the `subscriptions/listen` stream instead (see [What Changed in 2026-07-28](#what-changed-in-2026-07-28)).

### Prompts (User-Controlled) [#prompts-user-controlled]

Prompts are reusable message templates that users select to start or guide interactions. They are pre-defined conversation starters or structured workflows, chosen by the user rather than the model.

```json
{
  "name": "billing_monthly_review",
  "description": "Start a guided review of this month's billing activity",
  "arguments": [
    {
      "name": "month",
      "description": "Month to review in YYYY-MM format",
      "required": true
    },
    {
      "name": "currency",
      "description": "Filter to a specific currency (optional)",
      "required": false
    }
  ]
}
```

### Tasks Extension (Async Execution) [#tasks-extension-async-execution]

The official `io.modelcontextprotocol/tasks` extension allows long-running, asynchronous operations with explicit state management. Requestors poll with `tasks/get`; a receiver can request additional input with `tasks/update`; processing continues until a terminal state such as `completed`, `failed`, or `cancelled`. Negotiate the extension rather than assuming every MCP peer implements it.

```json
{
  "name": "billing_generate_report",
  "description": "Generate a comprehensive billing report. Returns a task_id for async polling.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "month": { "type": "string", "description": "YYYY-MM format" },
      "format": { "enum": ["csv", "json", "pdf"] }
    }
  }
}
```

Use tasks when an operation cannot complete in a single RPC roundtrip - report generation, data exports, compliance audits, or expensive batch work. Clients can poll task state and receivers may send optional task status notifications.

<Callout type="info">
  Compatibility note: `2025-11-25` treated Tasks as experimental core behavior and used
  `tasks/result` and `tasks/list`. Those methods are not part of the current Tasks extension.
</Callout>

## Wire Format [#wire-format]

MCP uses JSON-RPC 2.0 as its message format. Every interaction is a request/response pair or a notification, encoded as JSON.

### Current Per-Request Metadata [#current-per-request-metadata]

In `2026-07-28`, each request carries its protocol version and client capabilities in `_meta`. A server may implement `server/discover` to advertise supported versions, capabilities, and identity before normal calls, but discovery is optional. Handlers must not depend on connection state established by an initialization exchange.

### Legacy `2025-11-25` Initialization Handshake [#legacy-2025-11-25-initialization-handshake]

When a client connects, it sends an `initialize` request to negotiate protocol version and exchange capability declarations:

```json
// Client → Server
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "roots": { "listChanged": true },
      "sampling": {}
    },
    "clientInfo": {
      "name": "claude-desktop",
      "version": "1.4.0"
    }
  }
}

// Server → Client
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "tools": {},
      "resources": { "subscribe": true, "listChanged": true },
      "prompts": {},
      "logging": {}
    },
    "serverInfo": {
      "name": "billing-mcp",
      "version": "2.1.0"
    }
  }
}
```

The server must respond with the same `protocolVersion` or a lower supported version. If the versions are incompatible, the client disconnects.

<Callout type="warn">
  Current `2026-07-28` behavior (SEP-2575): the `initialize` request and the
  `notifications/initialized` acknowledgement are removed and the protocol core becomes stateless.
  Every request instead carries its own protocol version and client capabilities in `_meta` under
  `io.modelcontextprotocol/protocolVersion`, `io.modelcontextprotocol/clientCapabilities`, and
  `io.modelcontextprotocol/clientInfo`; servers identify themselves with
  `io.modelcontextprotocol/serverInfo` in the result `_meta`, and a version mismatch returns
  `UnsupportedProtocolVersionError`. Servers may implement `server/discover` to advertise supported
  versions, capabilities, and identity, with a back-compatibility probe for stdio. Design new
  servers so that no handler depends on state established at connect time.
</Callout>

### Tool Call [#tool-call]

```json
// Client → Server: tool call request
{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "billing_create_invoice",
    "arguments": {
      "customer_id": "cus_01HV3K8MNPQR",
      "amount_cents": 149900,
      "currency": "usd",
      "due_date": "2025-02-15"
    }
  }
}

// Server → Client: tool result
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Invoice inv_01HV3K8STUVW created for $1,499.00 USD, due 2025-02-15."
      }
    ],
    "isError": false
  }
}
```

For error conditions, set `isError: true` and include a recovery-oriented message in `content`:

```json
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Customer cus_01HV3K8MNPQR not found. Use billing_list_customers to find valid customer IDs."
      }
    ],
    "isError": true
  }
}
```

<Callout type="warn">
  Do not return JSON-RPC error objects for business logic failures. Use `isError: true` in the
  result instead. JSON-RPC errors (`error` instead of `result`) should only be used for
  protocol-level failures like malformed requests or unknown methods.
</Callout>

### Resource Read [#resource-read]

```json
// Client → Server
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "resources/read",
  "params": {
    "uri": "billing://customers/cus_01HV3K8MNP/invoices"
  }
}

// Server → Client
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "contents": [
      {
        "uri": "billing://customers/cus_01HV3K8MNP/invoices",
        "mimeType": "application/json",
        "text": "[{\"id\":\"inv_01HV\",\"status\":\"paid\",\"amount_cents\":149900}]"
      }
    ]
  }
}
```

## Tool Annotations [#tool-annotations]

Tool definitions support an `annotations` field. Annotations communicate behavioral characteristics to clients without embedding them in the description text.

```json
{
  "name": "billing_void_invoice",
  "description": "Voids an invoice, marking it as cancelled. This action cannot be undone.",
  "inputSchema": { ... },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": false,
    "openWorldHint": false
  }
}
```

| Annotation        | Type    | Meaning                                                                            |
| ----------------- | ------- | ---------------------------------------------------------------------------------- |
| `readOnlyHint`    | boolean | Tool does not modify any state (safe to call freely)                               |
| `destructiveHint` | boolean | Tool may delete or irreversibly modify data                                        |
| `idempotentHint`  | boolean | Calling with the same arguments multiple times has the same effect as calling once |
| `openWorldHint`   | boolean | Tool interacts with external systems outside the MCP server's control              |

Annotations are hints, not enforcement. Clients may use them to require user confirmation before destructive calls or to allow unrestricted use of read-only tools, but trusted server or workflow code must still enforce authorization, scopes, tenant boundaries, and approval gates.

## New in 2025-11-25 [#new-in-2025-11-25]

The November 2025 revision adds several important capabilities:

**Tasks** - Experimental durable requests with polling and deferred result retrieval.

**Client ID Metadata Documents** - Recommended OAuth client registration mechanism for clients and authorization servers that do not have a prior relationship.

**Authorization-server discovery** - HTTP authorization now leans on OAuth protected-resource metadata and supports OAuth or OpenID Connect discovery for authorization servers.

**URL Mode Elicitation** - Servers can direct users to external URLs for sensitive out-of-band interactions that should not pass through the MCP client. Removed in `2026-07-28`; see the multi round-trip replacement below.

**Tool-call support in sampling** - Sampling can include `tools` and `toolChoice` parameters.

## What Changed in 2026-07-28 [#what-changed-in-2026-07-28]

Beyond the stateless core, the removal of sessions, and the Tasks extension covered above, the `2026-07-28` revision made the following changes. Avoid reintroducing the predecessor behavior in new systems.

**Sessions are gone** (SEP-2567) - the `Mcp-Session-Id` header disappears from Streamable HTTP and list endpoints no longer vary per connection. Cross-call state moves to explicit server-minted handles passed as ordinary tool arguments.

**`subscriptions/listen` replaces the HTTP GET stream** (SEP-2575) - one long-lived POST-response stream carries `toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, and `resourceSubscriptions` notifications, opted into per type and tagged with `io.modelcontextprotocol/subscriptionId`. `resources/subscribe` and `resources/unsubscribe` are removed. Request-scoped notifications such as progress and log messages stay on the originating request's response stream.

**Multi round-trip requests replace server-initiated calls** (SEP-2322) - instead of the server calling `roots/list`, `sampling/createMessage`, or `elicitation/create`, it returns an `InputRequiredResult` (`resultType: "input_required"`) carrying `inputRequests`, and the client retries the original request with `inputResponses`. Every result now carries a required `resultType` of `"complete"` or `"input_required"`; results from older servers that omit it MUST be treated as `"complete"`. URL-mode elicitation's `notifications/elicitation/complete` and `elicitationId` are removed - the outcome is learned by retrying and correlated through `requestState`.

**Removed utilities** - `ping`, `logging/setLevel`, and `notifications/roots/list_changed` all go away. Log level is set per request through `io.modelcontextprotocol/logLevel` in `_meta`. SSE resumability goes with them: there are no event IDs or `Last-Event-ID` replay, so a broken stream is re-issued as a new request with a new ID.

**Deprecated with a 12-month minimum window** (SEP-2577) - Roots, Sampling, and Logging are all Deprecated. Replace Roots with tool parameters, resource URIs, or server configuration; replace Sampling with a direct call to your own LLM provider API; replace Logging with stderr on stdio or OpenTelemetry on HTTP. The `includeContext` values `"thisServer"` and `"allServers"` are Deprecated, and the legacy HTTP+SSE transport is formally Deprecated under the new lifecycle policy. New implementations should not adopt any of these.

**Transport and caching requirements** - Streamable HTTP POSTs must carry `Mcp-Method` and `Mcp-Name` headers, with custom headers namespaced under `x-mcp-header` (SEP-2243). List and read results must carry `ttlMs` and a `cacheScope` of `"public"` or `"private"` via `CacheableResult` (SEP-2549). `tools/list` SHOULD return a deterministic order so clients get prompt-cache hits.

**Schemas and errors** - tool `inputSchema` and `outputSchema` accept any JSON Schema 2020-12 keywords including `$ref`, and `structuredContent` may be any JSON value (SEP-2106). Resource-not-found moves from `-32002` to `-32602` for JSON-RPC alignment, and a new allocation policy reserves `-32020..-32099` for the spec while leaving `-32000..-32019` implementation-defined.

**Extensions and tracing** - `ClientCapabilities` and `ServerCapabilities` gain an `extensions` field using reverse-DNS identifiers, and OpenTelemetry trace context (`traceparent`, `tracestate`, `baggage`) travels in `_meta` (SEP-414).

**Authorization** - see [MCP Authorization](/docs/authentication/mcp-auth-model) for the six authorization changes, including `iss` validation per RFC 9207 and the deprecation of RFC 7591 dynamic client registration in favor of Client ID Metadata Documents.

## Advanced Capabilities [#advanced-capabilities]

### Sampling [#sampling]

Sampling allows an MCP server to request that the client (the AI model) generate a completion. This inverts the normal flow and enables servers to use the model's reasoning as part of their own logic.

```json
// Server → Client: sampling request
{
  "jsonrpc": "2.0",
  "id": 99,
  "method": "sampling/createMessage",
  "params": {
    "messages": [
      {
        "role": "user",
        "content": {
          "type": "text",
          "text": "Classify this invoice description as 'consulting', 'software', or 'hardware': Web development services for March 2025"
        }
      }
    ],
    "maxTokens": 10,
    "modelPreferences": {
      "costPriority": 0.8
    }
  }
}
```

Sampling requires the client to declare the `sampling` capability - during initialization in `2025-11-25`, or per request in `_meta` under the stateless `2026-07-28` core. Not all clients support it.

<Callout type="warn">
  Deprecated in `2026-07-28` (SEP-2577). Sampling stays available through the 12-month minimum
  deprecation window, but new servers should call their own LLM provider API directly instead. Where
  a server genuinely needs the client's model in the loop, the replacement path is a multi
  round-trip request returning `resultType: "input_required"`.
</Callout>

### Roots [#roots]

Roots tell the server which filesystem or resource locations the client has authorized it to access. A client running in a workspace might declare its project directory as a root:

```json
{
  "roots": [
    {
      "uri": "file:///Users/alice/projects/billing-app",
      "name": "Billing App Source"
    }
  ]
}
```

Servers that respect roots will not access paths outside the declared roots, providing a scope boundary for filesystem operations.

<Callout type="warn">
  Deprecated in `2026-07-28` (SEP-2577), along with the `notifications/roots/list_changed`
  notification, which is removed outright. Express scope boundaries as explicit tool parameters,
  resource URIs, or server configuration instead - all three survive the stateless core, whereas a
  client-declared root list does not.
</Callout>

### Elicitation [#elicitation]

Elicitation allows a server to request additional information from the user during a tool execution - mid-call user input rather than pre-declared parameters. A server handling a file operation might elicit confirmation before overwriting an existing file.

```json
{
  "jsonrpc": "2.0",
  "method": "elicitation/create",
  "params": {
    "message": "File report-2025.pdf already exists. Overwrite it?",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "overwrite": {
          "type": "boolean",
          "description": "Confirm overwrite"
        }
      },
      "required": ["overwrite"]
    }
  }
}
```

<Callout type="warn">
  Deprecated in `2026-07-28` (SEP-2577). Server-initiated `elicitation/create` stays available
  through the 12-month minimum deprecation window, but the replacement path is a multi round-trip
  request: the server returns `resultType: "input_required"` and the client retries with
  `inputResponses`.
</Callout>

### Progress Notifications [#progress-notifications]

For long-running tool operations, servers send progress notifications to keep clients informed without blocking on the final result:

```json
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "export-job-7821",
    "progress": 45,
    "total": 100,
    "message": "Processing invoice 2,247 of 5,000..."
  }
}
```

The client includes a `_meta.progressToken` in the original tool call request to opt into progress notifications for that call.

### Pagination [#pagination]

List operations (`tools/list`, `resources/list`, `prompts/list`) support cursor-based pagination for servers with large numbers of items:

```json
// Client request with cursor
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "resources/list",
  "params": {
    "cursor": "eyJwYWdlIjozfQ=="
  }
}

// Server response with next cursor
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "resources": [ ... ],
    "nextCursor": "eyJwYWdlIjo0fQ=="
  }
}
```

Omit `nextCursor` from the response to indicate the final page. Clients must not assume a specific page size.

## Transport [#transport]

### stdio (Local Processes) [#stdio-local-processes]

The stdio transport runs the MCP server as a subprocess. The client writes JSON-RPC messages to the process's stdin and reads responses from stdout. stderr is reserved for diagnostic logs.

This is the appropriate transport for:

* Desktop applications launching local tools
* Development tooling and IDE integrations
* Tools that require filesystem access on the user's machine

```json
// claude_desktop_config.json
{
  "mcpServers": {
    "billing": {
      "command": "npx",
      "args": ["-y", "billing-mcp@latest"],
      "env": {
        "STRIPE_API_KEY": "sk_live_..."
      }
    }
  }
}
```

The process is isolated at the OS level. Each MCP client instance gets its own subprocess. This provides natural process isolation - a crash in one MCP server does not affect others.

### Streamable HTTP (Remote Services) [#streamable-http-remote-services]

The Streamable HTTP transport is the preferred non-stdio transport for MCP servers. It runs the server as an HTTP endpoint where clients send JSON-RPC messages as POST requests. Responses can be:

* A single JSON response for request/response operations
* A Server-Sent Events stream for operations that produce multiple messages (progress notifications, streaming tool results, task polling)

```
POST /mcp HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJ...
MCP-Protocol-Version: 2025-11-25

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
```

Streamable HTTP replaced the earlier `http+sse` transport, which has been deprecated since the `2025-03-26` revision and is formally Deprecated under the `2026-07-28` lifecycle policy. The legacy SSE transport remains supported via fallback for backward compatibility with older clients, but should not be used in new implementations. Streamable HTTP scales horizontally since each request is stateless.

<Callout type="info">
  Current `2026-07-28` behavior: the `Mcp-Session-Id` header is removed, the GET endpoint that
  carried the server-to-client stream is replaced by `subscriptions/listen`, and SSE resumability
  via `Last-Event-ID` is gone. POST requests must instead carry `Mcp-Method` and `Mcp-Name` headers,
  with any custom headers namespaced under `x-mcp-header`. See [Transport
  Selection](/docs/mcp-servers/transports) for what this means for server code today.
</Callout>

## Security [#security]

### OAuth 2.1 for HTTP Transport [#oauth-21-for-http-transport]

Authorization is optional in MCP, but protected HTTP MCP servers should follow the MCP authorization specification. The server acts as an OAuth resource server, advertises protected-resource metadata, and points clients to one or more authorization servers:

```
1. Client receives `401 Unauthorized` with a `WWW-Authenticate` `resource_metadata` URL, or discovers protected-resource metadata at a well-known URI.
2. Client reads the `authorization_servers` value from protected-resource metadata.
3. Client discovers authorization server metadata using OAuth Authorization Server Metadata or OpenID Connect Discovery.
4. Client completes the supported OAuth flow and obtains an access token for the MCP server resource.
5. Client includes the bearer token in every protected MCP HTTP request.
```

The MCP server exposes protected-resource metadata, not necessarily authorization-server metadata:

```
GET /.well-known/oauth-protected-resource
```

Tokens must be validated on every request - not cached across sessions. Validate the audience (`aud`) claim to ensure tokens issued for other services are rejected.

### Process Isolation for stdio [#process-isolation-for-stdio]

Each MCP server launched via stdio runs as its own OS process. This provides:

* Memory isolation between MCP servers
* Separate environment variable namespaces
* OS-level resource limits per server
* Clean failure containment - a crashing server does not cascade

Do not share credentials between MCP servers. Each server should have its own scoped credentials with the minimum permissions required for its domain.

### Defense in Depth [#defense-in-depth]

```
┌─────────────────────────────────────┐
│  Network Layer                      │
│  TLS, rate limiting, IP allowlists  │
├─────────────────────────────────────┤
│  Authentication Layer               │
│  Bearer token / OAuth 2.1 with PKCE │
├─────────────────────────────────────┤
│  Authorization Layer                │
│  Scope validation per tool,         │
│  resource-level access control      │
├─────────────────────────────────────┤
│  Input Validation Layer             │
│  JSON Schema on every tool call,    │
│  reject unknown fields              │
├─────────────────────────────────────┤
│  Business Logic Layer               │
│  Domain invariants, idempotency,    │
│  per-customer rate limits           │
└─────────────────────────────────────┘
```

Each layer fails closed. A request that cannot be authenticated is rejected, not downgraded to reduced permissions.

## Capability Declaration [#capability-declaration]

Declare capabilities at server construction time. Only declare what you actually implement - undeclared capabilities are simply absent from the server's offerings, which is always preferable to declaring a capability and failing to implement its handler.

```json
{
  "capabilities": {
    "tools": {},
    "resources": {
      "subscribe": true,
      "listChanged": true
    },
    "prompts": {},
    "logging": {}
  }
}
```

Declaring `resources.subscribe: true` without implementing `resources/subscribe` will produce protocol errors that are difficult to trace back to the missing handler.

<Callout type="info">
  Current `2026-07-28` behavior: capabilities travel per request rather than being negotiated once,
  the `logging` capability is Deprecated, and `resources.subscribe` gives way to opting into
  `resourceSubscriptions` on the `subscriptions/listen` stream. Capability objects also gain an
  `extensions` field carrying reverse-DNS extension identifiers such as
  `io.modelcontextprotocol/tasks`.
</Callout>

## Spec & References [#spec--references]

* [MCP Specification 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/) - the predecessor revision, for compatibility work with clients that have not migrated
* [MCP 2026-07-28 specification](https://modelcontextprotocol.io/specification/2026-07-28/) - current normative specification
* [MCP 2026-07-28 release](https://blog.modelcontextprotocol.io/posts/2026-07-28/) - migration summary and SDK support status
* [modelcontextprotocol.io](https://modelcontextprotocol.io) - official docs and server registry

## Related Pages [#related-pages]

* [A2A Protocol](/docs/protocols/a2a) - agent-to-agent delegation, the horizontal complement to MCP
* [Protocol Comparison](/docs/protocols/comparison) - when to use MCP vs A2A vs ACP
