Your MCP Server Just Decided Where You Log In: The OAuth Flaw to Fix This Week
The official MCP Python SDK sent client secrets and PKCE keys to whatever login server a remote MCP server pointed at. Fixes shipped in 1.30.0 and 2.2.0, and upgrading alone is not enough.

If you build AI agents in Python, stop and check your dependency pins. On September 29, the maintainers of the Model Context Protocol (MCP) Python SDK published a security advisory for a flaw that let a malicious MCP server redirect an application’s OAuth credentials to an attacker. The affected versions shipped the client secret, the authorization code, and the PKCE proof key straight to a token endpoint the attacker controlled. Fixes landed in 1.30.0 on the 1.x line and 2.2.0 on the 2.x line.
This one matters for a specific reason: the attacker doesn’t need to exploit your code. Your code calls out to them. Every agent that connects to a third-party MCP server over HTTP and holds credentials for a real login service was in scope.
What actually happened
When an MCP client needs to log in, it asks the server it’s connecting to where the authorization server lives. On affected versions, the SDK didn’t always verify that answer. A malicious server could point the client at a login service of the attacker’s choosing, and the client would then send its secret, its authorization code, and its PKCE proof key to the attacker instead of the real provider.
A few details make this worse than a routine credential bug:
- The PKCE proof key was included. That’s the one-time value designed to prevent a stolen authorization code from being reused. Handing it over defeats the protection it exists to provide.
- Cycode, the firm that reported it, demonstrated the full exchange. With the stolen credentials, an attacker can request a valid access token from the real login service, and that token carries whatever permissions the app was granted.
- The client secret is long-lived. It keeps working until you change it. This isn’t a token you wait out.
- Scored 7.5 (high) for the machine-to-machine providers. Those flows need no person in the loop at all. The interactive provider scores 6.5 because a human approves the sign-in, and here’s the part that should bother you: the page they approve is the genuine login page. Nothing looks wrong.
Affected versions: 1.9.1 through 1.29.1 on the 1.x line and 2.0.0 through 2.1.1 on the 2.x line. An application is affected if it uses the SDK as an MCP client over HTTP with one of the OAuth providers (OAuthClientProvider, ClientCredentialsOAuthProvider, PrivateKeyJWTOAuthProvider, or the deprecated RFC7523OAuthClientProvider) and can connect to a server it doesn’t fully control. MCP servers built with the SDK, local stdio clients, and clients that attach their own tokens are not affected. No CVE had been assigned as of September 29, so don’t wait for one to show up in your scanner feed.
The trust inversion nobody budgeted for
Here’s the deeper issue. Your OAuth model assumes the client decides which identity provider to trust, and the provider verifies the client. This flaw inverts that: the server you’re connecting to got a vote on where you log in. Your MCP server became a pointer that your client followed without checking.
That’s a new class of trust boundary for most security programs. You’ve spent years locking down which service accounts can touch which systems. Now you have agents holding long-lived OAuth credentials that phone home to whatever endpoint a remote party names. The blast radius of one malicious or compromised MCP server is every credential its clients hold, not just the data in one session.
And the detection story is bad. A stolen client secret used against the real login service produces legitimate-looking API traffic from a valid credential. No impossible travel, no brute force, no anomaly. If your SIEM rules key on failed logins and suspicious geography, this class of theft walks right past them.
What to do this week
The fix has two parts, and the second one is where most teams will slip:
1. Upgrade, then verify the pin
Move to 1.30.0 on 1.x or 2.2.0 on 2.x. The fixed SDK decides which authorization server it expects before fetching any details and refuses any answer that names a different one. Then confirm the upgrade actually landed in every deployed environment, including containers baked before the fix.
2. Pass issuer= for the server-to-server providers
Upgrading changes nothing for ClientCredentialsOAuthProvider or PrivateKeyJWTOAuthProvider until you also pass issuer= to name the login service those credentials belong to. Without it, the client still follows whatever the MCP server points at. On 1.30.0 the warning about this is a standard Python deprecation warning, which Python hides by default, so it’s easy to miss. If you’re still on the deprecated RFC7523OAuthClientProvider, it has no issuer= option at all. Migrate to one of the other providers.
3. Clear stored OAuth client registrations once
Old registrations aren’t tied to a login service and stay that way even after you upgrade. Clear them so the client re-registers under the new validation rules.
4. Rotate anything that touched an untrusted server
If a client may have connected to an untrusted MCP server while vulnerable, rotate its client secret and revoke its tokens at the login provider. Assume the credentials left the building. Patching the SDK closes the door going forward; rotation evicts whoever already walked through it.
5. Put MCP servers in your third-party risk process
If your teams connect agents to external MCP servers, treat each one like a SaaS vendor: who runs it, what credentials do our clients hold when talking to it, and what’s the blast radius if it turns hostile. That inventory is the same lesson as every supply-chain incident before this one. You can’t rotate credentials for servers you never wrote down.
The takeaway
AI agents are becoming credential-holding infrastructure, and this advisory is the first at-scale proof. The MCP protocol is doing exactly what it was designed to do: let your agents connect to outside tools and data. But every connection that carries OAuth credentials is now a trust decision, and your SDK finally enforcing it client-side is the fix, not the cure.
Check your pins today. Upgrade, pass issuer=, clear the old registrations, rotate anything suspicious. It’s an hour of work that closes a credential-theft path your scanners won’t flag, because there’s no CVE to match on yet.
Need help auditing which of your agents hold OAuth credentials and where they connect? That’s the work I do every day. Reach out via the contact page.