Development Choices

Capability negotiation for MCP extensions

Author
Joseph TrasattiMember of technical staff
Published
Section
MCP
Length
11 min read3 sources cited

MCP extension negotiation is an explicit intersection of advertised client and server capabilities. An implementation may use an optional extension only when both sides name its vendor-prefixed identifier and agree on its contract. Third-party extensions need independent versioning, namespace ownership, and a useful core-protocol path when support is absent.

Capability negotiation in one sentence

Capability negotiation for MCP protocol extensions is the explicit agreement that allows a client and server to use an optional contract beyond the core protocol. Both sides advertise support, and extension behavior stays off unless their declarations match.

Extensions are additive contracts

Matrix comparing MCP core, official extensions, and vendor extensions
Extension support is negotiated; it is never safe to infer.

An MCP extension adds a capability without changing what the core protocol means. The MCP extensions overview, accessed August 26, 2026, defines extensions as optional additions for modular, specialized, or experimental behavior. Implementing one is not required for core protocol conformance.

Each extension has an identifier composed of a vendor prefix, a slash, and an extension name:

{vendor-prefix}/{extension-name}

Official extensions reserve the io.modelcontextprotocol prefix. A third-party publisher should use a reverse-domain prefix it controls, such as com.example, producing an identifier such as com.example/audit-hints. The prefix is the ownership boundary: it prevents two publishers from assigning different meanings to the same name and lets an implementation determine which contract governs the advertised settings.

The identifier names the extension, not a method that happens to exercise it. That distinction matters because an extension may alter the representation of a core result, add optional metadata, define additional methods, or combine those elements. None of those signals independently proves that the peer supports the whole extension contract. The declared identifier does.

Additive also sets a design boundary. An extension may define new behavior around a core method, but it should not redefine the core method itself. A client that knows only the core protocol must still be able to interpret the core portion according to the core specification. The same rule applies between extensions: com.example/audit-hints cannot assign new meaning to fields or methods owned by org.other/audit-policy.

An extension is therefore the wrong place for a silent change to existing semantics. If a server changes the meaning of tools/call only when it recognizes a particular method name or payload shape, the contract is implicit and cannot be negotiated safely. The extension identifier and its settings must carry the agreement.

Where each side advertises support

The protocol gives client and server capabilities separate locations because each declaration answers a different question. The 2026-07-28 schema reference defines an optional extensions map in both ClientCapabilities and ServerCapabilities. Keys are extension identifiers, and values are settings objects defined by the individual extensions. An empty object means support without additional settings.

Under the 2026-07-28 protocol, a client includes its capabilities in _meta["io.modelcontextprotocol/clientCapabilities"] on each request. An illustrative declaration is:

{
  "io.modelcontextprotocol/clientCapabilities": {
    "extensions": {
      "com.example/audit-hints": {
        "contractVersion": "1"
      }
    }
  }
}

The property contractVersion is illustrative rather than a core-defined field. Its name and allowed values belong to the extension specification. The core schema only establishes that each map value is a settings object.

A server advertises its extension map inside capabilities in a server/discover result:

{
  "capabilities": {
    "tools": {},
    "extensions": {
      "com.example/audit-hints": {
        "contractVersion": "1"
      }
    }
  }
}

server/discover lets a client inspect server support before relying on an extension, but discovery is optional for clients. The client declaration remains attached to requests. This fits the July 28, 2026 specification release model: requests carry their protocol version, client identity, and client capabilities, while discovery is available when a client wants the server’s capabilities in advance.

Extension negotiation and core MCP capability negotiation use the same underlying discipline: declared capabilities authorize behavior; incidental evidence does not. Extension identifiers simply provide namespaced slots for contracts outside the core capability set.

Protocol-version agreement is a separate layer. A request can use protocol version 2026-07-28 while advertising version 1 of a third-party extension contract. Changing the MCP protocol version does not by itself select an extension version, and selecting an extension version does not repair a core protocol version mismatch. An implementation needs to evaluate both agreements.

Mutual support is the gate

Extension behavior is available only in the intersection of client and server support. It is not enough for one side to implement the code, publish a method, understand a field, or advertise the identifier alone.

For server-originated extension behavior, the server checks that the client declared the extension before emitting a representation or requesting an action that depends on it. For client-originated behavior, the client checks the server’s advertised capabilities before sending an extension-dependent request. The direction of the message does not replace negotiation; it determines which side is about to rely on the other side’s support.

The practical gate has three parts:

Matching identifiers establish shared recognition, but they do not automatically make every settings combination compatible. If one peer advertises contract version 1 and the other supports only version 2, both know the extension name while lacking a usable agreement. The extension specification must say how to select a version, interpret omitted settings, and handle incompatible declarations.

Absence has a precise meaning: support was not advertised. It is not permission to probe. An empty settings object has a different meaning: the extension was advertised with no settings. Implementations should retain this distinction in their capability model instead of collapsing both states into a falsey value.

A method name is not a capability declaration. A server might expose a method introduced by an extension for compatibility, testing, or another negotiated mode. A client might recognize the method string without implementing the extension’s result rules. Inferring support from the method would skip the settings contract and could activate behavior the peer cannot interpret.

The same applies to tool names. A tool called render_interactive_report does not prove support for an interactive-output extension. Tools are application-level interfaces; extensions are protocol contracts. The client and server must still find the extension identifier in both capability declarations.

If extension-dependent processing requires a client capability that the request did not declare, the schema provides a missing-required-client-capability error. That is different from treating the request as an unknown JSON-RPC method. Readers tracing that boundary can use the site’s treatment of JSON-RPC protocol errors and MCP tool failures; the important rule here is that an undeclared capability cannot be reconstructed from the method name after the fact.

Settings belong to the extension

The core protocol supplies the extensions map but does not define one universal settings schema. Each extension owns the structure and meaning of its value. One extension may need only {}. Another may advertise supported formats, feature flags, or its contract version.

That ownership keeps the core schema from accumulating fields for every optional feature, but it transfers work to the extension author. The extension contract needs to state:

These are contract requirements, not implementation trivia. Without them, two peers can advertise the same identifier and still make different assumptions about the enabled behavior.

Settings should negotiate the smallest behavior both sides can perform. A peer must not treat its own preferred setting as accepted merely because the other side named the extension. If a client advertises two representation versions and a server implements one, the extension’s rules need to identify the shared version before either side sends version-specific data.

The settings object should also remain inside its namespace. A third-party extension must not place unqualified flags into another capability object and then treat those flags as proof of support. Doing so bypasses the collision protection provided by the vendor-prefixed identifier.

Version the extension contract

Third-party extensions evolve independently from the core protocol, so they need an explicit version policy of their own. That policy should identify compatible changes, incompatible changes, and the declarations peers use to select a compatible contract.

A settings field can carry a version or a supported-version set when the extension specification defines it. Capability flags can represent independently optional additions. These mechanisms allow a publisher to extend the contract without changing the identifier whenever older implementations can continue to behave correctly.

A breaking change needs a boundary old implementations cannot cross accidentally. Removing or renaming a required field, changing a field’s type, changing established behavior, or adding a new required field can make an older implementation fail or misinterpret a message. The contract must prevent peers on opposite sides of that boundary from enabling the changed behavior merely because their top-level extension identifiers match.

When compatibility cannot be expressed safely inside the settings contract, the extension needs a distinct identifier, such as a -v2 name under the same owned vendor prefix. That makes the incompatibility visible during capability intersection. It also permits a migration period in which a peer advertises both identifiers and uses whichever one the other side supports.

Core protocol version and extension contract version should remain separate in code, logs, and tests. Combining them into one variable makes upgrades ambiguous: an operator cannot tell whether a failure came from the MCP schema or the extension’s private contract.

Preserve a useful core-protocol path

A third-party extension should leave useful behavior for a peer that implements only the core protocol. The fallback does not need to reproduce every enhancement, but it must remain valid and meaningful under the core contract.

For an extension that adds a richer representation, the core path can carry the ordinary core representation while the extension adds an enhanced form only after negotiation. For an extension that adds optional metadata, the underlying core result remains usable without that metadata. The mechanism is the same in both cases: construct the core result first, then attach or select extension behavior only inside the mutual-support branch.

This has a concrete engineering cost. The implementation carries two paths, and both need tests: the negotiated path and the core-only path. Supporting multiple extension contract versions adds further branches. That cost is part of publishing an optional extension; removing the fallback by silently requiring the extension changes the interoperability promise.

A core fallback is not appropriate when the requested operation has no useful meaning without the extension. An extension may define itself as mandatory for a particular operation and reject a request when support is absent, but that requirement must be explicit in the extension contract. It must not be inferred from a method name or discovered only after the server emits extension-only data.

The useful-path rule also prevents a third-party extension from capturing an existing core operation. An extension can enrich a tool result, but it should not make ordinary tool results unreadable to every client that lacks the extension. If the new behavior cannot coexist with the established core semantics, it is not additive.

Keep namespace ownership intact

A vendor prefix is both an identifier component and a limit on authority. A publisher controls contracts under its own prefix. It does not control io.modelcontextprotocol, another organization’s reverse-domain prefix, or the semantics of core methods.

That means a third-party extension should not:

These restrictions keep capability declarations composable. A client and server can support several extensions at once because each settings object has one named owner and one contract. If extensions overwrite core semantics or one another’s namespaces, the capability map stops being a reliable record of what was agreed.

The same boundary should appear in implementation structure. Parsing an extension settings object, selecting its version, enabling its behavior, and producing its fallback should live behind the identifier that owns the contract. Scattered checks for method strings or individual fields make it harder to prove that all extension behavior is gated.

What an implementation should retain

A useful negotiation record contains the client’s advertised extensions, the server’s advertised extensions, the compatible contract selected for each shared identifier, and the fallback chosen for everything else. Keeping those states distinct makes later behavior explainable without treating an observed method as evidence.

Tests should cover the conditions created by the contract: neither side advertises the extension, only the client advertises it, only the server advertises it, both advertise compatible settings, and both advertise incompatible settings. Core behavior should remain valid in every condition where the extension is optional. Extension-only output should appear only in the mutually compatible condition.

Logs should name the extension identifier and selected contract version when extension behavior is enabled. When it is not enabled, the record should distinguish absent support from incompatible settings. That information narrows a later investigation without changing the protocol’s negotiation rules.

For the next check, confirm the underlying client and server capability exchange first, then verify extension identifier equality and settings compatibility. If the request still fails, follow the actual error boundary through diagnosing a failing MCP tool call rather than using the method name as a substitute for negotiated support.

Sources

  1. MCP extensions overviewmodelcontextprotocol.io
  2. 2026-07-28 schema referencemodelcontextprotocol.io
  3. July 28, 2026 specification release modelblog.modelcontextprotocol.io

See also