Skip to content

Extensions and the Road Ahead

The 2026-07-28 revision deliberately shrank the core protocol: sessions, the handshake, ping, logging/setLevel, and server-initiated requests all left. What remains is the minimum every implementation must speak. Everything richer now lives in extensions, which are optional, additive, composable, and versioned on their own.

That is good news for you as a server author: you implement the core once and opt into exactly the extensions your use case needs.

flowchart TB
  core["Core: JSON-RPC, _meta, server/discover, tools, resources, prompts, MRTR, subscriptions"]
  core --> t["io.modelcontextprotocol/tasks"]
  core --> u["io.modelcontextprotocol/ui  (MCP Apps)"]
  core --> a["ext-auth extensions"]
  core --> v["com.yourco/... vendor extensions"]
Core plus extensions

Both sides advertise extensions in an extensions map inside their capabilities. Keys follow the _meta naming rules with a mandatory prefix: official ones under io.modelcontextprotocol/, yours under your own reverse-DNS prefix. The value is a per-extension settings object; {} means “supported, no settings”.

Client, on every request:

{
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {},
"extensions": {
"io.modelcontextprotocol/tasks": {},
"io.modelcontextprotocol/ui": { "mimeTypes": ["text/html;profile=mcp-app"] }
}
}
}
}

Server, in server/discover:

{
"capabilities": {
"tools": { "listChanged": true },
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}

The fallback rule is simple: if one party supports an extension and the other does not, the supporting party MUST either behave as plain core or reject the request with a clear error. Extensions SHOULD document which they do. A server must never return an extension-shaped result (for example a task handle) to a client that did not declare the extension on that request.

io.modelcontextprotocol/tasks is for work that outlasts a request: CI runs, batch imports, human approvals. Instead of holding the connection open, the server returns a durable handle and the client polls. The earlier lesson on long-running work covers the mechanics; the shape in one paragraph:

  • The client declares the extension on the request. The server, if it chooses, answers tools/call with resultType: "task" carrying a taskId, a status, a ttlMs, and a pollIntervalMs.
  • The client calls tasks/get until the status is terminal (completed, failed, or cancelled). If the status is input_required, the response carries inputRequests (an elicitation, say) and the client answers with tasks/update.
  • tasks/cancel is cooperative. Servers may push notifications/tasks to clients that opened subscriptions/listen for them, which saves a poll.

Tasks moved out of the core in 2026-07-28, and the redesign replaced the blocking tasks/result with polling. If you read an older tutorial, that is why the method names differ.

io.modelcontextprotocol/ui lets a tool return an interactive HTML interface — a chart, a form, a dashboard — that the host renders inside the conversation instead of a wall of text. How it fits together:

  1. The tool definition carries _meta.ui.resourceUri pointing at a ui:// resource. The host can preload it before the tool is even called.
  2. That resource is an HTML page (usually bundled with its JS and CSS) served with the MIME type text/html;profile=mcp-app. The resource’s _meta.ui can request permissions and declare a csp for external origins.
  3. The host renders it in a sandboxed iframe. The app cannot touch the parent DOM, cookies, or storage.
  4. App and host talk over postMessage using a JSON-RPC dialect of MCP: some methods are shared (tools/call), some are new under a ui/ prefix (ui/initialize). The app can call tools on its own server, push context to the model, and receive fresh results from the host.

The @modelcontextprotocol/ext-apps package provides an App class for the app side and an App Bridge for hosts, with starter templates for React, Vue, Svelte, Preact, Solid, and plain JavaScript. It is a convenience, not a requirement; the postMessage protocol is standard web primitives. Hosts that support Apps today include Claude, Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, MCPJam, and Archestra; the client matrix on the MCP site tracks the list.

Reach for an App when the user needs to interact with data (drill into a map, fill a many-option form, watch live metrics), not merely read it.

The core authorization spec covers OAuth 2.1 between client and server. Additional mechanisms — enterprise identity patterns, alternative credential flows — live in the modelcontextprotocol/ext-auth repository as optional, composable extensions. Check there before inventing a custom scheme; the odds are good someone has specified yours.

Since 2026-07-28 every feature is in one of three states, governed by a written policy:

StateMeaning
ActivePart of the spec; implement it.
DeprecatedStill in the spec and still functional, with a documented migration path (or a note that none is needed). Stays at least twelve months (ninety days under the expedited exception). New implementations should not adopt it.
RemovedGone from a later revision.

The deprecated features registry at /specification/2026-07-28/deprecated is the authoritative list. As of this revision it includes:

DeprecatedUse instead
Sampling (sampling/createMessage)Call the LLM provider’s API directly from the server
Roots (roots/list, notifications/roots/list_changed)Pass directories or files as tool parameters, resource URIs, or server configuration
Logging feature (notifications/message opt-in remains via _meta log level)stderr on stdio, or OpenTelemetry
HTTP+SSE transport (from 2024-11-05)Streamable HTTP
Dynamic Client Registration (RFC 7591)Client ID Metadata Documents
includeContext values thisServer / allServersOmit the field or use none

Deprecated does not mean broken: a 2025-11-25 server using sampling keeps working during the window. It means do not start.

Changes to MCP go through Specification Enhancement Proposals. A SEP is a markdown file opened as a pull request in the seps/ directory of the specification repository; it gets its number from the PR, needs a sponsor from the core team, and moves through status labels until it is accepted or rejected. The changelog for each revision cites the SEPs behind every change (SEP-2575 for statelessness, SEP-2322 for MRTR, SEP-2549 for caching fields, and so on), so when a behaviour puzzles you, the SEP is where the reasoning lives.

If you want to influence the protocol, that is the door: open a discussion, write the SEP, find a sponsor.

Most tutorials on the web predate this revision. Use this table to translate them:

Older revisions (2025-11-25 and earlier)2026-07-28
initialize / notifications/initialized handshakeRemoved; every request carries io.modelcontextprotocol/protocolVersion and clientCapabilities in _meta
Mcp-Session-Id header, HTTP DELETE to end a sessionRemoved; the protocol is stateless, state uses explicit handles
HTTP GET opens a server-to-client streamRemoved; subscriptions/listen is a long-lived POST response
Server sends elicitation/create, sampling/createMessage, roots/list as requestsMRTR: server returns resultType: "input_required" with inputRequests; client retries with inputResponses
resources/subscribe / unsubscribesubscriptions/listen with resourceSubscriptions
Results have no type markerEvery result has resultType (complete, input_required, or an extension value such as task)
ping, logging/setLevel, notifications/roots/list_changedRemoved; log level per request via _meta
SSE resumability with Last-Event-IDRemoved; re-issue a lost request with a new id
Optional MCP-Protocol-Version headerRequired, plus Mcp-Method and Mcp-Name on POSTs; mismatch is -32020
Errors -32001 / -32003 / -32004 in the draft; -32002 resource not found-32020 HeaderMismatch, -32021 MissingRequiredClientCapability, -32022 UnsupportedProtocolVersion; resource not found is -32602
No caching hintsttlMs and cacheScope on list results, resources/read, and server/discover
Tasks in the coreTasks are an extension, polling via tasks/get
No mandatory discovery RPCserver/discover is mandatory for servers, optional for clients
Where do a client and a server advertise support for an extension?
Which is a deprecated feature as of 2026-07-28?
How does a tool tell the host that it has an MCP App UI?
What is the minimum deprecation window under the feature lifecycle policy?