MCP TypeScript SDK OAuth flaw: credentials followed the wrong issuer
A high-severity MCP TypeScript SDK flaw let the server choose which authorization server got client secrets. Patch, bind and rotate.
On 30 September 2026 the Model Context Protocol TypeScript SDK maintainers published advisory GHSA-6qxp-vccf-f47h: in affected versions the OAuth client let the MCP server decide which authorization server received the client's OAuth credentials, and those credentials were not tied to the issuer they belonged to. The rating is CVSS 7.5 high (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N). Say high, not critical. Upgrading alone is not enough if stored tokens lack an issuer or bundled providers omit expectedIssuer.
Context: what was wrong
The advisory says a malicious or compromised MCP server could name its own authorization server. With no user interaction, the client would send it the refresh_token and client_secret from an earlier sign-in, or the client_secret or signed assertion configured on a bundled provider.
Affected packages and versions:
@modelcontextprotocol/sdk1.12.0 through 1.30.1 (patched in 1.31.0+)@modelcontextprotocol/client2.x before 2.2.0 for bundled providers withoutexpectedIssuer, credentials withoutissuer, directfetchToken()calls, and providers that read storage through certain schemas (patched in 2.2.0+, with@modelcontextprotocol/core2.2.0+ if imported directly)
You are in scope if the application uses the SDK's OAuth client over HTTP (authProvider, withOAuth(), auth() or fetchToken()) and may connect to an MCP server you do not fully trust while holding credentials for a legitimate authorization server.
Not affected: MCP servers built with the SDK, and stdio clients.
A Cloud Security Alliance research note on a related Python SDK OAuth pattern (also dated 30 September 2026) describes the same structural risk: the MCP server is often the first party to tell the client where to send credentials, and SDKs that treat that metadata as trustworthy by default break down once agents connect to third-party servers. That note is about the Python OAuth discovery path, not this TypeScript advisory; use it for the pattern, not for TypeScript version numbers.
Analysis: patch is necessary but not sufficient
The fixed client 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.
The advisory is clear that upgrading is not enough in several cases:
- Bundled providers (
ClientCredentialsProvider,PrivateKeyJwtProvider,StaticPrivateKeyJwtProvider,CrossAppAccessProvider) still needexpectedIssuer(for exampleexpectedIssuer: 'https://auth.example.com'). Without it they still use whichever authorization server the MCP server names. - Credentials saved without
issuer(including everything 1.x saved before 1.31.0) still go to whichever server is named at first use. Addissuer, or clear them so users sign in again. - Custom
OAuthClientProviderimplementations must persistissuerfromsaveTokens()/saveClientInformation()and include it for pre-registered credentials.
Not covered by the fix: a new interactive sign-in still goes to the authorization server the MCP server names; only complete sign-ins for servers you trust. Direct calls to refreshAuthorization() / exchangeAuthorization(), and 2.x with skipIssuerMetadataValidation: true, are also called out.
If an affected client may have connected to an untrusted MCP server, rotate the client secret or signing key and revoke tokens.
This is a different failure mode from the Python SDK's cross-origin redirect of custom headers (advisory GHSA-5h93-6whr-6q8j). Do not merge the two into one "MCP OAuth" story in a risk register.
After the upgrade, operators still have work that a version bump alone does not finish. Bundled providers that omit expectedIssuer continue to trust whatever issuer the MCP server advertises. Stored credentials that were saved without an issuer binding should be cleared and obtained again from a known authorization server. If any untrusted MCP server was connected while the vulnerable range was installed, treat client secrets and refresh tokens as compromised and rotate them.
Interactive first-time sign-in remains a separate trust decision: the user may still be sent to an authorization server named by the MCP server. The patch binds stored credential reuse more tightly; it does not make every new OAuth handshake safe against a malicious server. Only connect MCP servers your organisation has reviewed, and prefer a gateway that holds connector credentials off the laptop.
What it means for engineering and security leaders
MCP clients are OAuth clients with a hostile metadata path. Conventional OAuth often hard-codes the issuer. MCP clients learn an issuer from a server that may be untrusted. That is closer to the SSRF and untrusted-URL problems in MCP servers and SSRF than to a normal SaaS login bug.
Inventory HTTP OAuth clients, not only servers. Teams that "only run MCP servers" are out of scope for this advisory. Teams that ship IDE plugins, agent hosts or gateways that speak OAuth to remote MCP servers are in scope.
Treat connector allowlists as credential policy. After the 2026-07-28 MCP specification changes for operators, issuer binding belongs next to tool permissions and approval flows.
Inventory every @modelcontextprotocol/sdk and @modelcontextprotocol/client pin. Agent harnesses, IDE extensions and internal MCP clients often pin the SDK transitively. A single outdated leaf package is enough.
Treat issuer metadata as untrusted input. The same habit that stops SSRF against internal URLs should stop OAuth clients from sending secrets to whatever authorization_servers field a remote MCP server returns.
Practical takeaways for this week
- Upgrade
@modelcontextprotocol/sdkto 1.31.0+ or@modelcontextprotocol/client(and core if needed) to 2.2.0+. - Set
expectedIssueron every bundled provider; fail closed if metadata names another issuer. - Clear or migrate tokens stored without
issuer; force re-auth where you cannot migrate. - Rotate client secrets and revoke refresh tokens for any client that may have contacted an untrusted MCP server.
- Restrict OAuth MCP clients to servers you trust until the upgrade and rotation are done.
- Separate this advisory from Python redirect-header work so owners and due dates stay distinct.
Where SpanDock fits
The SpanDock MCP gateway gives every computer's AI tools one MCP server on SpanDock, connects on your server to the organisation's connectors, keeps connector credentials on the server, and records every call. The MCP gateway is on the Premium and Enterprise plans, and in the 30-day trial. Pair that with privacy expectations for where secrets live. For a first connector rollout, see set up the MCP gateway.