---
id: 2026-09-mcp-python-sdk-oauth-issuer-validation-credential-redirect
title: "Official MCP Python SDK (`mcp` 1.9.1–1.29.1, 2.0.0–2.1.1): the OAuth client let the MCP server choose the authorization server, so a malicious or compromised server received the client secret, authorization code, PKCE verifier or signed assertion meant for the real login provider (GHSA-qx49-fqc8-xw99, CVSS 7.5); fixed 1.30.0 / 2.2.0 — and two providers need an explicit issuer= after upgrading"
date_disclosed: 2026-09-28
last_updated: 2026-10-08
severity: high
status: patched
ecosystems: [pypi, npm, mcp, python, typescript]
tools_affected: ["mcp (modelcontextprotocol/python-sdk) OAuthClientProvider", "ClientCredentialsOAuthProvider", "PrivateKeyJWTOAuthProvider", "deprecated 1.x RFC7523OAuthClientProvider", "every MCP client, agent and gateway built on the Python SDK that connects to remote HTTP MCP servers with OAuth", "@modelcontextprotocol/sdk 1.12.0–1.31.0 and @modelcontextprotocol/client 2.0.0–2.2.0 (TypeScript SDK: same OAuth bug CVE-2026-104850, plus redirect and task-store advisories)"]
tags: [cve-pending, ghsa, mcp, oauth, account-takeover, credential-theft, issuer-validation, sdk, silent-fix, configuration-required-after-upgrade]
---

## TL;DR

The reference Python implementation of the Model Context Protocol shipped an OAuth client that **did not check who the authorization server was**. An MCP server the client connected to could name its own authorization server in its protected-resource metadata — or publish none and serve metadata that presented the user's real provider as the issuer on a fallback path — and the SDK would send that server the **client secret, the authorization code, the PKCE `code_verifier`, or the signed JWT assertion** it should only ever have sent to the real login provider. That is account takeover of whatever the client used those credentials for. The vendor advisory (published **2026-09-28**) rates it **High, CVSS 7.5** for the unattended providers and 6.5 for the interactive one; there is no CVE yet. Fixes are **`mcp` 1.30.0 and 2.2.0** — both on PyPI since **2026-09-07**, three weeks before disclosure, with the issuer checks listed in release notes as behaviour changes. **Upgrading alone is not enough** for `ClientCredentialsOAuthProvider` and `PrivateKeyJWTOAuthProvider`: you must pass `issuer=`, or the check has nothing to compare against until 3.0 makes the argument mandatory. Servers built on the SDK, stdio clients, and clients that manage their own tokens are not affected.

## What happened

The SDK's OAuth client support performs discovery: connect to an MCP server, read its protected-resource metadata to learn which authorization server to use, fetch that server's metadata, then run the flow. The advisory describes two gaps:

- **1.9.1 through 1.29.1** — no issuer validation and no credential binding on *any* discovery path. The MCP server decides where credentials go.
- **2.0.0 through 2.1.1** — checks were added on the main path but were **missing on the 404-fallback path and the 403 step-up path**. Cycode's write-up: "when a malicious MCP server returns a 404 to discovery requests, the SDK never verifies the login provider's identity before accepting its configuration."

Impact by provider (advisory wording): a compromised or malicious server "could intercept client secrets, authorization codes, PKCE verifiers, or signed assertions by either advertising its own authorization server or manipulating metadata responses." For the **interactive** `OAuthClientProvider` the user must complete a login, hence 6.5; for the **unattended** `ClientCredentialsOAuthProvider` and `PrivateKeyJWTOAuthProvider` the credential is sent with no interaction, hence 7.5. The deprecated 1.x `RFC7523OAuthClientProvider` is in the affected list too.

Who this reaches: every MCP *client* built on the Python SDK that talks to remote HTTP servers with OAuth — agent frameworks, gateways, and the "connect your agent to this SaaS" integrations that hold a real client secret for the SaaS. Connecting to one untrusted or later-compromised MCP server was enough. **Not affected:** MCP servers built with the SDK, stdio transports, and clients that obtain and manage tokens themselves.

Timeline, as the sources state it: reported through Anthropic's security process by eight researchers (credited on the advisory); **1.30.0 and 2.2.0 published 2026-09-07** (PyPI upload dates confirmed this sweep) with the issuer checks described as behaviour changes rather than a security fix; **vendor advisory and Cycode's analysis on 2026-09-28**; The Hacker News on 09-29. No exploitation is reported. Note the pattern this repo keeps recording: a three-week window in which the fix was public, the reason was not, and anyone pinning an older `mcp` had no signal to move.

The TypeScript SDK's advisory tab (checked 2026-09-30) carries no equivalent entry; that is not evidence it is unaffected, only that nothing has been published.

## Am I affected?

```bash
# Which mcp is installed, and does anything you run use an OAuth provider?
pip show mcp 2>/dev/null | grep -i '^version'
pip index versions mcp | head -1                     # 2.2.0 is latest as of 2026-09-30
grep -rn "OAuthClientProvider\|ClientCredentialsOAuthProvider\|PrivateKeyJWTOAuthProvider\|RFC7523OAuthClientProvider" --include=*.py . | head

# Affected: 1.9.1 <= mcp <= 1.29.1, or 2.0.0 <= mcp <= 2.1.1, AND one of the providers above is used
# AND the client connects over HTTP to servers you do not fully control.
# After upgrading: do the two unattended providers pass issuer=?
grep -rn "ClientCredentialsOAuthProvider(\|PrivateKeyJWTOAuthProvider(" --include=*.py -A6 . | grep -c "issuer="
```

Stored client registrations from before the upgrade were made under the old rules; the advisory says to clear them.

## If you are affected

1. Upgrade to **`mcp` ≥ 2.2.0** (or ≥ 1.30.0 on the 1.x line).
2. **Add `issuer="https://<your-real-authorization-server>"`** to every `ClientCredentialsOAuthProvider` and `PrivateKeyJWTOAuthProvider` — without it the patched versions emit a deprecation warning and the binding is incomplete; 3.0 will refuse to start without it.
3. **Clear stored OAuth client registrations** so clients re-register against a validated issuer.
4. If the client ever connected to an MCP server you did not control: **rotate the client secret, rotate the private key behind the JWT assertion, and revoke tokens** at the real provider — the credential may already be in someone else's hands. See [playbooks/if-an-mcp-server-was-malicious.md](../playbooks/if-an-mcp-server-was-malicious.md) and [playbooks/rotating-cloud-credentials.md](../playbooks/rotating-cloud-credentials.md).

## Prevention

- Treat every remote MCP server as an attacker-controlled peer in the OAuth flow: the client must **pin the issuer** and bind credentials to it, never learn it from the resource it is about to authenticate to. This is the client-side twin of the [mcp-remote discovery SSRF batch](2026-09-mcp-remote-oauth-discovery-ssrf-cve-batch.md) and the [Obot dynamic-client-registration bug](2026-08-agent-framework-mcp-cve-batch.md) — the same protocol edge, three implementations.
- Keep `mcp` on latest, unpinned within a major: the fix shipped 21 days before the advisory did.
- Give agent integrations **per-server client credentials** so a compromised server can only redeem its own; see [prevention/mcp-hygiene.md](../prevention/mcp-hygiene.md) and [prevention/credential-hygiene.md](../prevention/credential-hygiene.md).

## Update — 2026-10-04: the TypeScript SDK had the same bug (CVE-2026-104850, GHSA-6qxp-vccf-f47h, 7.5; `@modelcontextprotocol/sdk` 1.12.0–1.30.1 → 1.31.0, `@modelcontextprotocol/client` 2.0.0–2.1.0 → 2.2.0) — and between 09-28 and 10-02 the two official SDKs published six more advisories: unbounded sessions (CVE-2026-59951) and request bodies, `$ref` fetches from tool output schemas, cross-origin redirects that re-send API keys and OAuth token requests, and a shared task store with no session binding

The 09-30 write-up above noted that the TypeScript SDK's advisory tab carried no equivalent entry. It was published the same day: **[GHSA-6qxp-vccf-f47h / CVE-2026-104850](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-6qxp-vccf-f47h)** (2026-09-30, High 7.5) — "credentials were not tied to the authorization server they belong to," so a malicious or compromised MCP server could name its own authorization server and receive refresh tokens, client secrets and signed assertions with no user interaction. Affected `@modelcontextprotocol/sdk` **1.12.0 – 1.30.1** (fixed **1.31.0**, npm 2026-09-28) and `@modelcontextprotocol/client` 2.0.0 – 2.1.0 (fixed 2.2.0). Same post-upgrade requirement as the Python fix: for bundled providers pass `expectedIssuer`, clear tokens saved without issuer metadata, rotate client secrets if a server you did not control was ever connected. Seven reporters credited. **Both official SDKs, both transports, one bug** — any MCP client, agent or gateway built on either should be on `mcp` ≥ 1.30.0 / 2.2.0 or `@modelcontextprotocol/sdk` ≥ 1.31.0 with an explicit issuer.

Six further advisories landed on the two tabs in the same five days, all vendor-published, none with press coverage; grouped by what an attacker gets:

- **A malicious server makes your client fetch things.** Python [GHSA-rwrf-2pqf-9j8j](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-rwrf-2pqf-9j8j) (09-28, 6.5): when validating tool results against an `outputSchema`, the client's JSON Schema resolver **fetched unresolved `$ref` URLs** — `http://`, `https://` and `file://` — from the client's network position, synchronously and without a timeout, so a server could also freeze the session. `mcp` 1.10.0–1.29.1 / 2.0.0–2.1.1 → 1.30.0 / 2.2.0. Partial mitigation: skip tools whose schema carries `$id` or an external `$ref`.
- **A redirect carries your credentials to another origin.** TypeScript [GHSA-6prh-2h8m-c8cw](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-6prh-2h8m-c8cw) (10-02, 6.5): HTTP client transports *and OAuth requests* followed redirects to any origin and re-sent custom headers (`X-API-Key`, `mcp-session-id`) and bodies; on 307/308 that included OAuth token requests carrying `refresh_token` and `client_secret`. `sdk` ≤ 1.31.0 → **1.32.0** (npm 2026-10-02), `client` 2.0.0–2.2.0 → 2.3.0; workaround `fetch(url, {...init, redirect: 'error'})`. Python [GHSA-5h93-6whr-6q8j](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-5h93-6whr-6q8j) (10-02, 5.9): same for the HTTP and SSE client transports — custom headers and bodies cross origins; a lone `Authorization` bearer is dropped by the HTTP library. → 1.30.0 / 2.2.0; workaround `follow_redirects=False`.
- **Anyone can take your server offline.** Python [GHSA-84m7-p3x7-pcfv / CVE-2026-59951](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-84m7-p3x7-pcfv) (09-30, 7.5): Streamable HTTP servers in the default stateful mode **never reclaimed sessions** and kept each initialize payload, so ordinary unauthenticated requests grow memory without bound until restart. `mcp` 1.8.0–1.29.1 / 2.0.0–2.1.1 → 1.30.0 / 2.2.0; `stateless_http=True` on ≥ 1.12.0 is the workaround, and the fix adds `session_idle_timeout` / `max_sessions`. Python [GHSA-fmmv-w9g8-j3gc](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-fmmv-w9g8-j3gc) (09-30, 7.5): HTTP server transports and OAuth endpoints **read request bodies with no size limit** (2–3× the request size in memory); before 1.29.1 and 2.0.0/2.0.1 → 1.29.1 / 2.1.0, default cap now 4 MiB (`max_request_body_size`).
- **Tenants see each other's work.** TypeScript [GHSA-22jm-h49p-29qw](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-22jm-h49p-29qw) (10-02, **8.6**): the experimental tasks feature never checked which session created a task, so "clients sharing one `InMemoryTaskStore` could list, read and cancel each other's tasks." `sdk` 1.24.0–1.31.0 → 1.32.0; the feature is deprecated — drop `taskStore` or give each session its own store.

Registry check 2026-10-04: npm `@modelcontextprotocol/sdk` 1.30.1 (09-23), **1.31.0 (09-28)**, **1.32.0 (10-02)**; PyPI `mcp` latest **2.3.0** (2.2.0 and 1.30.0 carry every Python fix above). The practical reading for anyone building on either SDK: the September–October batch is the SDKs' first full audit of the *client* side — what a hostile server can make the client fetch, send, or leak — after a year in which every MCP advisory was about servers. Upgrade both SDKs to current, pass the issuer, and refuse redirects.

## Update — 2026-10-06: the TypeScript SDK's server-side bearer authentication accepted tokens issued for a different service (GHSA-rvq5-wwqv-78pq, 6.8; `@modelcontextprotocol/sdk` 1.6.0 – 1.31.x → 1.32.0, `server` 2.0.0 – 2.2.x → 2.3.0, `express` → 2.0.2, `server-legacy` → 2.3.1) — the fix adds an `expectedResource` setting that upgrading alone does not turn on

Published on the TypeScript tab on **2026-10-05** (no CVE at fetch time; reporter gdegrange; CWE-863): `requireBearerAuth()` "lacked audience validation, allowing tokens intended for other services to be accepted." Where one authorization server issues tokens for several resource servers — the normal enterprise OAuth layout — a token minted for service A was accepted by an MCP server B built on the SDK, so a client authorized for A could call B's tools as the same principal. Scored 6.8 (`AC:H/PR:L`): the attacker needs a valid token for *some* service from the same issuer.

**Upgrading is not the whole fix.** The patched versions add `expectedResource`; to be protected you must (1) pass your server's audience to `requireBearerAuth()` and (2) return the token's audience from your `verifyAccessToken()` as `AuthInfo.resource`. On any version, validating the `aud` claim inside your own `verifyAccessToken()` closes it. This is the second "configuration required after upgrade" item in this file — the Python SDK's `issuer=` for two providers was the first — and it is the server-side counterpart of the client-side audit the 10-04 update described: the SDKs now check who a token was *for*, not only whether it was signed. Registry check 2026-10-06 was not repeated for this entry; the versions above are the vendor advisory's.

## Update — 2026-10-08: the Python SDK had the same server-side bearer-token audience bug (GHSA-w4fh-qvv9-3v23, 6.8; `mcp` 1.10.0 – 1.29.1 and 2.0.0 – 2.1.1 → 1.30.0 / 2.2.0), with a `validate_token_resource` setting that is off by default

Published on the Python tab on **2026-10-05** (no CVE at fetch time; reporter u-ktdi; CWE-863), the Python twin of the TypeScript bearer-token bug above: a server's `AuthSettings` + `token_verifier` "could not check whether a token was issued for the server's own audience," so a token minted for another service sharing the same authorization server was accepted. Scored **6.8** (`AV:N/AC:H/PR:L`). As with the TS SDK, **upgrading alone is not the fix**: the patched releases add `validate_token_resource` (default **off**), which must be set to `True` with the correct `resource_server_url`, and `verify_token()` must return the audience as `AccessToken.resource` — both changes, or the server rejects everything; validating `aud` inside your own `verify_token()` closes it on any version. The default becomes `True` in 3.0. The fixed versions (1.30.0 / 2.2.0) are the same floor as the OAuth-client issuer fix this file opened with, so a project already on the patched line only needs the configuration. Status unchanged (`patched`).

This is now the third "configuration required after upgrade" item here and completes the pair with the 10-06 TypeScript entry: both official SDKs shipped audience-validation as an opt-in, so a reader who upgraded to close the September OAuth-client bug is **not** protected against the token-audience bug until they turn the new setting on.

## Sources

- [modelcontextprotocol/python-sdk — GHSA-qx49-fqc8-xw99: OAuth client could send credentials to an authorization server chosen by the MCP server](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-qx49-fqc8-xw99) — vendor advisory, published 2026-09-28: High / CVSS 7.5, the two affected ranges, patched 1.30.0 / 2.2.0, the provider list, the `issuer=` requirement and 3.0 note, the clear-registrations and rotate guidance, eight credited reporters. Fetched 2026-09-30.
- [Cycode — MCP SDK OAuth Flaw Enabled Account Takeover](https://cycode.com/blog/mcp-python-sdk-oauth-account-takeover/) — researcher analysis, 2026-09-28: the 404-fallback and 403 step-up paths, "no issuer validation or credential binding on any path" for 1.x, 7.5 vs 6.5 rationale, the "reported through Anthropic's security process" note. Fetched 2026-09-30.
- [The Hacker News — Official MCP Python SDK Flaw Can Let Malicious Servers Steal OAuth Credentials](https://thehackernews.com/2026/09/official-mcp-python-sdk-flaw-can-let.html) — 2026-09-29: the 09-07 release-notes-as-behaviour-change detail, the version table, "no attacks exploiting this flaw reported to date." Fetched 2026-09-30.
- PyPI `mcp` release metadata (`pypi.org/pypi/mcp/json`, queried 2026-09-30): 1.29.1 uploaded 2026-08-24, 2.1.1 2026-08-25, **1.30.0 and 2.2.0 both 2026-09-07**; latest 2.2.0.
- [modelcontextprotocol/typescript-sdk — security advisories](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories) — checked 2026-09-30: three entries (Dec 2025 – Feb 2026), none about OAuth issuer validation.
- Related in this corpus: [mcp-remote OAuth-discovery SSRF CVE batch](2026-09-mcp-remote-oauth-discovery-ssrf-cve-batch.md), [Bifrost unauthenticated client registration](2026-09-bifrost-mcp-client-registration-unauth-rce.md), [systemic MCP stdio RCE](2026-05-mcp-stdio-systemic-rce.md).
- **2026-10-04 update sources** — TypeScript: [GHSA-6qxp-vccf-f47h / CVE-2026-104850](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-6qxp-vccf-f47h) (published 2026-09-30: High 7.5, the "not tied to the authorization server" wording, ranges and fixes, `expectedIssuer`, seven credits), [GHSA-22jm-h49p-29qw](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-22jm-h49p-29qw) (2026-10-02: 8.6, `InMemoryTaskStore`, 1.24.0–1.31.0 → 1.32.0, credit cipher-creator), [GHSA-6prh-2h8m-c8cw](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-6prh-2h8m-c8cw) (2026-10-02: 6.5, the header/body/307-308 detail, `redirect: 'error'`, credit nickelsec); Python: [GHSA-rwrf-2pqf-9j8j](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-rwrf-2pqf-9j8j) (2026-09-28: 6.5, `$ref` fetches incl. `file://`, credit Hem1700), [GHSA-84m7-p3x7-pcfv / CVE-2026-59951](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-84m7-p3x7-pcfv) (2026-09-30: 7.5, stateful-mode session growth, `stateless_http=True`, credits rharang / evilgensec / Madan301 / nickelsec), [GHSA-fmmv-w9g8-j3gc](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-fmmv-w9g8-j3gc) (2026-09-30: 7.5, no body-size limit, 4 MiB default, credits razashariff / shmulc8), [GHSA-5h93-6whr-6q8j](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-5h93-6whr-6q8j) (2026-10-02: 5.9, `follow_redirects=False`, credits dodge1218 / nickelsec); the two tabs [typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories?state=published) and [python-sdk](https://github.com/modelcontextprotocol/python-sdk/security/advisories?state=published) (walked 2026-10-04); npm `@modelcontextprotocol/sdk` `time` and `pip index versions mcp` (queried 2026-10-04). All fetched 2026-10-04.
- [GHSA-rvq5-wwqv-78pq — Server bearer authentication accepted tokens issued for another service](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-rvq5-wwqv-78pq) — fetched 2026-10-06: 6.8 `AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N`, the four packages and their ranges, the `expectedResource` / `AuthInfo.resource` remediation, CWE-863, reporter gdegrange, published 2026-10-05.
- [modelcontextprotocol/typescript-sdk — security advisories tab](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories) — fetched 2026-10-06: seven advisories, the 10-05 entry newest.
- **2026-10-08 update source** — [modelcontextprotocol/python-sdk — GHSA-w4fh-qvv9-3v23: Server bearer-token authentication accepts tokens issued for a different resource server](https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-w4fh-qvv9-3v23) (fetched 2026-10-08: Moderate 6.8 `AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N`, `mcp` 1.10.0–1.29.1 and 2.0.0–2.1.1 → 1.30.0 / 2.2.0, the `validate_token_resource` opt-in and `AccessToken.resource` requirement, CWE-863, reporter u-ktdi, published 2026-10-05; no CVE at fetch).
