Skip to content

SEP-XXXX: Extension Versioning#18

Open
LucaButBoring wants to merge 2 commits into
modelcontextprotocol:mainfrom
LucaButBoring:feat/extension-versioning-2
Open

SEP-XXXX: Extension Versioning#18
LucaButBoring wants to merge 2 commits into
modelcontextprotocol:mainfrom
LucaButBoring:feat/extension-versioning-2

Conversation

@LucaButBoring

Copy link
Copy Markdown
Collaborator

SEP-2133 establishes that extensions evolve independently of the core protocol and SHOULD be versioned, but it leaves the versioning approach unspecified and defers extension dependency declaration to future work. This proposal fills both gaps with a single mechanism. Each extension carries a semantic version in its settings object, and MAY declare the core protocol version its behavior depends on. A difference in the major version means the two peers are incompatible; they negotiate a shared major version through inline retry, as they negotiate a protocol version (see protocol version negotiation). A difference in the minor version is not a conflict: each peer uses the features common to both, and neither rejects the other. A difference in the patch version changes nothing about how the peers interoperate. A new error code, -32005 (Unsupported Extension Version), reports a major-version mismatch and tells the client which major versions the server supports, so it can retry against one of them.

Motivation and Context

SEP-2133 scoped itself narrowly: it defined how extensions are governed, identified, and negotiated, but excluded two compatibility concerns from its scope. Under Not Specified, it omits both extension dependencies on core protocol versions and a concrete versioning approach, recording only that "Extensions SHOULD be versioned, but exact versioning approach is not specified here." Three problems follow from those omissions, and each currently surfaces as a runtime failure rather than a negotiated outcome.

  1. An extension's protocol-version dependency is undiscoverable.
  2. An extension cannot roll out a breaking change of its own.
  3. Optional, non-breaking features cannot be advertised.

This proposal address all three issues by introducing a versioning scheme and negotiation pattern that can generalize across extensions.

Based on #11 from and my previous versioning draft.

How Has This Been Tested?

Hasn't.

Breaking Changes

Technically if extensions are already using version as a settings field, but as far as I've seen they aren't.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

AI Use Disclosure: Claude wrote this and I made it revise its own work for several hours after I tore it apart.

@cayerbe

cayerbe commented Jun 4, 2026

Copy link
Copy Markdown

Coming to this from the call for early review on the versioning approach — read the current draft end to end, mostly with a conformance / compatibility-verification hat on. It fills a real gap, and the semver framing is well argued: the date-vs-semver rationale especially, and requires.protocolVersion as an advisory floor is a clean way to make a protocol dependency discoverable without forcing stale servers to enumerate compatibility. Three things I'd stress-test before it generalizes to every extension, taking a security-relevant extension (signing / identity / audit, where versions differ in the guarantees they make) as the hard case.

1. Minor-version compatibility is asserted by the maintainer, and nothing can check it. The minor-resolution rule is elegant — intersection, no rejection, graceful degradation. But its safety rests entirely on each change being classified correctly: "MINOR identifies an additive, backward-compatible revision," and the rationale's "if a minor version increment is genuinely additive" both assume the one property the mechanism never verifies. A breaking change shipped as a minor bump negotiates to "compatible," both peers confine themselves to the shared surface, and then break at runtime — silently, because nothing detects it. Two suggestions, escalating: (a) at minimum, define what a peer does when a declared-compatible version turns out incompatible in practice — surface or fall back, rather than undefined behavior; (b) more durably, consider recommending (not requiring) that an extension ship a conformance suite so a minor's backward-compatibility claim is checkable — the new minor must still pass the prior minor's tests. As written, "minor = additive" is a promise the framework makes on the maintainer's behalf with no recourse when it's wrong.

2. The extension-version negotiation is itself a downgrade surface — the floor is covered, the version isn't. The "no downgrade through an understated floor" bullet is exactly right for requires.protocolVersion. But the symmetric vector on the extension's own versions isn't addressed: the -32005 flow says a client "MUST select a mutually supported major version and retry, or abort," so a peer that advertises only an older major can steer a client down to it. For an extension whose majors differ in their security guarantees (say v1 unsigned, v2 signed/tamper-evident), that's a forced downgrade across a security boundary, with no analogue to the protocol floor — no way for a client or the extension to say "do not negotiate below major N." Worth either a security-section bullet naming extension-version downgrade, or a non-downgradable floor on the extension version mirroring the protocol floor.

3. The inter-extension-dependency Open Question seems to argue against the proposal's own motivating principle. requires.protocolVersion exists precisely so a protocol dependency is discoverable rather than surfacing as a runtime failure on the first request that needs it — Motivation problem #1, almost verbatim. The Open Question then declines a requires.extensions analogue for extension-on-extension dependencies because "the dependent extension's implementation enforces what it needs from the other" at runtime — which is the same undiscoverable-runtime-failure pattern requires.protocolVersion removes, one layer up. The "intermediaries" benefit dismissed as speculative is the same discoverability win valued for the protocol case. Not arguing to ship requires.extensions in this trial — but the asymmetry deserves a sentence: why is discoverability worth a field for the protocol dependency and not the extension dependency?

All three are about generalizing cleanly — security/identity/audit extensions are where version semantics get sharp, so they make a useful forcing function. Glad to help pressure-test the conformance-backed-compatibility idea if it's worth pursuing.

@pja-ant

pja-ant commented Jun 4, 2026

Copy link
Copy Markdown

I have to say I did a poor job of writing out how this was all intended to work in SEP-2133, but after reading this I'm still finding myself unconvinced that we need anything new in the protocol to handle the scenarios described in the Motivation section. Here's how I'd expect things to work with how the protocol stands today:

Core protocol version dependencies

The key thing is that the extensions themselves (e.g. Tasks) should just state plainly in their SEP language that they require a specific protocol version. This is no different from e.g. subscriptions/listen requiring 2026-07-28 in the core protocol. We don't need protocol message to say that subscriptions/listen requires 2026-07-28, the client and server SDKs just know it because they implement the extension (or they don't, in which case it just gets rejected anyway, regardless of protocol).

What this means is that a server telling the client that Tasks v1.2.3 requires 2026-07-28 is just redundant. The client already knows this because it implements Tasks and the Tasks extension says that it requires 2026-07-28. The server doesn't need to tell the client.

As a side note, even ignoring what I'm saying here, requires.protocolVersion is just redundant in server/discover because that method explicitly only returns capabilities for the negotiated protocol version: so if a client sends a request with 2025-11-25, the server just omits the tasks extension entirely because it isn't available in the negotiated version.

Minor changes

For minor changes we already have a way to advertise those: extension capabilities. If you e.g. introduce event streaming to tasks as an optional add-on (backwards compatible) then you just add an eventSource: {} to the extension capability schema and negotiate the same way that base protocol negotiation happens.

This is actually better than minor versions in my opinion since it allows servers to declare different subsets of additive features rather than 1.2.3 meaning that you support everything from all prior versions.

Patch versions themselves are pure ceremony. By definition, they MUST NOT affect interoperability, so they by definition cannot be part of protocol semantics.

A semver version might be useful metadata (similar to serverVersion), for logging/auditing etc. - but I don't think it needs to participate in the protocol or have any semantics. I'd be in favor of extension version as a pure metadata/telemetry field.

Breaking changes

This SEP says there's no way for major version to co-exist, but I don't see why not. You call server/discover, it says it supports tasks and tasks-v2 and the client chooses whichever is preferable (again, the client knows about both these extensions, so it knows that one may supersede the other).

The benefit of have separate extension IDs is that you can have a separate capabilities schema for each one (which is 100% necessary, because the schemas may not be compatible).


What I do think we're missing from extensions (which is only tangentially related to this) is a concept of stability, i.e. people using extension what to know "is this beta, is it going to break, how long are deprecation periods?" etc. This is not protocol, but it's related to versioning and is something we should solve.

@cayerbe

cayerbe commented Jun 4, 2026

Copy link
Copy Markdown

This is a much clearer framing, and I think you're right that most of it doesn't need new protocol machinery — capabilities, with the version as metadata, handle the additive case more cleanly than minor versions, which was really what I was getting at.

Stability feels like the actual gap. 2133 already asks extensions to document it and lets them self-flag "experimental"; the open question to me is just whether that signal stays self-declared or eventually gets something observable behind it. Either way, agreed it's worth solving — glad to help if it ever becomes its own thread.

@LucaButBoring

Copy link
Copy Markdown
Collaborator Author

What this means is that a server telling the client that Tasks v1.2.3 requires 2026-07-28 is just redundant. The client already knows this because it implements Tasks and the Tasks extension says that it requires 2026-07-28. The server doesn't need to tell the client.

As a side note, even ignoring what I'm saying here, requires.protocolVersion is just redundant in server/discover because that method explicitly only returns capabilities for the negotiated protocol version: so if a client sends a request with 2025-11-25, the server just omits the tasks extension entirely because it isn't available in the negotiated version.

Still chewing on the rest of the feedback, but this point jumped out to me as somewhat imprecise. I think you're basically right that we don't need to declare a protocol floor during capability discovery, but that does not actually mean that the status quo works.

Tasks (as of SEP-2663) depends on subscriptions/listen for delivering task notifications. Consider a case where we decide in the base protocol that it's not the right mechanism for notification delivery, and we summarily remove it. We can declare a required initial protocol version in the Tasks specification, but then we're stuck with either:

  1. Being locked to that protocol version and needing to publish a new extension revision whenever a new base protocol version releases, or
  2. Declaring a minimum version and just hoping things don't break in new base protocol revisions.

Of those, the former seems like the more-correct option, but it also has its own immediate consequences, for example breaking every existing extension on 7/28. Any way you look at it, that seems like a problem we need to reckon with.

Furthermore, I think it is worth noting that that server/discover does not actually explicitly only return capabilities for the negotiated protocol version, it only implicitly does. That is doubly-important for extensions, as nothing in SEP-2575 specifies that extensions should consider checking the protocol version before registering their own extension capabilities (I wouldn't have considered this when implementing Tasks, at least). Extensions are ostensibly decoupled from the base protocol's release schedule, but this seems to throw a wrench in that, as base protocol releases would then set hard beginning/end periods for the validity of any given extension identifier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants