In affected versions, the SDK's OAuth client let the MCP server decide which authorization server received the client's OAuth credentials. Credentials were not tied to the authorization server they belong to.
A malicious or compromised MCP server could name its own authorization server. With no user interaction, the client would send it:
refresh_token and client_secret stored from an earlier sign-inclient_secret or signed assertion configured on a bundled providerYes, if both of these hold:
authProvider on a transportwithOAuth() middlewareauth() or fetchToken()Affected versions:
@modelcontextprotocol/sdk 1.12.0 through 1.30.1@modelcontextprotocol/client 2.0.0 through 2.1.0, only for:
expectedIssuerissuerfetchToken()OAuthTokensSchema or OAuthClientInformationSchemaNot affected:
Upgrade to:
@modelcontextprotocol/sdk 1.31.0 or later@modelcontextprotocol/client 2.2.0 or later, and @modelcontextprotocol/core 2.2.0 or later if you import it directlyThe client now records the authorization server as issuer on saved credentials and does not send them to a different one. The user signs in again, or the call throws.
In the following cases, upgrading to the patched version is not enough. You also need to make a change:
ClientCredentialsProvider, PrivateKeyJwtProvider, StaticPrivateKeyJwtProvider, CrossAppAccessProvider): pass expectedIssuer, for example expectedIssuer: 'https://auth.example.com'. Without it they still use whichever authorization server the MCP server names.issuer: tokens and client information your OAuthClientProvider persisted (file, keychain, database) before upgrading, including everything 1.x saved before 1.31.0. They still go to whichever authorization server is named at first use. Add issuer to them, or clear them so users sign in again.OAuthClientProvider: save exactly what saveTokens() and saveClientInformation() are given, including issuer. For pre-registered credentials, include issuer in what clientInformation() returns.Not covered by this fix:
refreshAuthorization() and exchangeAuthorization() called directlyskipIssuerMetadataValidation: trueIf an affected client may have connected to an untrusted MCP server, rotate its client secret or signing key and revoke its tokens.
If you cannot upgrade yet, connect OAuth clients only to MCP servers you trust. 2.0.0 and 2.1.0 already accept expectedIssuer.
{
"cwe_ids": [
"CWE-345",
"CWE-522"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-06T15:35:44Z",
"nvd_published_at": null,
"severity": "HIGH"
}