FR
live

The official MCP Python SDK lets a malicious server steal OAuth credentials

A flaw in the official Python SDK of the MCP protocol lets a malicious server redirect the client secret, the authorization code, and the PKCE proof key to a token endpoint it controls. Upgrade to 1.30.0 or 2.2.0, set the issuer parameter, and rotate your OAuth secrets.

A wall of identical metal mailboxes in a dim lobby, one door slightly ajar with a blank sealed envelope half-inserted into the wrong slot, a thin amber indicator light glowing.

September 28, 2026. The maintainers of the official Python SDK for the MCP protocol publish a security advisory: a malicious server can make a client hand over its OAuth credentials. September 29, 2026. The Hacker News relays the alert, and Cycode, the firm that found the flaw, documents the full exchange in a test. September 7, 2026. The fixes — versions 1.30.0 and 2.2.0 — had already shipped, framed as “behavior changes” rather than a security fix. Why it matters: MCP is becoming the standard wiring that connects AI agents to tools, and that wiring trusts the server on the single most sensitive question of all — where to send your secrets.

A network protocol that trusts the wrong address

MCP, the Model Context Protocol, is an open standard that connects AI applications to outside tools and data — an HTTP of sorts, specialized for agent capabilities. Its official Python SDK is used to build both MCP servers and clients. When a client needs to authenticate to a service, it asks the server it is connecting to where the login service — the authorization server — lives.

That is where the design cracks. On the vulnerable versions, the SDK did not always check that answer. A malicious server could point the client at a login service of its own choosing, either by naming the attacker’s own server or by serving login details that name the user’s real service while sending the credentials elsewhere. The client simply complied.

The defect is therefore a flaw of protocol trust, not a buffer overflow: endpoint discovery, which should be a derived and verified value, was treated as information supplied by a potentially hostile party. In network terms, it is the equivalent of handing a key to an address announced by the very person who just asked for it, without checking that it is actually your door.

The scenario: the client hands its secrets to the wrong terminal

When an MCP client authenticates, it sends three things to the login service: the client secret, the authorization code, and the PKCE proof key. The PKCE key is a one-time value designed to stop a stolen authorization code from being replayed — handing it over therefore defeats that protection too.

The theft unfolds like this: the client asks the server for the address of its login service, the server answers with its own, and the client sends all three secrets to the wrong recipient. With those, the attacker requests a valid access token from the real service. Cycode demonstrated the full exchange in a test: the resulting token carries exactly the permissions the application was granted. Because the client secret is long-lived, it keeps working until it is changed.

Severity varies by authentication provider. The CVSS score is 7.5 for the two machine-to-machine providers, which authenticate with no human in the loop, and 6.5 for the interactive provider, where a person must start the sign-in — but the page they approve is the genuine login page, so nothing looks wrong. No CVE had been assigned as of September 29.

Who is affected, and who is not

An application is vulnerable if it uses the SDK as an MCP client over HTTP with one of these OAuth providers — OAuthClientProvider, ClientCredentialsOAuthProvider, PrivateKeyJWTOAuthProvider, or the deprecated 1.x RFC7523OAuthClientProvider — and it can connect to a server it does not fully control while holding credentials for a real login service.

By contrast, three cases are not affected: MCP servers built with the SDK, local stdio clients, and clients that attach their own tokens.

LineAffected versionsFixed in
1.x1.9.1 through 1.29.11.30.0
2.x2.0.0 through 2.1.12.2.0

In the fixed versions, the client works out which login service it expects first, before fetching any details, and refuses any answer that names a different one. The check becomes a comparison against a known value instead of an acceptance of an unverified source.

What to do beyond upgrading

Upgrading is not always the whole fix. If you use ClientCredentialsOAuthProvider or PrivateKeyJWTOAuthProvider, the advisory says upgrading changes nothing until you also pass issuer= to name the login service those credentials belong to. Without it, the client still follows whichever server MCP points it at.

On version 1.30.0, the corresponding warning is a plain DeprecationWarning, which Python hides by default — so it is easy to miss. And the deprecated RFC7523OAuthClientProvider has no issuer= option at all; you must migrate to one of the other two providers.

Three steps complete the fix. Clear any stored OAuth client registrations once, because older ones are not tied to a login service and stay that way. If a client may already have connected to an untrusted server, rotate its client secret and revoke its tokens at the login service. On older versions, there is no workaround: the only defense is to connect only to MCP servers you trust.

What this means for the agent ecosystem

The flaw lands just as MCP establishes itself as the de facto infrastructure for AI agents — from coding assistants to platforms that expose APIs to models. A protocol that carries credentials must apply OAuth’s lessons from the start: the issuer is a value the client must know and impose, not a discovery it accepts from the remote party.

That lesson bites hardest in the supply chain. The whole point of MCP is to let a client reach servers it has never met — a directory listing, a vendor tool, a community connector — and the discovery step is exactly where an attacker sits. A client that holds a real service’s credentials and accepts a server’s word on where to log in is one careless pip install away from handing that credential over, even before the model does anything at all.

The absence of a CVE at publication is itself a signal. The fixes shipped on September 7 under the label “behavior changes,” and the advisory followed only on September 28 — three weeks during which deployments that read only the release notes had no reason to treat the upgrade as urgent. The gap between a technical fix and its security communication is, for a protocol in rapid adoption, a blind spot worth watching.

Verdict

If you build an MCP client over HTTP with the official Python SDK, move to 1.30.0 or 2.2.0 immediately — that is the non-negotiable fix, and it is backward-compatible. If you use ClientCredentialsOAuthProvider or PrivateKeyJWTOAuthProvider, upgrading alone is not enough: pass issuer= explicitly, then purge your stored client registrations. And if one of your clients has already talked to an untrusted MCP server, treat the secret as compromised: rotate it, revoke the tokens, and do not reconnect until issuer verification is in place. While MCP stays this young, the rule of thumb is simple — connect a credential-bearing client only to servers whose issuer you already know.

References

The cyber brief, every Tuesday

The flaws that matter and the patches to apply, in a ten-minute read.

No spam. One-click unsubscribe.
read next

On the same topic

← Back to the feed

Type at least two characters.

↑ ↓ navigate ↵ open esc dismiss