A malicious MCP server could make the MCP TypeScript SDK send your OAuth credentials to a server it controls. The flaw, CVE-2026-104850 (GHSA-6qxp-vccf-f47h), is rated high (CVSS 7.5). If you build or run an MCP client on @modelcontextprotocol/sdk that connects to remote servers over OAuth, upgrade and check the steps below.
| Question | Short answer |
|---|---|
| Am I affected? | If your client uses @modelcontextprotocol/sdk 1.12.0 to 1.30.1, or @modelcontextprotocol/client 2.0.0 to 2.1.0 in narrower configurations, and connects to remote HTTP servers |
| Is there a fix? | Yes: sdk 1.31.0, client 2.2.0 (and core 2.2.0 if imported directly) |
| Are servers or stdio clients affected? | No, per the advisory |
| Does data leave my machine? | Only if your client connected to an untrusted server, which is the attack |
| Is upgrading enough? | No. Set expectedIssuer, fix saved credentials, rotate if exposed |
Timeline
The GitHub advisory lists these dates:
- September 28, 2026: the maintainers published release 2.2.0 of the 2.x packages. The release notes point to pull request 2887, which deprecates omitting
expectedIssuerand applies the issuer check in more places. - September 30, 2026: the advisory was published in the
modelcontextprotocol/typescript-sdkrepository. - October 6, 2026: GitHub reviewed it and added it to the Advisory Database.
The advisory credits seven reporters by handle. It describes no exploitation in the wild, and it gives the weaknesses as CWE-345 (insufficient verification of data authenticity) and CWE-522 (insufficiently protected credentials). Its CVSS 3.1 vector is AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N: reachable over the network, low complexity, no privileges and no user interaction, with the impact limited to confidentiality. On the day we rechecked, October 20, the advisory page showed an EPSS score of 0.176%, which FIRST defines as an estimate of the chance of exploitation in the next 30 days. Treat that number as a snapshot; it changes.
How the attack works
In the MCP authorization flow, a server tells the client where its authorization server lives. The MCP authorization spec has clients discover that through OAuth Protected Resource Metadata. The spec also says clients must record the expected issuer and must not send tokens other than ones issued by the MCP server's own authorization server.
According to the advisory, the vulnerable SDK did not hold the line. A malicious or compromised server could name an authorization server of its choosing, and the client would then send stored credentials there, with no user prompt. Exposed material includes the refresh token and client secret, or the client secret or signed assertion of a bundled provider.
The practical path: a user or agent adds a remote MCP server that looks harmless, or a trusted one is compromised. On the next connection, the client talks to the attacker's authorization server instead. The attacker now holds a refresh token that may work against the real service.
Where the check was missing
The specification asks for more than the old SDK did. In its authorization section, the MCP specification says the client must record the issuer value from the selected authorization server's validated metadata before redirecting the user. It also requires validation of the iss parameter in authorization responses, following RFC 9207, before the client sends an authorization code to any token endpoint. That check is aimed at mix-up attacks, where a response from one authorization server is passed off as coming from another. And it states: "MCP clients MUST NOT send tokens to the MCP server other than ones issued by the MCP server's authorization server."
Those rules cover the interactive sign-in. The advisory describes a gap in a different place: credentials that already existed. A refresh token and client secret saved from an earlier sign-in, or a client secret or signed assertion configured on a bundled provider, carried no record of which authorization server they belonged to. When the MCP server later advertised a different authorization server, the client had nothing to compare against and sent them.
What the fix changes in the code
The advisory says the patched client now records the authorization server as issuer on saved credentials and refuses to send them to a different one. The user signs in again, or the call throws.
The pull request adds detail on how that looks to a developer:
- The four bundled machine-to-machine providers,
ClientCredentialsProvider,PrivateKeyJwtProvider,StaticPrivateKeyJwtProviderandCrossAppAccessProvider, hold credentials the SDK did not obtain itself, so onlyexpectedIssuercan say which server they belong to. Constructing one without it is now deprecated and logs oneconsole.warnper construction. An empty or null value throws at construction. fetchToken()now throwsAuthorizationServerMismatchErrorbefore preparing or sending any request when the stored client information is bound to a different authorization server.OAuthTokensSchemaandOAuthClientInformationSchemaused to strip theissuerfield, so a provider that read its storage back through them lost the binding on every read. They now accept it.- A provider that implements both
saveClientInformation()andaddClientAuthentication()no longer registers again silently when the authorization server changes. It getsAuthorizationServerMismatchError, and clearing the stored client information (invalidateCredentials('client')) is how you move to the new server on purpose.
The pull request says these are bug-fix changes without API breaks, with one behavioural point worth testing: a console warning where expectedIssuer is missing, and the new error type where a stored binding disagrees with the server.
Who is affected
| Package | Affected | Fixed |
|---|---|---|
@modelcontextprotocol/sdk | 1.12.0 – 1.30.1 | 1.31.0 |
@modelcontextprotocol/client | 2.0.0 – 2.1.0 (narrower configurations) | 2.2.0 |
@modelcontextprotocol/core | imported directly | 2.2.0 |
What this means if you run AI coding agents
The advisory concerns applications that use the SDK's OAuth client, so the direct risk is to anyone building MCP clients, agent harnesses or internal tooling on the TypeScript SDK. The advisory does not say which off-the-shelf agents embed the SDK, so check any tool you build or ship, and watch for vendor updates from the tools you use.
- Find the version. Run
npm ls @modelcontextprotocol/sdkandnpm ls @modelcontextprotocol/clientin each project that embeds an MCP client. - Upgrade to sdk 1.31.0 or client 2.2.0.
- Bundled providers: set
expectedIssuer, for exampleexpectedIssuer: 'https://auth.example.com'. - Saved credentials: add an
issuerfield to them, or clear them so users sign in again. - Custom
OAuthClientProvider: persist theissuervalue passed tosaveTokens()andsaveClientInformation(). - If the client may have connected to an untrusted server: rotate its client secret or signing key and revoke its tokens.
Know what the fix leaves open. New interactive sign-ins still go to whichever authorization server the MCP server names, so only finish them with servers you trust. Direct calls to refreshAuthorization() and exchangeAuthorization(), and 2.x setups using skipIssuerMetadataValidation: true, are not covered.
A short audit you can do today
These steps use only the package manager and the SDK's own interfaces:
- Search your code for the entry points the advisory names: an
authProvideron a transport, thewithOAuth()middleware, and direct calls toauth()orfetchToken(). Any of them puts you in the first condition of "Am I affected?". - Search for
expectedIssuer. Each bundled provider you construct should pass the issuer of the authorization server your credentials were registered with. - Look for the new deprecation warning in your logs after upgrading. One warning per construction means a provider is still missing
expectedIssuer. - Inspect where your
OAuthClientProviderpersists tokens and client information, whether a file, keychain or database. Records written before the upgrade, including everything 1.x saved before 1.31.0, have noissuer. Add it or clear them. - Exercise the failure path in a test: a stored credential bound to one issuer and a server that names another should now throw, not send.
If you cannot upgrade yet, the advisory's only workaround is to connect OAuth clients only to MCP servers you trust. It also notes that 2.0.0 and 2.1.0 already accept expectedIssuer, so you can set it before you move to 2.2.0.
To tell if you were hit, look for OAuth requests from your client to authorization hosts you do not recognise, and for unexpected use of your refresh tokens at the provider. The advisory does not describe in-the-wild exploitation.
Why the residual cases matter
Three cases remain after upgrading, and each one has a different owner.
A new interactive sign-in still goes where the MCP server points. No library can fully solve that, because at first contact the client has no earlier record of the right authorization server. The decision belongs to the person approving the connection. This is the same trust decision covered in reviewing MCP servers before you connect an agent.
Direct calls to refreshAuthorization() and exchangeAuthorization() bypass the new binding, so code that calls them has to do its own check. And skipIssuerMetadataValidation: true in 2.x turns off the validation the fix depends on; the pull request notes that the unchecked issuer is also what stamps and expectedIssuer are compared with.
Because the vulnerable condition needs the client to hold credentials for a legitimate authorization server and meet an untrusted MCP server, the exposure is largest for long-lived agent harnesses and internal tools that connect to many servers. A desktop app that talks to one first-party server is much less exposed. The advisory does not rank the risk by product type, so this is our reading of its conditions, not a finding.
What Beam can and cannot do here
Beam's agent hooks and config scan cannot see inside your own client's OAuth code, and they will not flag this CVE. For your own dependency tree, beam cve check . reads lockfiles (npm, yarn, pnpm and bun among them), sends only package names and versions, and matches them against AgentBeam's CVE database, exiting with code 2 when a finding meets --fail-on so it can gate CI. Whether a given advisory is covered depends on the database, so look it up in the CVE database. See Check dependencies against known CVEs in CI. What it does offer is a pre-connect review habit: beam scan .mcp.json --mcp checks an MCP config for risky patterns and version pinning without launching the servers. That helps you notice unfamiliar remote servers before an agent connects. The scan is heuristic and cannot judge a server's runtime behaviour. For the review process, see MCP security: review servers before connecting your agent and the practical MCP guide.
Why this matters beyond one SDK
Remote MCP servers are untrusted input that now sits on an authentication path. The same lesson showed up in agentjacking via an MCP server and in Cursor MCP config poisoning: the thing you connect to can steer the client. Treat every server entry as a trust decision, and see the wider supply-chain picture. Two October posts show other ways a connected component can reach credentials or code: npm packages that hijack MCP and LLM traffic and the Langflow MCP stdio remote code execution flaw.
Frequently asked questions
What is CVE-2026-104850?
It is a high-severity (CVSS 7.5) flaw in the MCP TypeScript SDK OAuth client. A malicious or compromised MCP server could name its own authorization server, and the client would send stored credentials to it without user interaction.
Which versions are affected and which are fixed?
@modelcontextprotocol/sdk 1.12.0 through 1.30.1 is affected and 1.31.0 is fixed. @modelcontextprotocol/client 2.0.0 through 2.1.0 is affected in narrower configurations and 2.2.0 is fixed.
Are MCP servers or stdio clients affected?
No. The advisory says MCP servers built with the SDK and stdio clients are not affected. The risk is in OAuth clients that connect to remote HTTP MCP servers.
Is upgrading enough?
Not always. Bundled providers should set expectedIssuer, saved credentials need an issuer field or must be cleared, and anything that may have reached an untrusted server should be rotated and revoked.
What does the fix not cover?
New interactive sign-ins still go to whichever authorization server the MCP server names. Direct calls to refreshAuthorization() and exchangeAuthorization(), and 2.x setups with skipIssuerMetadataValidation: true, are also outside the fix.
Can Beam detect this vulnerability?
No. Beam scans MCP config files with heuristics and a version-pin check, but it does not read the dependency tree of your own MCP client code. Use npm ls to check the SDK version.
Related reading
- MCP security: review servers before connecting your agent
- MCP security practical guide
- Agentjacking through an MCP server
- Four more MCP TypeScript SDK advisories fixed in 1.32.0
- npm packages that hijack MCP and LLM traffic
- Official advisory and MCP authorization specification
Versions and advisory details were first written on October 13, 2026 and rechecked against the advisory, release notes and pull request on October 20, 2026. Check the advisory for later changes.

