skip to content

Optional Extensions

Features layered on top of the core protocol and declared in the extensions capability map, such as Tasks and MCP Apps. Interviewers ask what left the core protocol in 2026-07-28.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

questions

10

How do MCP peers declare extension support in the extensions capability map?

level: middleimportance: must knowfreq 58%

answer

  1. both sides have to say yes
  2. a map, not a list
  3. key is the identifier, value is settings
  4. an empty object still means yes

basics

~20 s

Each side lists supported extensions in the extensions field of its capabilities: a map from a prefixed identifier to a settings object. An empty object means supported with no settings, not unsupported. An extension is live only if both sides list it.

solid answer

~40 s

In MCP revision 2026-07-28, `extensions` is a field on both `ClientCapabilities` and `ServerCapabilities`. It is a map, not a list: the key is the prefixed extension identifier, such as `io.modelcontextprotocol/tasks`, and the value is that extension's settings object. An **empty object is a positive declaration** — it means "supported, with nothing to configure" — which is the shape most declarations take. Opt-in is mutual and explicit: an extension is only in play when it appears in **both** peers' maps, so a client declaring it against a server that does not is simply a client with an unused capability. Because MCP is stateless in 2026-07-28, capabilities accompany the request rather than being established once, so the opt-in is re-asserted on each request and a server must not carry an earlier one forward.

code

json · 9 lines
json
{
  "elicitation": {},
  "extensions": {
    "io.modelcontextprotocol/tasks": {},
    "com.example/replay": {
      "maxAttempts": 3
    }
  }
}

go deeper

for a junior

Recall that support is declared in an extensions map keyed by the extension identifier, and that an empty object under a key still means the extension is supported.

for a middle

Explain the map's key/value shape and state the mutual opt-in rule plainly: an extension is only active when it appears in both peers' maps, and absence of a key is what means non-support.

for a senior

Demonstrate that you read the opt-in off the request in front of you rather than caching it, and that your code tolerates extension identifiers it has never seen without erroring.

for a principal

Be ready to discuss what belongs in an extension's settings object versus what should be a separate extension, and how you keep a fleet's support matrix observable once several extensions are in play.

## The shape The declaration lives in one field, `extensions`, present on both `ClientCapabilities` and `ServerCapabilities` in MCP revision 2026-07-28. Its type is a map: - **key** — the extension identifier, namespace-prefixed in the same style MCP uses for prefixed `_meta` keys: dot-separated reverse-DNS labels, a slash, then a name (`io.modelcontextprotocol/tasks`, `io.modelcontextprotocol/ui`, or a vendor's own `com.example/replay`). - **value** — a settings object belonging to that extension. Its contents are defined by the extension, not by the core spec. Being a map rather than a list is the design point: it gives every extension a slot for configuration without the core protocol having to know what any of it means. ## The empty object is not nothing The single most common misreading is treating `{}` as "no support" or "unset". It is the opposite. `"io.modelcontextprotocol/tasks": {}` means **I support Tasks and have no settings to pass**. Absence of the key is what means no support. Implementations that fall for this either fail to advertise a feature they actually implement, or refuse a peer that legitimately advertised one. ## Opt-in is mutual and explicit An extension exists for a client-server pair only when it appears in **both** maps. That has some blunt consequences: - A client listing an extension the server has never heard of has not committed anyone to anything. The server treats the unknown key as inactive, not as an error. - A server that implements an extension does not get to use it against a client that did not list it. - There is no "default on" tier. Nothing is implied by silence except non-support. This symmetry is why the mechanism is called opt-in negotiation rather than advertisement: the intersection of the two maps is the working feature set. ## Where the maps travel A client's capabilities, including its extensions map, accompany each request rather than being registered once — revision 2026-07-28 made MCP stateless, and its own words are that every request is self-contained and carries its own protocol version and capabilities. A server's capabilities, including its extensions map, come back from `server/discover`. The carrier mechanics belong to per-request metadata and discovery respectively; what matters for negotiation is the consequence: **the opt-in is a property of the request in front of you, not of the connection.** A server that remembers "this client supports Tasks" from an earlier call and applies it to a later one that did not declare it has broken the statelessness rule. Conversely, a client must not assume a server's extension support survives a `ttlMs` expiry of its discovery result without re-checking. ## Practical implementation notes - **Read the map, do not scan for a boolean.** Extension support is key presence, and settings live under that key. Code that checks `capabilities.extensions != null` and stops has learned nothing. - **Namespace your own.** If you define an extension, put it under a prefix you control. `io.modelcontextprotocol` is the specification's own namespace. - **Emit an empty object rather than omitting the key** when you support an extension with default settings. Omission is a claim of non-support. - **Unknown keys are inert, not fatal.** A peer must be able to receive an extensions map containing identifiers it has never seen and carry on with core behaviour. - **Do not conflate with versioning.** The protocol revision (`YYYY-MM-DD`) says which spec you speak; the extensions map says which optional features you have on top of it. A peer can be perfectly current on revision and declare an empty extensions map. ## What good answers sound like The answer an interviewer is listening for names the field, gets the map-of-identifier-to-settings shape right, states that `{}` is a yes, and closes with the mutual-opt-in rule. If you also point out that statelessness makes the declaration per-request rather than per-connection, you have shown you understand the 2026-07-28 model rather than reciting the handshake era it replaced.

  • What does MCP's statelessness in 2026-07-28 change about an extension opt-in?
    It makes the opt-in per request rather than per connection. Capabilities accompany the request, so a server evaluates the extensions map it was just given and must not carry an earlier request's declaration forward. Practically, an implementation behind a load balancer needs no shared memory of who opted into what — whatever node handles a request has everything it needs in that request.
  • If a client's extensions map lists an identifier the server has never heard of, what should the server do?
    Nothing special — treat it as inactive and proceed with core behaviour. Unknown extension identifiers are not a protocol error; the mutual opt-in simply fails to complete, so the extension is not in play. Rejecting the request because an unrecognised key was present would break forward compatibility with clients that support newer extensions.
  • Can a peer declare an extension and then not use it?
    Yes. The declaration advertises capability; it does not commit either side to exercising it. Most requests over a connection where both sides support an extension will not involve that extension at all.

saying these in an interview costs you the question

  • Reads an empty settings object as 'not supported'
  • Declares an extension on one side only and expects it to engage
  • Uses an unprefixed identifier like 'tasks' as the map key
  • Treats an unknown extension identifier as a protocol error
  • Believes one declaration holds for the life of the connection

context

open as a page

In MCP, what does a server return when a tools/call runs as an asynchronous task?

level: middleimportance: must knowfreq 62%

basics

~20 s

The server answers immediately with a CreateTaskResult: resultType "task" plus a taskId. The work keeps running server-side, and the client polls tasks/get with that taskId until the task reaches a terminal status and yields its result.

open as a page

In MCP, what must happen when one side does not support a requested extension?

level: seniorimportance: must knowfreq 48%

basics

~20 s

The extension simply does not apply. Under MCP revision 2026-07-28 the other side MUST either revert to core protocol behaviour or reject the request outright. Proceeding as though the extension were in effect is not an option.

open as a page

In MCP, what is an optional extension, and which ones are official?

level: juniorimportance: should knowfreq 52%

basics

~10 s

An MCP extension is an optional feature layered on top of the core protocol and used only when both peers declare it. Revision 2026-07-28 names two official ones: io.modelcontextprotocol/tasks (Tasks) and io.modelcontextprotocol/ui (MCP Apps).

open as a page

In MCP's tasks extension, what task statuses exist and which of them are terminal?

level: middleimportance: should knowfreq 47%

basics

~20 s

A task is working, input_required, completed, failed or cancelled. The first two are live states the client keeps polling; completed, failed and cancelled are terminal. Cancellation is cooperative — tasks/cancel requests it, the server decides when the task actually stops.

open as a page

In MCP's tasks extension, how does a client feed input to a task already running?

level: middleimportance: should knowfreq 38%

basics

~10 s

When a task reports the input_required status, the client calls tasks/update with that taskId and supplies what the server asked for. The task then resumes. A running task is never re-issued — only fed.

open as a page

How can an MCP client learn a task advanced without polling tasks/get in a loop?

level: seniorimportance: should knowfreq 34%

basics

~20 s

A server may push notifications/tasks over a subscriptions/listen stream, so the client learns of transitions instead of polling tightly. It is optional and best-effort: a robust client still polls tasks/get after any reconnect, because a push delivered while the stream was down is simply lost.

open as a page

Why do MCP tasks live in the io.modelcontextprotocol/tasks extension rather than core?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Revision 2026-07-28 kept the core protocol small and moved long-running work into the optional io.modelcontextprotocol/tasks extension. Both sides must opt in; the client does so once through its capabilities, and a server facing a client that did not must answer synchronously instead.

open as a page

When should an MCP server run a tool as a task instead of answering synchronously?

level: principalimportance: should knowfreq 30%

basics

~20 s

Use a task when the work outlives what a single request can safely hold open — minutes, human approvals, or anything that must survive a dropped connection. Keep short, cheap, idempotent calls synchronous: a task adds durable state, polling and a retention policy nobody needs for a two-second query.

open as a page

When should an MCP capability ship as an extension rather than in the core protocol?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Ship it as an extension when only some deployments need it and a peer without it can still be served sensibly. Core is the floor every implementation must clear, so anything universal and unavoidable belongs there — everything else belongs behind an opt-in identifier.

open as a page