Software

Does Your MCP Client Leak OAuth Secrets? Check the SDK Version

By · Thu Oct 15 2026 · 9 min read · 0 views

View as a Web Story

Software#mcp#security#typescript#oauth#CVE

MCP TypeScript SDK affected and fixed version ranges for CVE-2026-104850

What is CVE-2026-104850, and does it affect your MCP client?

CVE-2026-104850 affects your MCP client if it uses the MCP TypeScript SDK as an HTTP client with an OAuth authProvider, connects to servers you do not fully trust and holds stored credentials. The flaw lets the connected server decide which authorization server receives those credentials. The GitHub advisory GHSA-6qxp-vccf-f47h rates it High, with a CVSS score of 7.5.

The MCP TypeScript SDK is the official JavaScript and TypeScript library for building clients and servers that speak the Model Context Protocol. Developers use it to let AI agents call tools over HTTP or stdio. OAuth is the standard sign-in protocol that those HTTP connections use to prove who the client is.

The advisory was published on September 30, 2026. The CVE record followed on October 6, 2026, according to a vulnerability tracker entry. That entry lists an EPSS score of 0.18 percent, no public proof-of-concept code and no listing in the CISA Known Exploited Vulnerabilities catalog at the time we read it. So the risk is real but not under active attack, as far as public sources show.

This guide is defensive. We did not build or run an attack. We installed six SDK versions, read the advisory line by line and wrote a short checker so you can see which of your projects sit in the affected ranges.

Affected and fixed version ranges for the MCP TypeScript SDK packages under CVE-2026-104850

Which MCP SDK versions are affected, and which are fixed?

Two npm packages are affected, and each has a fixed release. The table shows the ranges from the advisory next to what npm offered on the day we tested. Check the "Your action" column against npm ls.

Package Affected versions Fixed in Latest we saw Your action
@modelcontextprotocol/sdk 1.12.0 to 1.30.1 1.31.0 1.32.1 Upgrade to 1.31.0 or later
@modelcontextprotocol/client 2.0.0 to 2.1.0 (narrower configurations) 2.2.0 2.3.1 Upgrade to 2.2.0 or later

The client package is only affected in narrower setups. The advisory names the cases that still need care after the upgrade, and we cover them below. The older sdk package has the wider exposure, because its range covers every 1.x release from 1.12.0 on.

Version 1.30.1 sits one release below the fix. We installed it next to 1.31.0 and searched the compiled client code. The string expectedIssuer appeared in zero files of 1.30.1 and in eight files of 1.31.0. The word issuer appeared ten times in the OAuth module of 1.30.1 and thirty-four times in 1.31.0. That is a rough count, not a proof, but it shows the fix added issuer tracking throughout the OAuth code rather than a one-line patch.

How does the credential leak work, in plain English?

The leak works because the client trusted the MCP server to name its own authorization server, and then sent credentials there. When an HTTP MCP server needs sign-in, it publishes metadata that says which authorization server to use. The SDK followed that pointer. The advisory states the root cause directly: "Credentials were not tied to the authorization server they belong to."

An authorization server is the service that issues and checks OAuth tokens, such as your company's identity provider. A refresh token is a long-lived credential that lets a client get new access tokens without asking the user again. A client secret is the password your application itself uses to prove its identity to that service.

Advertisement

Now put the pieces together. A malicious or compromised MCP server could name a different authorization server in its metadata. The client would then send its stored refresh token and client secret to that address. The advisory says this happens "without user interaction," which is why the severity is high. The data confidentiality impact is rated High, while integrity and availability are not affected.

Notice what is not in this story. The advisory describes one thing: where the client sends the credentials it keeps for OAuth. That makes the fix narrow and the remediation concrete.

Is upgrading the SDK enough to fix CVE-2026-104850?

Upgrading is not enough for four bundled credential providers, which still follow whatever authorization server the MCP server names unless you pass expectedIssuer. The advisory lists them: ClientCredentialsProvider, PrivateKeyJwtProvider, StaticPrivateKeyJwtProvider and CrossAppAccessProvider. Versions 2.0.0 and 2.1.0 already accept the option.

The setting tells the provider which issuer you trust. Here is the shape from the advisory's own example:

const provider = new ClientCredentialsProvider({
  // ...your existing client ID, secret and options
  expectedIssuer: 'https://auth.example.com',
});

Use the issuer URL of your real authorization server. If you leave the option out, the provider behaves as before and trusts the server's choice. That is the quiet trap: the upgrade succeeds, the tests pass and the exposure remains.

The advisory also lists three follow-up changes after you upgrade:

  1. Add an issuer value to any credentials you saved without one, or clear them so users sign in again.
  2. If you wrote a custom OAuthClientProvider, save issuer along with what saveTokens() and saveClientInformation() receive.
  3. Re-test sign-in for each server your client talks to, because credentials without an issuer will no longer match.

The advisory is also clear about what the patch does not cover. New interactive sign-ins still go to the server-named authorization server. Direct calls to refreshAuthorization() and exchangeAuthorization() are not covered. The 2.x line with skipIssuerMetadataValidation: true is not covered either. If your code uses any of these, the upgrade alone leaves a gap, and you should review the call sites by hand.

How do you check your projects for the affected versions?

Scan the lockfile, not just package.json, because the SDK often arrives as a dependency of another package. We wrote a short script that reads package-lock.json and flags any copy of the two packages inside the affected ranges. It exits with code 1 when it finds one, so you can run it in CI.

// check-mcp-sdk.mjs — flags MCP SDK versions named in GHSA-6qxp-vccf-f47h
import { readFileSync } from "node:fs";

const cmp = (a, b) => {
  const [x, y] = [a, b].map((v) => v.split("-")[0].split(".").map(Number));
  for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] - y[i];
  return 0;
};
const RULES = {
  "node_modules/@modelcontextprotocol/sdk": (v) => cmp(v, "1.12.0") >= 0 && cmp(v, "1.31.0") < 0 && "upgrade to 1.31.0 or later",
  "node_modules/@modelcontextprotocol/client": (v) => cmp(v, "2.0.0") >= 0 && cmp(v, "2.2.0") < 0 && "upgrade to 2.2.0 or later",
};
const lock = JSON.parse(readFileSync("package-lock.json", "utf8"));
let bad = 0;
for (const [path, pkg] of Object.entries(lock.packages ?? {})) {
  const name = path.slice(path.lastIndexOf("node_modules/") === 0 ? 0 : path.lastIndexOf("node_modules/"));
  const fix = RULES[name]?.(pkg.version);
  if (fix) { bad++; console.log(`AFFECTED  ${name}@${pkg.version}  (${path})  ${fix}`); }
  else if (RULES[name]) console.log(`ok        ${name}@${pkg.version}`);
}
process.exit(bad ? 1 : 0);

We ran it against six folders, each with one pinned install. The results were exactly what the advisory predicts:

Folder Installed version Script output Exit code
1 sdk 1.30.1 AFFECTED, upgrade to 1.31.0 or later 1
2 sdk 1.31.0 ok 0
3 sdk 1.32.1 ok 0
4 client 2.1.0 AFFECTED, upgrade to 2.2.0 or later 1
5 client 2.2.0 ok 0
6 client 2.3.1 ok 0

Run it in each repository that touches MCP. For pnpm or Yarn projects, pnpm why @modelcontextprotocol/sdk and yarn why @modelcontextprotocol/sdk print the same dependency paths. Two limits apply. The script reads only npm lockfiles, so pnpm and Yarn projects need their own check, such as pnpm why @modelcontextprotocol/sdk. And it cannot tell whether you use the vulnerable code path. It only tells you the version. Treat a hit as a reason to read the next section, not as proof of exposure.

Decision path for an MCP client after CVE-2026-104850: version, provider type and trust in the server

Who is not affected by CVE-2026-104850?

Two groups are not affected, according to the advisory. MCP servers built with the SDK are not affected, because the flaw lives in the client's OAuth code. Stdio clients are not affected either, because a local process connection does not use this HTTP authorization flow.

This matters for planning. Many teams use the SDK on the server side only, to expose their own tools. Those teams can skip the emergency and upgrade on a normal schedule. Teams that build agents, desktop assistants or gateways that connect out to many third-party MCP servers are the ones to move first.

Exposure also depends on what you store. A client that holds no refresh token and no client secret has nothing for the flaw to send. A client that uses only stdio servers never reaches the code path. A client that connects to one server you run yourself has a trusted counterpart. The risky profile is a long-lived client, with saved credentials, connecting to servers run by other people.

What should you do if your client met an untrusted MCP server?

Rotate the credentials the client held. The advisory's remediation says that if a client may have connected to an untrusted server, you should rotate its client secret or signing key and revoke its tokens. Do this even after you upgrade, because the upgrade protects future connections and cannot recall what was already sent.

Follow this order:

  1. Upgrade the SDK and set expectedIssuer first, so the new credentials are not exposed the same way.
  2. Rotate the client secret or signing key at your authorization server.
  3. Revoke outstanding refresh tokens for that client.
  4. Clear saved credentials that lack an issuer, or add the value.
  5. Review which MCP servers the client has contacted, and remove any you do not recognise.

Step 1 comes first for a reason. If you rotate secrets before you fix the client, the client can send the new secret to the same wrong place. The order is the whole point of the list.

There is no public sign that this flaw is being used in attacks. Still, MCP servers are easy to publish, and users add them to clients quickly. That is the pattern our earlier posts on MCP security keep returning to, such as Postgres MCP Read-Only Mode Can Be Bypassed. The client is where trust decisions get made, so it is where hardening pays off.

Should you upgrade the MCP TypeScript SDK now?

Upgrade now if your project is an HTTP MCP client that holds OAuth credentials. The fixed releases are available, the change is a version bump plus one option, and the risk of waiting is credentials leaving your system. Upgrade on your normal schedule if you only build servers or only use stdio.

The bottom line has three parts. First, check the lockfile with the script above. Second, bump to 1.31.0 or 2.2.0, or to the newer releases we saw, 1.32.1 and 2.3.1. Third, set expectedIssuer on any of the four bundled providers, because the version bump alone does not change their behaviour.

We would also add one rule to your agent code review: no MCP client should accept an authorization server it did not expect. That rule outlives this CVE. If you want a second pair of eyes on an agent that talks to many MCP servers, that is the kind of review I do for teams.

If your client also fails to connect for reasons that have nothing to do with security, see Claude Cannot Reach Your MCP Server But curl Can for the usual network causes.

Advertisement

FAQ

Which MCP TypeScript SDK versions fix CVE-2026-104850?

Version 1.31.0 of `@modelcontextprotocol/sdk` and version 2.2.0 of `@modelcontextprotocol/client` fix it. The affected ranges are 1.12.0 to 1.30.1 and 2.0.0 to 2.1.0. At the time of our test, npm offered 1.32.1 and 2.3.1, which include the fix.

Do I need expectedIssuer after upgrading the MCP SDK?

Yes, for four bundled providers: `ClientCredentialsProvider`, `PrivateKeyJwtProvider`, `StaticPrivateKeyJwtProvider` and `CrossAppAccessProvider`. Without `expectedIssuer`, they still use whichever authorization server the MCP server names. Pass the issuer URL you trust, for example `https://auth.example.com`.

Are MCP servers built with the TypeScript SDK affected?

No. The advisory says servers built with the SDK are not affected, and neither are stdio clients. The flaw sits in the OAuth client code that follows an HTTP server's authorization metadata. Server-only projects can upgrade on a normal schedule.

What credentials could leak through CVE-2026-104850?

A client's stored refresh token and client secret, or its configured client secret or signed assertion. A malicious server could name its own authorization server, and the client would send the credentials there without asking the user. The advisory rates the confidentiality impact as High.

How do I know if my project uses a vulnerable MCP SDK version?

Run `npm ls @modelcontextprotocol/sdk @modelcontextprotocol/client` and compare the output with the affected ranges. Better, scan `package-lock.json` with a script, because the SDK often arrives as a nested dependency. A hit means upgrade; it does not prove your code was exposed.

Comments

Loading…

Sign in to join the conversation.

Related posts