Skip to content

Extensions ​

Protocol extensions are negotiated via the extensions map on ClientCapabilities and ServerCapabilities. Each entry maps an extension identifier to its configuration value.

Server-Side Auto-Derivation ​

Setting a task_store on ServerOptions automatically declares the tasks extension:

cpp
auto transport = std::make_shared<StdioServerTransport>();

ServerOptions opts;
opts.task_store = std::make_shared<FileTaskStore>("tasks.json");

auto server = McpServer::Create(transport, opts);

There is no ServerOptions.capabilities field — capabilities are always auto-derived:

ConditionDeclared extension keyValue
task_store is set"io.modelcontextprotocol/tasks"Empty object {}
At least one skill registered (RegisterSkill) and the resources capability is present"io.modelcontextprotocol/skills"Empty object; contains "directoryRead": true after SetSkillsDirectoryRead(true)

For client-side declarations see Client Overview (ClientOptions::capabilities.extensions or ClientOptions::extensions carrying the tasks extension key); in the 2026 era a client that receives a task result without declaring the tasks extension throws HandlerError.

Task Notification Routing ​

In the 2026 era, task status notifications (the initial Working and terminal state of taskified tools/call, tasks/completed/tasks/working from tasks/update, tasks/cancelled from tasks/cancel) are no longer suppressed — they are routed via the server's subscription table to all current subscribers (task notifications bypass filters), with each frame carrying io.modelcontextprotocol/subscriptionId in _meta (echoing the ID passed to subscriptions/listen, or a server-assigned incrementing ID otherwise); with no subscribers the notification is dropped, and clients poll to the terminal state with PollTaskToCompletion (a positive task poll_interval_ms switches to the server-indicated interval, capped at 300s). The 2025 era keeps broadcast delivery (notifications/tasks/*).

Skill URI Shape and Verification Helpers ​

A skill entry uri must have the shape <scheme>://<name>/SKILL.md (scheme-agnostic: only a non-empty ://, a non-empty name segment, and the /SKILL.md suffix are required; skill:// remains the conventional scheme). The server only enforces the shape; whether to trust a foreign-origin skill is decided client-side with the five helpers in mcp/client/SkillVerify.hpp: CheckSkillLimits (manifest ≤ 512 files, ≤ 16 MB total), VerifyFileBytes (size length match + sha256: digest recomputation), VerifyFrontmatter (both copies equal and name matching the URI name segment), IsApprovalStillValid (approval snapshot re-check), ApprovalKey ("<host>\x1f<skill-uri>" approval key).

Extension Convention ​

FieldDescription
KeyReverse-DNS identifier (e.g., "io.modelcontextprotocol/tasks")
ValueFree-form JSON object with the extension's configuration

Experimental Flags ​

The 10 ServerOptions flags and 4 ClientOptions flags all default to false and must be explicitly set to true (flags sync at construction: McpServer::Create copies options by value, later mutations of the caller's options have no effect; etag is process-global — any server constructed with it on enables process-wide etag reads/writes):

SideFlagOnOff
Serverenable_tasks_extensionDeclares the tasks extension; tasks/get|update|cancel and taskified tools/call take effectWithout task_store, the corresponding methods return MethodNotFound (tasks extension is disabled); with only task_store set, no longer declared — explicit enable_tasks_extension=true is required (implicit path removed, see below)
Serverenable_skills_extensionWith at least one registered skill and the resources capability, declares the skills extension; skills/list|get and directory/read take effectAll three methods return MethodNotFound (skills extension is disabled); no implicit path
Serverenable_etag_cacheList results carry a content-hash etag (skipped past the 8 MB cap); on a notModified reply clients fall back to the stored body, sending ifNoneMatch via the meta extensionsNo etag attached; client store access short-circuits, unconditional sends
Serverenable_progressive_discoverytools/list supports query filtering + pageSize paging; listen flushes and holds open, list_changed buffers per subscriptiontools/list serves the cached full set; listen and list_changed use the legacy paths
Serverenable_file_inputfile:///resource:// references inside tools/call arguments are range-sliced and small files inlined (http(s) passes through; out-of-range/over-limit returns an error)Arguments pass through untouched
Serverenable_triggers_eventsRegistry + type-matched emit, delivered via subscription-filtered trigger event notifications plus webhooks (failures only log a Warning)Emit returns immediately; events are silently dropped
Serverenable_dpop (ServerOptions::enable_dpop has no read site and does not take effect; the actual switch is transport-side bearer_auth->enable_dpop)DPoP proof verification added to the Bearer chain by the transport's Bearer auth config (401 with nonce on failure, jti replay protection; hard error without OpenSSL); the field is only declared in ServerOptions and has no read site repository-widePlain Bearer chain
Serverenable_interceptor_aliasAlias as implementation: Interceptor aliases MessageFilter, registration forwards to the existing pipeline with zero behavior change; the flag itself has no gatingSame as left
Serverenable_apps_extension / enable_server_cardReserved: declared, default false, no gating wired yetSame as left
Clientenable_tasks_extensionDeclared when the extensions map carries the tasks key; UpdateTask/PollTaskToCompletion and the task-handle path take effectThose calls throw MethodNotFound (tasks extension is disabled)
Clientenable_skills_extensionListSkills/GetSkill/ReadDirectory take effectAll three calls throw MethodNotFound (skills extension is disabled)
Clientenable_progressive_discoverySearchTools paging requests carry query/pageSizePaged aggregation still works but without query/pageSize
Clientenable_apps_extensionReserved: declared, default false, no gating wired yetSame as left

Compatibility transition (0.3.5→0.3.8, scope given by this task): TasksImplicitlyEnabled() / SkillsImplicitlyEnabled() are both marked [[deprecated]], directing users to set the flags explicitly; the implicit path has been removed — setting just task_store no longer declares the tasks extension and logs no Warning, explicit enable_tasks_extension=true is required; skills has no implicit path and must be enabled explicitly.