MCP SDK 2.3.1: Legacy Server Patch Closes the Token Gap

Article image
15 Oct 2026
9 min
MCP
AI Agents
Docusign IAM

MCP SDK 2.3.1, released October 5, 2026, adds an optional expectedResource parameter to requireBearerAuth in the @modelcontextprotocol/server-legacy package, letting legacy MCP servers verify that an incoming bearer token was actually issued for them. The change matches what @modelcontextprotocol/server 2.3.0 and @modelcontextprotocol/sdk 1.32.0 already shipped three days earlier, closing a gap where legacy-track servers had no way to enforce audience binding on tokens.

If you run an MCP server on the legacy package and you have not upgraded past 2.3.1, treat this as a security patch, not a routine version bump. Without it, your server accepts any valid bearer token regardless of which resource the token's issuer meant it for, including a token minted for a completely different MCP server.

What exactly changed in MCP SDK 2.3.1?

The patch gives requireBearerAuth in @modelcontextprotocol/server-legacy the same optional expectedResource option that the non-legacy server package picked up in version 2.3.0. Set it to the resource identifier your authorization server embeds in tokens, usually the MCP server's own URL, and the middleware now checks that value against AuthInfo.resource before letting a request through. The two values are compared as strings, ignoring a URL fragment and a single trailing slash.

If the token's audience does not match, or the field is missing entirely, the server answers with a 401 invalid_token response and the standard WWW-Authenticate challenge header. Leave expectedResource unset and behavior is unchanged from before 2.3.1, so the upgrade itself does not break anything. It is opt-in by design, which is exactly why teams tend to skip it unless someone flags the release notes.

ts
import { requireBearerAuth } from "@modelcontextprotocol/server-legacy"; app.use( requireBearerAuth({ verifier: myTokenVerifier, expectedResource: "https://mcp.example.com/agreements", }) );

That one extra field is the entire patch. There is no new transport, no changed handshake, nothing else to migrate.

Why did the legacy package need a separate patch?

@modelcontextprotocol/server-legacy is a frozen copy of the SDK's original 1.x HTTP+SSE transport and OAuth authorization-server helpers, kept around so older clients and servers built against the pre-Streamable-HTTP transport keep working. Because it is frozen, it did not automatically inherit the expectedResource option when the current server package added it in 2.3.0. For three days, anyone still building against server-legacy had a requireBearerAuth that could verify a token's signature and expiry but had no concept of audience at all.

That gap mattered more than a typical legacy-package lag. The Model Context Protocol's own authorization specification requires audience binding using RFC 8707 resource indicators, and later spec revisions attach a hard MUST to it precisely because a token without audience binding can be replayed against any resource server that accepts tokens from the same authorization server. If your MCP deployment still depends on the legacy transport, for a long-lived internal tool, a vendor integration that has not migrated yet, or a server frozen for compatibility reasons, you were running a known audience-binding gap until you pulled in 2.3.1.

What is audience binding and why does it matter for agentic integrations?

Audience binding is the rule that a bearer token is only valid for the specific resource server it was issued for, not for any server that happens to trust the same identity provider. Without it, a token obtained legitimately for one MCP server can be replayed against a different MCP server sharing the same authorization server, and that second server has no way to tell the token was never meant for it.

This is not an abstract risk once you connect MCP servers to systems that take real actions. An AI agent operating against agreement data (sending documents, pulling contract terms, triggering approvals) is exactly the kind of resource server where a stolen or misdirected token should fail loudly, not quietly succeed. Docusign's MCP connector is a working example of this pattern in production: it gives Claude and other MCP hosts scoped access to agreement intelligence and lifecycle actions, which means the authorization layer in front of it is doing real work, not just gating a read-only demo. We covered the mechanics of that connector in our guide on connecting AI agents to Docusign over MCP.

Teams building or operating any MCP server that fronts agreement, financial, or identity data should treat expectedResource as a baseline control, not an optional hardening step. It costs one config line and it is the difference between a token scoped to your server and a token that happens to work on your server because nobody checked.

What does this mean if you are running Docusign agent integrations?

If you are building an MCP server that proxies or wraps agreement actions, your own server, not Docusign's hosted connector, the audience-binding question is yours to answer. A custom MCP server sitting in front of Docusign IAM, built on either the legacy or current SDK package, should set expectedResource to its own canonical URL so a token minted for a different internal MCP server cannot be replayed against it.

This is a narrower concern than webhook security, but the discipline is the same one we described in our piece on triggering Docusign Workflow Builder runs from any platform: verify that the thing calling you is actually who it claims to be, every time, not just at initial setup. For webhook-triggered automations specifically, that verification happens via HMAC signatures on Docusign Connect payloads; for MCP tool calls, it is expectedResource doing the equivalent job on the OAuth side.

We have also written about the production patterns for running Claude agents against the Docusign MCP connector, and the same lesson applies there: agent-facing integrations into agreement systems need the authorization layer taken as seriously as the business logic, because the failure mode is not a bug report, it is an agent doing something it should not have been able to do.

How do you upgrade safely?

  1. Check your package versions. Run npm ls @modelcontextprotocol/server-legacy or check your lockfile. Anything below 2.3.1 lacks expectedResource entirely.
  2. Upgrade the package, not just the lockfile. npm install @modelcontextprotocol/server-legacy@2.3.1. No breaking changes ship with this patch, so this should be a drop-in bump.
  3. Set expectedResource explicitly. It stays off unless you configure it, so upgrading alone does nothing. Set it to your server's canonical URL and confirm your authorization server actually populates the matching resource claim in tokens it issues.
  4. Confirm your verifier fills AuthInfo.resource. The check only works if whatever verifies the token (your own code or a library) maps the token's audience claim into AuthInfo.resource before requireBearerAuth runs.
  5. Test the 401 path. Issue a token scoped to a different resource and confirm your server rejects it with invalid_token, not a silent pass.

If you are on Express specifically, note that the equivalent fix on the non-legacy server package required upgrading @modelcontextprotocol/express alongside server 2.3.0, because the older Express adapter did not pass the option through. Check whether your legacy-track transport adapter has the same dependency before assuming the patch alone is sufficient.

How does this compare to the other recent MCP SDK auth patches?

This is the third MCP TypeScript SDK release in recent months to tighten the authorization layer, and the pattern is worth tracking if you run MCP infrastructure in production.

ReleaseDateWhat it changed
2.1.0earlier 2026Added OAuth DPoP token support and tightened scope-challenge handling, covered in our MCP SDK 2.1.0 breakdown
2.3.0Oct 2, 2026Added expectedResource audience binding to the current server package and maxToolInputElements limits, covered in our MCP SDK 2.3.0 piece
2.3.1Oct 5, 2026Brought the same expectedResource option to server-legacy, the subject of this article

None of these are breaking changes. All three are opt-in hardening that the SDK maintainers shipped ahead of the spec tightening further. Taken together, they show the MCP ecosystem converging on the same two controls OAuth 2.1 already expects: proof-of-possession for tokens (DPoP) and audience restriction (resource indicators). If you run any MCP server, current or legacy track, both are worth having configured rather than treating either as a nice-to-have.

The three-day gap between 2.3.0 and 2.3.1 is itself a useful data point. It tells you the legacy package is not on the same release cadence as the current one; new security options land on server first and server-legacy catches up only when someone notices and files the patch. If you are pinned to server-legacy for any reason, budget for that lag when a security-relevant SDK change ships, and don't assume parity until you check the legacy package's own changelog.

Should you still be running server-legacy at all?

@modelcontextprotocol/server-legacy is explicitly described as a frozen copy of the original SSE transport and OAuth helpers, kept for compatibility and marked deprecated in favor of Streamable HTTP and a dedicated OAuth authorization server in production. 2.3.1 is a security-parity patch, not a signal that the legacy package is getting ongoing feature investment. If you are maintaining a server on this package purely because migrating clients felt like more work than it was worth, this release is a reasonable prompt to budget that migration rather than keep patching a frozen transport one option at a time.

FAQ

Does upgrading to MCP SDK 2.3.1 break existing legacy servers? No. expectedResource is optional and off by default, so servers that do not set it behave exactly as before. The release notes confirm the package otherwise stays frozen.

Is expectedResource required by the MCP specification? The MCP authorization specification requires resource indicators under RFC 8707 for audience binding, so implementing expectedResource is how the SDK satisfies that spec requirement in practice; the SDK itself leaves the option off by default rather than forcing it on.

What happens if a token's audience does not match expectedResource? The server responds 401 invalid_token with the standard WWW-Authenticate challenge header, the same response shape used for any other bearer-auth failure.

Does this affect Docusign's own MCP connector? This patch is about MCP servers you build and operate yourself. Docusign's hosted MCP connector is Docusign's infrastructure; the relevant action item here is for teams running their own MCP server in front of Docusign IAM or any other agreement data, where you control the SDK version and the expectedResource configuration.

Next step

If you are building a custom MCP server that fronts Docusign IAM or another agreement system, audience binding is a small piece of a much larger authorization design. Talk to the fluidlabs team about reviewing your MCP and Docusign integration architecture before an agent gets access it should not have.

Other articles
Get in touch

Tell us what Docusign needs to talk to

Thirty minutes with an engineer. Within 48 hours you get a scoped plan and a fixed price in writing, or a straight “you don't need us.” No charge, nothing to sign.

Talk to an engineer →

or email us at hello@fluidlabs.com