← Back to Payloads
AI News2026-10-05

MCP TypeScript SDK v2.3.1 + v1.32.1 (Oct 5, 2026): The `expectedResource` Audience-Binding Backport Reaches `@modelcontextprotocol/server-legacy`

The MCP TypeScript SDK shipped two releases on 2026-10-05. The substantive one is v2.3.1: @modelcontextprotocol/server-legacy's requireBearerAuth now accepts the same expectedResource (audience-binding) option that @modelcontextprotocol/server got in v2.3.0 three days earlier, closing the asymmetry left by the Sept 28 OAuth issuer-binding cycle. v1.32.1 on the v1.x mainline is docs-only. The check is off by default, opt-in per call, and verifies the token's aud claim against the configured URL. For anyone running MCP servers on the legacy package behind a corporate IdP, this is the cleanest day in months to tighten the trust contract.

MCP TypeScript SDK v2.3.1 + v1.32.1 (Oct 5, 2026): The expectedResource Audience-Binding Backport Reaches @modelcontextprotocol/server-legacy

Originally published: 2026-10-05 12:08 UTC / 14:08 Berlin / 08:08 EDT Last verified: 2026-10-05 12:08 UTC (GitHub modelcontextprotocol/typescript-sdk releases atom feed + the v2.3.1, v2.3.0, v1.32.0, and v1.32.1 release pages, plus PRs #2952 and #2929 via the GitHub REST API). No corrections at this time.

What happened

The MCP TypeScript SDK shipped two releases on the same day, 2026-10-05:

  • v2.3.1 at 11:54:56 UTC — the headline item. @modelcontextprotocol/server-legacy, the back-compat copy of the v1.x middleware that ships separately, now accepts the same expectedResource option that @modelcontextprotocol/server got in v2.3.0 three days earlier. Off unless you set it.
  • v1.32.1 at 11:42:59 UTC — a docs-only bump on the v1.x mainline SDK. Two README rewrites and a version-bump commit. No behavior change.

Both come from the same source change: PR #2952, "feat(server-legacy): add expectedResource to requireBearerAuth", merged at 11:22:32 UTC by @claude[bot]. The legacy npm package's requireBearerAuth now takes an optional URL — the value the authorization server puts into tokens meant for this server, the token's audience — and rejects any token whose aud claim does not match.

This is the second half of a hardening cycle that started 22 days ago. On 2026-09-28 the SDK shipped v2.2.0 / v1.31.0 with expectedIssuer on the M2M OAuth providers — the issuer half of the trust check. On 2026-10-02 it shipped v2.3.0 / v1.32.0 with expectedResource plus a maxToolInputElements cap. Today closes the loop by bringing expectedResource to the legacy server package, so anyone who has not yet migrated off the legacy middleware now has the same audience check.

What actually changed

The v2.3.1 release page lists four line items. Three are dependency-bump noise that always rides along with a same‑server package bump; one is the substantive change.

The substantive change is on @modelcontextprotocol/server-legacy@2.3.1 (PR #2952, merge commit 5a1867390b91b8836f49af76c45427279f513325):

requireBearerAuth in @modelcontextprotocol/server-legacy takes the optional expectedResource that @modelcontextprotocol/server 2.3.0 added: it accepts only tokens issued for this server (the token's audience). Off unless you set it. (#2952)

The legacy package is a near-copy of the v1.x middleware; with this patch, both sides of the same auth skill are in step. The same option is documented on the v2.3.0 release page for @modelcontextprotocol/server and verifyBearerToken:

expectedResource on requireBearerAuth / verifyBearerToken accepts only tokens issued for this server (the token's audience), usually the server's URL. (#2926, #2929)

The semantic shift is small but specific. When the option is set, the verifier looks at AuthInfo.resource and compares it to expectedResource "as strings, ignoring a fragment and one trailing slash," as the v1.32.0 server-legacy release entry puts it. A token whose aud claim does not match is answered with 401 invalid_token plus the usual WWW-Authenticate challenge. When the option is not set, the check is a no-op — no behavior change for existing installs.

The same-day v1.32.1 on the v1.x mainline is, per the GitHub release body, three PRs deep and zero-feature:

  • PR #2939 ([v1.x] docs: state the principles in CLAUDE.md)
  • PR #2942 ([v1.x] docs: point the README at v2 and say what v1.x supports)
  • PR #2954 (chore: bump version to 1.32.1)

The only functional change for v1.x consumers today is the README rewrite telling v1.x users that v2 exists and where the migration guide lives. The v1.x mainline SDK already got expectedResource in v1.32.0 on 2026-10-02; v1.32.1 does not change it.

The npm-page cleanup (PR #2955, merged at 11:40:25 UTC) is unrelated to security. The @modelcontextprotocol/server and @modelcontextprotocol/client READMEs used to open with a [!WARNING] and a [!NOTE] saying nearly the same thing; now each opens with a single plain blockquote linking the documentation, the migration guide, and the issue form. Doc churn, not behavior.

What did not change in v2.3.1. The v2.3.0 release notes carry two breaking upgrade notes that remain true after v2.3.1 and apply to anyone bumping from 2.2.x:

1. One server per request. Server.connect() now rejects while the instance is already connected, and a stateless Streamable HTTP transport handles one request. Create the McpServer and the transport inside the request handler (or in the createMcpHandler factory) instead of sharing one instance across requests. (#2918) 2. Redirects stay on the same origin. The HTTP client transports follow a redirect only when it stays on the same scheme/host/port (http→https on the same host is allowed). A deployment whose endpoint redirects to another host or port either configures the final URL or sets redirectPolicy: 'follow'. In browsers, a redirected request fails unless that option is set. (#2901)

v2.3.1 does not relax either of these. Plan your upgrade accordingly.

Why developers and founders should care

This is a small patch in line count but a real one in class. It completes the audience half of the trust check the SDK has been building toward since expectedIssuer landed in v2.2.0.

The trust-check story in one paragraph. When an MCP server validates a bearer token, it is implicitly trusting the authorization server that minted the token. The MCP SDK now exposes three levers to bound that trust:

  • expectedIssuer (since v2.2.0 / v1.31.0 on 2026-09-28) — the issuer URL the provider's client information was registered with. If a token comes from a different issuer, the SDK rejects the request before sending anything. This is the who issued it half.
  • expectedResource (since v2.3.0 / v1.32.0 on 2026-10-02; now on @modelcontextprotocol/server-legacy as of v2.3.1 on 2026-10-05) — the audience URL the token was minted for, conventionally the resource server's URL. If a token's aud claim does not match this server, the SDK answers 401 invalid_token. This is the what is it for half.
  • requireBearerAuth vs. verifyBearerAuth — the former is middleware that does the check on every request, the latter is a one-shot call. The new option exists on both.

Why "what is it for" matters. A confused-deputy attack is the canonical scenario: a single authorization server mints tokens for many resource servers. Without an audience check, a token legitimately minted for resource server B can be replayed against resource server A if both trust the same issuer. With expectedResource set, A will refuse B's tokens. This is the same defense that RFC 9068 (the JWT Profile for OAuth 2.0 Access Tokens) codifies for general OAuth 2.0 deployments, and it is the same check that major API providers bake into their gateway auth. The MCP SDK now exposes the same control on its bearer-token path.

Why this release, why today. The legacy package exists specifically so apps that built on the v1.x middleware could upgrade without rewriting their auth wiring. Until today, the legacy server had the issuer check (because the legacy package had been kept roughly in sync with v1.x for years) but not the audience check that v2.3.0 added to the modern server. That asymmetry is now closed. Operators who have been deferring the migration off the legacy package because their auth wiring depends on it can adopt expectedResource in place — no rewrite required.

Who is affected. Three groups, in order of how much they should pay attention:

1. Operators running MCP servers on @modelcontextprotocol/server-legacy. This is the headline audience. Today is the first day you can pin expectedResource on the legacy path without forking it. Add it to every requireBearerAuth call site, bump to 2.3.1, and confirm your authorization server puts your server's URL in the aud claim. 2. Operators running MCP servers on @modelcontextprotocol/server 2.2.x. You should already have expectedResource from v2.3.0 (Oct 2). v2.3.1 is a same-day refresh; bump for hygiene. 3. Operators running MCP servers on the v1.x mainline SDK (@modelcontextprotocol/sdk 1.x). v1.32.0 gave you the feature on Oct 2; v1.32.1 is docs-only. Use it.

Evidence or test results

Documentation indicates the behavior described below. No firsthand install, exploit reproduction, or auth-flow test was run during this report; the verification is a side-by-side read of the v2.3.1, v2.3.0, v1.32.0, and v1.32.1 release pages and the corresponding PRs.

Primary source 1 — v2.3.1 release page (2026-10-05 11:54:56 UTC). Lists the substantive change verbatim: "requireBearerAuth in @modelcontextprotocol/server-legacy takes the optional expectedResource that @modelcontextprotocol/server 2.3.0 added: it accepts only tokens issued for this server (the token's audience). Off unless you set it. (#2952)" Package table bumps @modelcontextprotocol/client, @modelcontextprotocol/server, @modelcontextprotocol/core, @modelcontextprotocol/server-legacy, and @modelcontextprotocol/codemod all to 2.3.1, with the express, hono, and fastify adapters unchanged. (release tag v2.3.1)

Primary source 2 — v2.3.0 release page (2026-10-02 17:55:03 UTC). Original introduction of expectedResource for the v2.x server: "expectedResource on requireBearerAuth / verifyBearerToken accepts only tokens issued for this server (the token's audience), usually the server's URL." It also calls out the cross-package requirement: "with Express, upgrade @modelcontextprotocol/express together with @modelcontextprotocol/server." The same release also adds maxToolInputElements to McpServer, breaks the one-server-per-request invariant, and tightens client-side redirect handling — none of which are touched by v2.3.1. (release tag v2.3.0)

Primary source 3 — v1.32.0 release page (2026-10-02 17:28:24 UTC). Puts expectedResource on the v1.x mainline SDK, three days before the legacy backport. Body text: "New options, both off unless you set them: maxToolInputElements on McpServer limits the number of array elements and object members in a tool call's arguments. expectedResource on requireBearerAuth accepts only tokens issued for this server (the token's audience)." This is the version that establishes the feature in v1.x. (release tag 1.32.0)

Primary source 4 — v1.32.1 release page (2026-10-05 11:42:59 UTC). Documents that the v1.x bump today is doc-only. Body lists three PRs: #2939 (CLAUDE.md principles), #2942 (README pointer at v2), and #2954 (version bump). No features, no breaking changes. (release tag 1.32.1)

Primary source 5 — PR #2952 (server-legacy backport). Merged at 2026-10-05 11:22:32 UTC, merge commit 5a1867390b91b8836f49af76c45427279f513325. Title: "feat(server-legacy): add expectedResource to requireBearerAuth." Body, paraphrased from the description field: the option matches what @modelcontextprotocol/server 2.3.0 added and what the 1.x middleware has since 1.32.0, so the legacy package is now in step with both. Requested by Felix Weinberger on the ant-mcp-oss Slack. (PR #2952)

Primary source 6 — PR #2929 (original v2.x change). Merged at 2026-10-02 16:28:25 UTC, merge commit 40f8f4e229d963cd7fd2890bda2aeebe7005299a. Title: "feat(server): add expectedResource to the bearer-token check." Defines the option as a URL and the behavior as "a token is accepted only if the verifier reports that value in AuthInfo.resource; the two are compared as strings, ignoring a fragment and one trailing slash." Requested by Felix Weinberger. (PR #2929)

Reference background — the Sept 28 pillar. The earlier pillar MCP TypeScript SDK Shipped v2.2.0 and v1.31.0 on the Same Day. The OAuth Provider's Issuer Binding Now Has a Name. ([slug mcp-typescript-sdk-v2-2-0-v1-31-0-oauth-issuer-binding-sep-2026](https://mr.technology/blog/mcp-typescript-sdk-v2-2-0-v1-31-0-oauth-issuer-binding-sep-2026)) covers the issuer half of the same trust check. Today's article is the audience half. The two together form the full MCP-bearer-auth hardening story.

Inference, labeled. I infer — without a firsthand test — that an MCP deployment whose authorization server does not already set the resource server URL as the aud claim will see all requests fail with 401 invalid_token after the operator turns the new option on. The release notes only state that the check rejects mismatched tokens; they do not characterize the exact error surface for legacy auth servers that omit aud entirely. Treat this as a deployment-time concern, not a documentation defect.

Cost, risk, and limitations

Off by default. The single most important sentence in the v2.3.1 release notes is "Off unless you set it." Existing deployments do not change behavior on bump. The risk surface is opt-in.

One-server-per-request is still a breaking change for v2.x upgraders. v2.3.1 ships on top of v2.3.0, which made Server.connect() reject while an instance is already connected. If you are on v2.2.x and today is the day you bump, you have to refactor shared-instance Streamable HTTP handlers to construct the McpServer and the transport inside the request handler. (See Primary source 2.)

Redirects stay on the same origin. v2.3.0's client-side redirect tightening is unchanged in v2.3.1. If your client deployment relies on cross-origin redirects to a gateway, set redirectPolicy: 'follow' on StreamableHTTPClientTransport or SSEClientTransport before bumping. (See Primary source 2.)

Express adapter must move with the server. Per the v2.3.0 release notes: "with Express, upgrade @modelcontextprotocol/express together with @modelcontextprotocol/server." If you are using the Express adapter and bumping past 2.2.x, do not bump @modelcontextprotocol/server alone.

Authorization-server compatibility is the deployment-side question. The check only does anything if your authorization server emits your server URL in the aud claim. If it does not — for example, if it uses opaque tokens, scopes-only, or a generic audience — turning the option on will reject every legitimate request. The release notes do not address this case; verify with your identity provider's token introspection output before enabling.

Legacy package is not deprecated. Per the v1.32.1 README rewrite, the v1.x mainline now has a clearer pointer to v2 but the legacy package remains in v2.3.1 alongside the modern server. The migration is encouraged, not enforced.

v1.32.1 has no new test surface. The Oct 5 v1.x bump is three PRs, all documentation or version-bump. If you pin to v1.32.1 expecting a new behavior, you will be disappointed; the functional change for v1.x consumers happened in v1.32.0 on Oct 2.

No security advisory is referenced. v2.3.1 does not list a CVE or GitHub Security Advisory in its body. The fix is preventative hardening of the bearer-token check, not a closed CVE. Treat it as the next-best category of clean install.

What the change does not cover. The release notes do not address:

  • Tokens minted with a list of audiences (RFC 7519 §4.1.3 allows aud to be either a string or an array). Documentation indicates the verifier treats expectedResource as a single string; behavior on array-valued aud is not characterized.
  • Tokens whose aud claim matches by URL normalization beyond what the release notes describe (trailing slash and fragment). A deployment that registers https://example.com and accepts https://example.com:443 may or may not match; treat as unverified.
  • Clock-skewed tokens, audience-claim inflation attacks beyond the simple string compare, or PKCE-binding mismatches.

These are not defects for today. They are the limit of what is claimed.

Mr. Technology verdict

For anyone running production MCP servers behind a bearer-token auth wall, this is the cleanest week in months to tighten the auth contract: you have expectedIssuer since Sept 28, expectedResource since Oct 2 on both SDK lines, and now the same expectedResource on the legacy server since today. The MCP TS SDK has stopped treating "trust the issuer" as a complete answer and is now willing to ask "for this server?" on every request.

The legacy backport matters because the legacy package is the on-ramp most v1.x-built agents and gateways never exited. Today is the first day that on-ramp has the audience check. If you are running MCP behind a corporate SSO that mints one token per app, you almost certainly want both halves on.

The single caveat that should give you pause: the check only works if your identity provider already populates the audience claim correctly. If your IdP mints opaque tokens, or emits generic audiences, or ignores the request-side audience hint, turning the option on will lock you out. Fix the IdP first; then turn on the check.

The release is small. The story is the trajectory. The trajectory is: the MCP SDK is making bearer-token auth less implicit and more configurable on every release, and the legacy backports are coming. If you are sizing an MCP rollout, the auth surface is no longer "set it and forget it."

Recommended action

For operators on the modern server (@modelcontextprotocol/server 2.2.x or 2.3.0):

1. Bump to @modelcontextprotocol/server@2.3.1, @modelcontextprotocol/client@2.3.1, @modelcontextprotocol/core@2.3.1, and (only if you use the Express adapter) @modelcontextprotocol/express in lockstep. 2. Confirm your auth flow already passes the v2.3.0 breaking-change gate: one McpServer and one transport per request, and cross-origin redirects are explicit (redirectPolicy: 'follow' if you want them). 3. On every requireBearerAuth and verifyBearerToken call, set expectedResource to your server's canonical URL. 4. Confirm with your authorization server that minted tokens for your MCP server include that URL in the aud claim. If your IdP mints opaque tokens, this fix will not work for you until you move to JWTs. 5. If you were deferring the v1.x → v2.x migration because of auth wiring, you no longer need to defer to get the audience check. The legacy path has it.

For operators on the legacy server (@modelcontextprotocol/server-legacy 2.2.x or earlier):

1. Bump @modelcontextprotocol/server-legacy to 2.3.1 and @modelcontextprotocol/core to 2.3.1 in lockstep. 2. Add expectedResource: new URL('https://your-mcp-server.example.com') to every requireBearerAuth call. 3. Confirm your IdP populates that URL in aud. If not, fix that first. 4. The same-day v1.32.1 bump on the v1.x mainline SDK is unrelated to security; take it or leave it, but pin it for the README clarity if you have any v1.x consumers in your stack.

For operators on the v1.x mainline SDK (@modelcontextprotocol/sdk 1.x):

1. If you are on 1.31.x, bump to 1.32.0 first to pick up expectedResource. Then bump to 1.32.1 for the README clarity. There is no functional change in 1.32.1. 2. Apply the same expectedResource wiring to your v1.x requireBearerAuth calls.

For everyone: do not bump past 2.2.x on @modelcontextprotocol/server or @modelcontextprotocol/server-legacy without also bumping @modelcontextprotocol/express. The v2.3.0 release notes call this out explicitly.

Related Dispatches