// hackerlogs
login+ register
Agentic AISupply ChainThreat BriefHigh

Ship Day Was 7 September. The Advisory Was 21 Days Later.

The official MCP Python SDK shipped issuer checks on 7 September as a behaviour change. The high advisory arrived 28 September. Upgrade is not the whole fix.

The short answer

The primary is GitHub advisory GHSA-qx49-fqc8-xw99 from 28 September 2026. An affected MCP Python client can send its client secret, authorization code, and PKCE verifier to a token endpoint the server chooses. Versions 1.30.0 and 2.2.0 shipped the issuer check on 7 September as a behaviour change. Pass issuer on machine providers, then rotate any secret that already left.

Key takeaways

  • The primary is GHSA-qx49-fqc8-xw99 on the mcp package. High is 7.5 for the two unattended providers. The interactive provider, which needs a person to start sign-in, is scored 6.5. The advisory lists no CVE.
  • The package clock is 7 September. mcp 1.30.0 published at 14:03 UTC and 2.2.0 at 15:53 UTC. Both release notes file the issuer check under Behaviour changes, not as a security fix.
  • The disclosure clock is 28 September, when the advisory and Cycode's write-up landed. The Hacker News ran it on 29 September. Twenty-one days of installs sat between the tag and the label.
  • Upgrade is not the whole fix. ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider still follow the server until you pass issuer. On 1.30.0 that omission is a DeprecationWarning, which Python hides by default.
  • The impact clock is the secret. The advisory does not report exploitation in the wild. A client secret that already left keeps working until you rotate it and revoke tokens at the login service.

On 28 September the maintainers of the official MCP Python SDK published GHSA-qx49-fqc8-xw99. A malicious or compromised MCP server can choose where an affected HTTP client's OAuth credentials are sent. The client secret, the authorization code, and the PKCE verifier meant for a real login service can arrive at a token endpoint that server named. The fix tags, 1.30.0 and 2.2.0, had already been on PyPI for 21 days, filed as behaviour changes.

Hacker News on 29 September led with GPT-6.1 Sol, a privacy paper on conversational agents, and OpenAI's Dots launch. US X trends were baseball, a Senate fight, and fashion. Dots and GPT-6.1 Sol trended as product launches. The operator record is the advisory.

High Official client SDK, unattended OAuth, secret leaves toward a server-chosen issuer. High because the advisory scores the unattended providers at 7.5, not because an in-the-wild attack has been named.

Three panels for the MCP Python SDK OAuth advisory: a server-chosen login issuer, a 21-day gap between the 7 September tags and the 28 September advisory, and the issuer argument that upgrade alone does not set.

Who the advisory says is affected.#

The package is mcp on PyPI. Affected ranges on the advisory are 1.9.1 up to but not including 1.30.0, and 2.0.0a1 up to but not including 2.2.0. You are in scope only if both of these hold: the app uses the SDK as an MCP client over HTTP with OAuthClientProvider, ClientCredentialsOAuthProvider, PrivateKeyJWTOAuthProvider, or the deprecated 1.x RFC7523OAuthClientProvider, and it can connect to an MCP server you do not fully trust while holding credentials for a real login service.

The 1.x line, 1.9.1 through 1.29.1, had no issuer check and no credential binding on any path. On 2.0.0 through 2.1.1 the checks are missing when the server publishes no protected resource metadata, the legacy fallback, or when it answers 403 insufficient_scope. On both lines, the two machine-to-machine providers had no way to name the authorization server their credentials belong to. That last gap survives a version bump until you pass issuer.

The advisory's own account of the miss is short. The server can name its own authorization server in protected resource metadata, or publish none and serve metadata that presents the user's real authorization server as the issuer. The client then sends the secret, the code, and the verifier, or a signed client assertion for PrivateKeyJWTOAuthProvider, to a token endpoint of the server's choosing. Cycode, one of eight reporters on the advisory, says it completed that exchange in a test and that the resulting token carries the permissions the app was granted. This brief does not repeat the steps.

The package clock and the impact clock.#

v1.30.0 published at 14:03 UTC on 7 September. v2.2.0 published at 15:53 UTC the same day. Both notes lead with "Behaviour changes." The 1.x note says the OAuth client now rejects authorization-server metadata whose issuer does not match the server it was fetched for, and remembers registrations per issuer. The 2.x note is narrower, and you should read it that way: the protected-resource-metadata path has checked issuer since 2.0, and 2.2.0 extends the check to the legacy path. Both notes add issuer= on the two machine providers and say the old construction keeps working in this major version.

That is a maintenance heading. It is not a security advisory. The advisory itself is 28 September, the same day Cycode published. The Hacker News ran the story on 29 September and said no CVE had been assigned. The advisory page still lists no CVE ID. Twenty-one days is the gap between a behaviour change a careful reader could have applied and a label that tells everyone else to look.

After the upgrade, stored registrations are a second clock. A registration stored without an issuer stays unbound. That includes everything 1.x stored before 1.30.0. Clear stored OAuth client information once so the client registers again. If you placed a pre-registered client in storage yourself, set issuer on that record instead of wiping it and hoping.

  1. 14:03 UTC: mcp 1.30.0. 15:53 UTC: mcp 2.2.0. Issuer checks are under Behaviour changes. issuer= exists and is not yet required.
  2. GHSA-qx49-fqc8-xw99 published. Cycode publishes the same day. High 7.5 for unattended providers, 6.5 when a person must start sign-in. No CVE field.
  3. The Hacker News reports the advisory and says neither it nor Cycode reports an attack using the flaw.

What this is not.#

It is not a bug in MCP servers you built with this SDK. It is not a stdio client. It is not a client that attaches its own bearer token and never runs these OAuth providers.

It is not CVE-2026-52869. That June advisory is session mixing on the server transports, fixed in 1.27.2. Patching it does not bind an OAuth issuer.

It is not a confirmed intrusion. The advisory does not describe an attack in the wild. The Hacker News says Cycode does not either. Absence of a named victim is not a reason to skip rotation if an untrusted server was already reachable.

It is also not a 2.x story in which nothing changed until 7 September. The 2.2.0 notes say the protected-resource-metadata path already checked issuer. The hole that remained was the legacy fallback, the 403 step-up, and machine credentials with no issuer of their own.

What to do.#

  1. Find HTTP MCP clients on mcp that use one of the four OAuth providers and can reach a server you do not fully control. Stdio and "we attach the token ourselves" stay off this list.
  2. Upgrade the 1.x line to 1.30.0 or later, and the 2.x line to 2.2.0 or later. Read the behaviour-change notes. The redirect and idle-session defaults shipped in the same tags.
  3. Pass issuer on ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider, set to the login service those credentials belong to. Treat a missing argument as a review failure, including where Python swallows DeprecationWarning. Retire RFC7523OAuthClientProvider. It has no issuer argument.
  4. Clear stored OAuth client registrations once, or set issuer on a record you inserted yourself. A pre-1.30.0 registration stays unbound if you leave it.
  5. If that client may already have connected to an untrusted MCP server, rotate the client secret and revoke its tokens at the authorization server. Do this even when the new build is installed.
  6. On versions before the fix, the advisory's only workaround is to connect OAuth clients only to MCP servers you trust. Do not treat that sentence as a substitute for the upgrade.

The package clock stopped on 7 September for anyone who read a behaviour change as a security fix and also passed issuer. Everyone else got the label on 28 September. The secret, if it left, is still on the impact clock.

Frequently asked

Does upgrading to 1.30.0 or 2.2.0 close this?

Not by itself if you use ClientCredentialsOAuthProvider or PrivateKeyJWTOAuthProvider. The advisory says upgrading changes nothing until you also pass issuer and name the login service those credentials belong to. Without it, they still follow whichever authorization server the MCP server advertises. Omitting it warns, and version 3.0 will require it. On 1.30.0 the warning is a DeprecationWarning, which Python hides by default. The deprecated 1.x RFC7523OAuthClientProvider has no issuer option. Move off it.

Are MCP servers I built with this SDK affected?

No. The advisory's affected set is the SDK used as an MCP client over HTTP, with one of four OAuth handlers, while the client can reach a server you do not fully trust and still holds credentials for a real login service. Servers built with the SDK are out. Local stdio clients are out. Clients that attach their own tokens or headers are out.

Is this the June session-mixing bug, CVE-2026-52869?

No. That advisory, GHSA-jpw9-pfvf-9f58, is a server transport bug fixed in 1.27.2. This one is an OAuth client bug, GHSA-qx49-fqc8-xw99, with no CVE on the advisory page. Do not close the September ticket because the June CVE is already patched.

If we only connect to MCP servers we trust, can we wait?

That is the only workaround the advisory gives for versions before the fix, and it is a policy, not a control. A directory entry, a compromised server you used to trust, or a client that can be pointed at a new URL breaks it. On a fixed version you still clear stored registrations that have no issuer, and you still rotate if an untrusted server may already have been in the path.

Sources

  1. mcp v1.30.0 modelcontextprotocol/python-sdk · 2026-09-07
  2. mcp v2.2.0 modelcontextprotocol/python-sdk · 2026-09-07

Related