MCP extensions: how the opt-in layer works and which ones are worth adopting
The 2026-07-28 MCP revision made extensions an official part of the protocol and moved tasks out of core. Here is how extensions are negotiated, what the lifecycle stages promise, and how to decide whether your server should use Apps, Tasks, Skills or the auth extensions.
Most of the attention on the 2026-07-28 MCP revision went to the stateless rewrite: no more initialize handshake, no more session IDs, and a new server/discover call. We covered that in our piece on the stateless revision. A quieter change in the same release matters just as much if you build servers or clients. The changelog adds an extensions field to both client and server capabilities, and it moves experimental tasks out of the core protocol and into an official extension called io.modelcontextprotocol/tasks.
So MCP now has two layers. The core spec is what every conforming client and server has to speak. Extensions are optional features on top, and neither side uses one unless both sides say they support it. If you have been wondering why some features you read about in a working group thread never showed up in the spec, this is usually why. They live in extension repositories now, on their own release schedule.
This article goes through how that layer actually works on the wire, what the lifecycle labels mean, and how to decide which of the current official extensions belong in something you run.
What counts as an extension
The extensions overview describes them as "optional additions to the specification that define capabilities beyond the core protocol," and gives three reasons something ends up as one: it is modular (authentication is the example), specialized (industry-specific logic), or experimental (being incubated for a possible move into core).
Each extension has an identifier in the form {vendor-prefix}/{extension-name}. Official ones use the io.modelcontextprotocol prefix, so you will see io.modelcontextprotocol/ui for MCP Apps and io.modelcontextprotocol/oauth-client-credentials for the machine-to-machine auth flow. If you write your own, the docs tell you to use a reversed domain you own, Java-package style, so a company at example.com would ship com.example/my-extension. That sounds like a formality until two vendors both invent something called approvals and your client has to guess which one a server means.
The line in the overview that should shape how you think about all of this is short: "Extensions are always disabled by default and require explicit opt-in from the developer, at every stage." Nothing turns on because a library upgraded.
How negotiation works now
Because the 2026-07-28 revision dropped the handshake, there is no single moment where both sides trade capabilities anymore. Instead, the client sends its capabilities on every request, inside _meta, and the server publishes its own in the server/discover response. The overview's example is a plain tools/call whose _meta block carries the protocol version, client info, and an extensions object:
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/ui": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
}
On the server side, server/discover returns capabilities.extensions with the same identifiers. Each extension defines its own settings object, and an empty object just means "supported, no settings." The Skills extension is a good example of a non-empty one: a server can declare "directoryRead": true to say it also supports reading whole skill directories.
There is a practical wrinkle for anyone supporting older clients. Extensions only became official in 2026-07-28, but the overview admits that "many SDKs, clients, and servers already negotiated extensions on 2025-11-25 and earlier" by putting the same extensions object inside the old initialize request and response. It is not in the published schema for those versions and is not required for conformance. If you serve both protocol eras, check that your SDK actually passes that field through initialize, and if it doesn't, assume the extension is off for those clients.
That leads to the rule you will apply most often, which the docs call graceful degradation. If one side supports an extension and the other does not, the side that supports it either falls back to core behavior or rejects the request if the extension is mandatory. Their example is a server with UI-enhanced tools, which "should still return meaningful text content for clients that don't support the UI extension." In practice, write the plain text result first and treat the extension as a nicer rendering of it, not a replacement.
The lifecycle labels are a contract
Every extension sits at one of three stages, defined in SEP-3392, and each stage tells you what you can expect from it.
Experimental means the design "may change or disappear," and implementers should pin to a tag or commit because two revisions need not be compatible. These live under specification/experimental/ or in repositories named experimental-ext-*. Beta means real usage is welcome and breaking changes are documented, though they are "expected to be rare." A Beta extension that breaks something should bump a revision string in its settings object so the other side can detect the mismatch. Stable means behavior is fixed within a protocol revision. Stable specs are published in folders keyed to a core revision, like specification/stable/<name>/2026-07-28/, and a published revision is immutable.
Getting from Beta to Stable is not just a vote. The second Extensions Track SEP needs two independent implementations that interoperate, one client-side and one server-side, plus a conformance scenario. The alternative path out of Beta is promotion into the core spec itself, and the overview spells out what happens then: from protocol revision R onward the feature is core behavior whether or not anyone advertises the extension, while clients that still speak earlier revisions should keep advertising it.
For a builder, the takeaway is simple. Experimental is for prototypes you are willing to rewrite. Beta is fine for production if you watch the changelog and check that revision string. Stable is the one you can build on and forget about until the next core revision.
The official extensions today
Here is where things stood when we checked the overview on November 3, 2026, based on the official ext- repositories.
MCP Apps is Stable, and it lets a tool render interactive HTML inside the conversation. A tool declares a _meta.ui.resourceUri pointing at a ui:// resource, the host fetches that HTML, and web hosts usually render it in a sandboxed iframe. That fits charts, forms and dashboards where text falls short. The docs say plainly that if your use case does not benefit from those properties, "a regular web app might be simpler." Take them at their word.
The two auth extensions in the ext-auth repository are both Stable. OAuth Client Credentials covers the case the standard MCP flow handles badly: no human present. Background jobs, CI pipelines and daemons can authenticate with application credentials instead of a browser redirect, and the docs recommend JWT bearer assertions (RFC 7523) over a shared secret. Enterprise-Managed Authorization is for companies that want access to MCP servers controlled centrally through their identity provider. If you run scheduled agents, the client credentials extension is the one to read first, because it replaces whatever workaround you are using now to keep tokens alive.
Tasks is Beta, with core as its stated destination. Its job is long-running work. A server that decides a request will take a while returns a result with resultType: "task" holding a taskId, a TTL and a suggested pollIntervalMs. The client polls with tasks/get until the status is completed, failed or cancelled. If the task needs something along the way, like a user confirmation, it moves to input_required and the client answers through tasks/update. The changelog notes what changed from the 2025-11-25 experimental version: the blocking tasks/result method is gone in favor of polling, tasks/list was removed, and servers can now return task handles without a per-request opt-in. The docs also warn about one thing that will bite you if you skip it: "Never return a task to a client that did not declare support." And on the client side, store task IDs durably, because surviving a restart is the whole point.
Skills over MCP is Beta with no stated destination yet. It lets a server publish Agent Skills, meaning a directory with a SKILL.md and supporting files, through skills/list, skills/get and ordinary resources/read. One detail stands out for security: reading a SKILL.md "does not itself activate a skill." The host is supposed to route it through its own loading path, verify it and apply any required user approval. That matches the stance in our prompt injection piece: instructions from a server are input to check, not commands to follow.
Server Cards is also Beta. SEP-2127 describes a metadata document a client can read before it connects, covering transports, protocol versions and connection guidance, with <streamable-http-url>/server-card reserved as the recommended location. It solves a different problem from server/discover, which only works once you already know where the server is.
Check the client before you build
The extension support matrix is maintained by the community and lists hosts like Claude, VS Code GitHub Copilot, ChatGPT, Cursor, Goose and the MCP Inspector against each extension. Read it before you commit to anything, and read the linked notes too. When we looked, several entries for Skills were marked "Partial," with links explaining what partial means for that client. An extension only helps you if the clients your users actually run support it.
A sensible way to adopt them
If you maintain a server, start with core features and add extensions only where they solve a problem you can name. Machine-to-machine access is a real problem with a Stable answer. Long-running jobs are a real problem with a Beta answer that is headed for core, so it is reasonable to build on Tasks now as long as you keep a synchronous fallback for clients that don't declare it. Apps are worth it when a chart or form genuinely beats text. In every case, advertise the extension in server/discover, check the client's per-request capabilities before using it, and return something useful in core terms when the client doesn't support it.
If you maintain a client, the work is mostly bookkeeping: send the extensions you support in every request's _meta, read the server's list from server/discover, handle the result shapes the extension can return, and send a pull request to update the matrix when you ship. None of it is hard. The thing to avoid is turning an extension on just because it exists.
Sources
MCP specification 2026-07-28: Key Changes
MCP Tasks extension and the ext-tasks repository
MCP Apps and the ext-apps repository
OAuth Client Credentials extension
Skills over MCP and the ext-skills repository