The Model Context Protocol TypeScript SDK published two coordinated releases on 2026-09-28:
1.30.1...1.31.0.@modelcontextprotocol/* packages (client, server, core, server-legacy, codemod), with per-package releases timestamped 19:07:43-19:07:55 UTC. node, express, hono, fastify packages unchanged. Compare link: v2.1.x...v2.2.0.Both releases land in the same six-hour window. The OAuth issuer-binding change is the substantive one; the v2.2.0 release packages that change plus a required parameter, a list-auto-walk, and a stack of fix items. This is a documentation-comparison report — no firsthand API call was run for this article.
Required expectedIssuer on machine-to-machine OAuth providers. Per the v2.2.0 release notes: "Constructing ClientCredentialsProvider, PrivateKeyJwtProvider, StaticPrivateKeyJwtProvider or CrossAppAccessProvider without it is deprecated and logs a warning. Set it to the issuer of the authorization server the credentials were registered with. (#2887)" The deprecation is in this release; a future release is expected to make the parameter mandatory. v1.31.0 carries the same shape: "Pass expectedIssuer when constructing ClientCredentialsProvider, PrivateKeyJwtProvider or StaticPrivateKeyJwtProvider. Constructing them without it is deprecated."
Token-endpoint authorization-server mismatch check. Per v2.2.0: "fetchToken() checks which authorization server the client information belongs to. It throws AuthorizationServerMismatchError before sending anything when the provider's client information is bound to a different authorization server. (#2887)" This is a credential-confused-deputy guard: if a client object bound to authorization server A is asked to fetch a token from authorization server B, the SDK now refuses before any network call.
Stored OAuth tokens now carry an issuer field. Per the v1.31.0 release notes: "Stored OAuth tokens and client information now include an issuer field. Storage that rejects unknown fields needs to allow it." The v2.2.0 release notes extend this: "OAuthTokensSchema and OAuthClientInformationSchema accept the optional issuer field, so a provider that reads its storage back through them keeps it." Persistent storage schemas that reject unknown fields need an update.
List calls auto-walk nextCursor. Per v2.2.0: "listTools(), listPrompts(), listResources() and listResourceTemplates() called without a cursor now follow nextCursor until the server stops sending one. listMaxPages still caps the walk. (#2886)" This is a behaviour change for callers that previously paged explicitly. Callers that passed a cursor will see no change; callers that did not will now receive the full list subject to the listMaxPages cap.
CommonJS TypeScript regression fixed. Per v2.2.0: "CommonJS TypeScript projects type-check again: the jose types used by the DPoP API are inlined into the declaration files (regression in 2.1.0). (#2883)" This was an unresolved regression that broke CommonJS consumers on the 2.1.0 line.
Hostname loopback for OAuth. Per v2.2.0: "Hostnames ending in .localhost count as loopback for OAuth token endpoints, so host-based multi-tenant local setups work. (#2597)" A practical ergonomic fix for developers running hostnames like tenant-a.localhost / tenant-b.localhost against local OAuth providers.
Codemod preserves leading comments. Per v2.2.0: "The v1-to-v2 codemod keeps a file's leading comment block and directives above the rewritten imports. (#2582)" A small but operationally important fix for teams migrating from the v1 SDK line to the v2 modular packages.
Other v2.2.0 fixes. Client.listen() no longer lets a rejection escape as a process-level unhandled rejection, and no longer hangs when a send never settles (#2642). _meta is preserved on input_required results (#2862). createMcpHandler no longer overflows the stack when the factory returns the same server instance for more than one request (#2778). Sending a notification on a closed connection no longer produces a briefly unhandled promise rejection (#2885). Corrected JSDoc citations on OAuth token endpoints and the registerClient deprecation notice (#2768, #2729).
The OAuth changes are the substantive ones. Storing an issuer field with each token, refusing to send a token to an authorization server the client object is not bound to, and requiring callers to pass expectedIssuer together form a credential-confused-deputy guard rail that closes a real class of misconfiguration in multi-tenant OAuth setups. For teams that already construct these providers explicitly, the change is a parameter addition. For teams that rely on default storage schemas, an unknown-field rejection could surface as a write failure on the next token refresh.
The list-auto-walk is a behaviour change. Callers that paged explicitly pass a cursor and see no change; callers that did not now get the whole list subject to the listMaxPages cap. For servers that page through thousands of tools, prompts, or resources, this could increase the per-call payload substantially. Audit your client code for list*() calls without cursors.
The codemod-preserves-comments fix matters for any team planning a v1→v2 migration. The v1.31.0 release is the natural pivot point: while the legacy line still accepts v1 contract code, v1.31.0 carries the issuer-binding change so migration can start from a known-good v1 baseline.
862578138/v2.2.0, published 2026-09-28T19:24:07Z) and 1.31.0 (id 862578138/1.31.0, published 2026-09-28T18:52:02Z), release bodies inspected verbatim.@modelcontextprotocol/server-legacy@2.2.0 19:07:43Z, @modelcontextprotocol/codemod@2.2.0 19:07:49Z, @modelcontextprotocol/server@2.2.0 19:07:52Z, @modelcontextprotocol/client@2.2.0 19:07:55Z, @modelcontextprotocol/core@2.2.0 19:07:55Z, parent tag 19:24:07Z. node, express, hono, fastify packages unchanged.1.30.1...1.31.0, 2 commits ahead, 0 behind.Cost. No pricing surface on the SDK. Operational cost is in the migration: switching storage schemas to accept issuer, passing expectedIssuer to provider constructors, and reviewing list-cursor usage.
Risk. Three risk classes. First, storage schemas that reject unknown fields (e.g., strict Zod or JSON-Schema validators) will fail token reads after upgrade. Second, the expectedIssuer parameter is currently deprecated-with-warning, not mandatory; future v2.x release is expected to make it required. Third, list-auto-walk is silent — clients that relied on the old behaviour (one page per call) will now receive more data per call subject to listMaxPages.
Limitations. Documentation-comparison report. No firsthand SDK install, provider construction, or token-exchange call was run for this article. Cross-check against the Anthropic SDK and the Python SDK was not performed in this sweep. Per-package changelogs were not exhaustively cross-referenced; the v2.2.0 release body summarises per-package changes with linked references.
This is a quiet but real OAuth-hygiene release. The mandatory expectedIssuer, the AuthorizationServerMismatchError pre-flight, and the stored-token issuer field form a coherent credential-confused-deputy story. For teams that already store per-tenant OAuth credentials, this is a straightforward migration. For teams that rely on default storage, plan to update your schemas. The list-auto-walk is the kind of change that hides in plain sight — audit your listTools() / listPrompts() / listResources() calls for cursorless usage before upgrading. The v1.31.0 parallel release is a useful pivot point if you want to migrate from the v1 line on a known-good baseline before the v2 modular migration.
ClientCredentialsProvider, PrivateKeyJwtProvider, StaticPrivateKeyJwtProvider, and CrossAppAccessProvider constructions. Pass expectedIssuer explicitly to silence the deprecation warning now and avoid the future mandatory change.issuer field. Verify both writes and reads round-trip the field.listTools(), listPrompts(), listResources(), listResourceTemplates() for cursorless usage. For servers with thousands of items, set listMaxPages explicitly to bound auto-walk payload.v1-to-v2 codemod to migrate imports; verify leading comment blocks survive the rewrite..localhost now count as loopback for OAuth token endpoints; if you run host-based multi-tenant local OAuth, the upgrade unblocks that pattern without code changes.Documentation comparison; no firsthand SDK install or token-exchange call run for this article. Release bodies quoted verbatim from the GitHub release pages.