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
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 by this bug. The dated updates below add three more settings the patched SDKs ship off: expectedResource (TypeScript server bearer auth, 10-06), validate_token_resource (Python server bearer auth, 10-08) and maxToolInputElements (TypeScript validation memory limit, 10-17), plus session, body-size and cross-client fixes that upgrading does deliver.
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?
# 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
- Upgrade to
mcp≥ 2.2.0 (or ≥ 1.30.0 on the 1.x line). - Add
issuer="https://<your-real-authorization-server>"to everyClientCredentialsOAuthProviderandPrivateKeyJWTOAuthProvider— without it the patched versions emit a deprecation warning and the binding is incomplete; 3.0 will refuse to start without it. - Clear stored OAuth client registrations so clients re-register against a validated issuer.
- 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 and 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 and the Obot dynamic-client-registration bug — the same protocol edge, three implementations.
- Keep
mcpon 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 and 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 (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 (09-28, 6.5): when validating tool results against an
outputSchema, the client's JSON Schema resolver fetched unresolved$refURLs —http://,https://andfile://— from the client's network position, synchronously and without a timeout, so a server could also freeze the session.mcp1.10.0–1.29.1 / 2.0.0–2.1.1 → 1.30.0 / 2.2.0. Partial mitigation: skip tools whose schema carries$idor an external$ref. - A redirect carries your credentials to another origin. TypeScript 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 carryingrefresh_tokenandclient_secret.sdk≤ 1.31.0 → 1.32.0 (npm 2026-10-02),client2.0.0–2.2.0 → 2.3.0; workaroundfetch(url, {...init, redirect: 'error'}). Python GHSA-5h93-6whr-6q8j (10-02, 5.9): same for the HTTP and SSE client transports — custom headers and bodies cross origins; a loneAuthorizationbearer is dropped by the HTTP library. → 1.30.0 / 2.2.0; workaroundfollow_redirects=False. - Anyone can take your server offline. Python GHSA-84m7-p3x7-pcfv / CVE-2026-59951 (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.
mcp1.8.0–1.29.1 / 2.0.0–2.1.1 → 1.30.0 / 2.2.0;stateless_http=Trueon ≥ 1.12.0 is the workaround, and the fix addssession_idle_timeout/max_sessions. Python 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 (10-02, 8.6): the experimental tasks feature never checked which session created a task, so "clients sharing one
InMemoryTaskStorecould list, read and cancel each other's tasks."sdk1.24.0–1.31.0 → 1.32.0; the feature is deprecated — droptaskStoreor 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.
Update — 2026-10-17: three more TypeScript SDK advisories published 2026-10-15 for fixes shipped 09-23 and 10-02 (cross-client message delivery 7.7, validation memory exhaustion 7.5, unbounded bodies 7.5); one fix is a default-off option, so this is the fourth setting an upgrade does not turn on
On 2026-10-15 the TypeScript SDK's tab gained three advisories, all published by maxisbey, all High, none with a CVE at fetch time:
- GHSA-jm63-cq47-j5jp, 7.7 (
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:H, CWE-362). An HTTP MCP server that shared one stateless transport, or oneMcpServerorServerinstance, across clients could deliver one client's responses and notifications to another, because "messages were matched to a connection by JSON-RPC id alone" and SDK clients number requests from 0. Affected@modelcontextprotocol/server2.0.0 through 2.2.0, patched 2.3.0 (npm 2026-10-02). The README examples for the Hono, Express, Fastify and Node packages showed the shared-instance pattern until this release. Not affected:createMcpHandler()with a per-call server factory, stdio servers,@modelcontextprotocol/sdk1.26.0 and later. The patched release rejects shared use, so an affected app fails requests after upgrading until it creates the transport or server per request or per session. Credits DavidCarliez and legitu1337. - GHSA-46jr-4wx8-v453, 7.5 (
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H, CWE-770). One HTTP request inside the default 4 MiB body limit could exhaust server memory during validation, because the number of array elements and object keys validated per request was unbounded;McpServertools with array or record inputs are the main surface, stdio servers are not affected. Affected@modelcontextprotocol/sdk1.3.0 to below 1.32.0 (npm 2026-10-02) andserver2.0.0 to below 2.3.0. The advisory's own words: upgrading alone does not fix it. The patched releases add amaxToolInputElementsoption that is off by default; set it just above what your tools need and lowermaxRequestBodySizeon the transport orcreateMcpHandler(). Credit root-Brainoverflow. - GHSA-f897-62hf-6gxq, 7.5 (same vector, CWE-770). HTTP server transports and handlers placed no limit on POST body size or JSON-RPC batch length; a body parser such as
express.json()does not help because it does not bound batch length. Affected@modelcontextprotocol/sdk1.10.0 to below 1.30.1 (npm 2026-09-23),serverandnode2.0.0 to below 2.1.0 (2026-09-23),hono2.0.0 to below 2.0.1. After patching, bodies over 4 MiB get 413 and batches over 100 messages get 400. Seven reporters credited.
Two things to carry. First, the dates: the fixes reached npm on 2026-09-23 and 2026-10-02 and the advisories followed on 2026-10-15, so a reader who upgraded on release day was protected before anyone said why, and a reader who pins is now three weeks behind an undisclosed fix; npm view @modelcontextprotocol/sdk time is the record of when each line shipped. Second, maxToolInputElements joins issuer=, expectedResource and validate_token_resource as a setting the patched SDK ships off; the TL;DR's "upgrading is not the whole fix" now has four items.
Also on 2026-10-14 the GitHub CNA published the record for the Python session-accumulation bug this file covered on 10-04: CVE-2026-59951 (CVSS 4.0 6.9, mcp 1.8.0 to below 1.30.0 and 2.0.0 to below 2.2.0, fixed 1.30.0 and 2.2.0), and the database listed it on 10-15. The vendor advisory scores it 7.5 on CVSS 3.1; the record's 6.9 is the same finding on the 4.0 scale, not a downgrade.
Update — 2026-10-28: the three Python SDK advisories of 09-28 to 10-05 now carry CVE ids (CVE-2026-116684, 116685, 116686), reviewed into the database on 2026-10-27; the TypeScript body-size advisory was reviewed the same day
The GitHub CNA published records on 2026-10-27 for three Python SDK advisories this file already tracks, and the database reviewed all three the same day: CVE-2026-116684 for the server bearer-token audience bug (GHSA-w4fh-qvv9-3v23, 6.8, AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N, affected 1.10.0 to 1.29.1 and 2.0.0 to 2.1.1, vendor-published 2026-10-05); CVE-2026-116685 for HTTP client transports following cross-origin redirects with custom headers and 307/308 bodies (GHSA-5h93-6whr-6q8j, 5.9, affected 1.8.0 to 1.29.1 and 2.0.0 to 2.1.1, vendor-published 2026-10-02); and CVE-2026-116686 for clients fetching server-chosen $ref URLs while validating tool results (GHSA-rwrf-2pqf-9j8j, 6.5, UI:R, affected 1.10.0 to 1.29.1 and 2.0.0 to 2.1.1, vendor-published 2026-09-28). All three are fixed in 1.30.0 and 2.2.0, both on PyPI since 2026-09-07, and the audience-bug fix still needs validate_token_resource=True as the 10-08 update says; the CNA descriptions match the vendor pages, and the records reference the same pull requests and release tags. The TypeScript SDK's body-size and batch-length advisory (GHSA-f897-62hf-6gxq, 7.5, the 10-17 update) was reviewed into the database on 10-27 with no CVE. Nothing here changes a version or a setting; a scanner keyed on CVE ids now sees the three Python findings it did not on 10-26.
Sources
- modelcontextprotocol/python-sdk — GHSA-qx49-fqc8-xw99: OAuth client could send credentials to an authorization server chosen by the MCP server — 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 — 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 — 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
mcprelease 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 — 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, Bifrost unauthenticated client registration, systemic MCP stdio RCE.
- 2026-10-04 update sources — TypeScript: GHSA-6qxp-vccf-f47h / CVE-2026-104850 (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 (2026-10-02: 8.6,InMemoryTaskStore, 1.24.0–1.31.0 → 1.32.0, credit cipher-creator), GHSA-6prh-2h8m-c8cw (2026-10-02: 6.5, the header/body/307-308 detail,redirect: 'error', credit nickelsec); Python: GHSA-rwrf-2pqf-9j8j (2026-09-28: 6.5,$reffetches incl.file://, credit Hem1700), GHSA-84m7-p3x7-pcfv / CVE-2026-59951 (2026-09-30: 7.5, stateful-mode session growth,stateless_http=True, credits rharang / evilgensec / Madan301 / nickelsec), GHSA-fmmv-w9g8-j3gc (2026-09-30: 7.5, no body-size limit, 4 MiB default, credits razashariff / shmulc8), GHSA-5h93-6whr-6q8j (2026-10-02: 5.9,follow_redirects=False, credits dodge1218 / nickelsec); the two tabs typescript-sdk and python-sdk (walked 2026-10-04); npm@modelcontextprotocol/sdktimeandpip 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 — 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, theexpectedResource/AuthInfo.resourceremediation, CWE-863, reporter gdegrange, published 2026-10-05. - modelcontextprotocol/typescript-sdk — security advisories tab — 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 (fetched 2026-10-08: Moderate 6.8
AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N,mcp1.10.0–1.29.1 and 2.0.0–2.1.1 → 1.30.0 / 2.2.0, thevalidate_token_resourceopt-in andAccessToken.resourcerequirement, CWE-863, reporter u-ktdi, published 2026-10-05; no CVE at fetch). - 2026-10-17 update sources — GHSA-jm63-cq47-j5jp, GHSA-46jr-4wx8-v453 and GHSA-f897-62hf-6gxq (all published 2026-10-15; fetched 2026-10-17: scores, vectors, ranges, patched versions, the
maxToolInputElementsdefault, the shared-instance rejection, credits); the TypeScript SDK advisories tab (ten entries on 2026-10-17); npmtimefor@modelcontextprotocol/sdk(1.30.1 → 2026-09-23, 1.32.0 → 2026-10-02, latest 1.32.1 → 2026-10-05) and@modelcontextprotocol/server(2.1.0 → 2026-09-23, 2.3.0 → 2026-10-02), queried 2026-10-17; CVE-2026-59951, CVE Services record (GitHub CNA, published 2026-10-14, CVSS 4.0 6.9) and the Python vendor advisory GHSA-84m7-p3x7-pcfv re-read 2026-10-17 (7.5 on 3.1, the CVE now shown). - 2026-10-28 update sources — CVE Services — CVE-2026-116684, CVE-2026-116685, CVE-2026-116686 (queried 2026-10-28: GitHub CNA, published 2026-10-27, affected ranges, CVSS 3.1 scores and vectors, references); GitHub Advisory Database — GHSA-w4fh-qvv9-3v23, GHSA-5h93-6whr-6q8j, GHSA-rwrf-2pqf-9j8j, GHSA-f897-62hf-6gxq (fetched 2026-10-28: vendor dates, database-published and reviewed 2026-10-27, credits); mcp listing, sorted by publication (fetched 2026-10-28: the four rows dated 10-27).