Official MCP Python SDK flaw let malicious servers steal OAuth credentials
A missing check in how MCP clients find their login service shows why AI integrations must treat every server answer as untrusted input.
The official Python software development kit for the Model Context Protocol (MCP) contained a flaw that let a malicious or compromised MCP server trick a connecting client into handing over its OAuth credentials, according to a report by The Hacker News on 29 September 2026. The issue is tracked as GitHub security advisory GHSA-qx49-fqc8-xw99. No CVE had been assigned when the report was published.
The weakness sits in how an MCP client learns where to log in. When a client that talks to a server over HTTP needs to authenticate, it asks that server where the authorisation service lives. Affected versions of the SDK did not check that the answer pointed at the genuine login service. A hostile server could name an endpoint controlled by the attacker, and the client would send it three things: the OAuth client secret, the authorisation code and the PKCE proof key. With those in hand, the attacker could ask the real login service for access tokens carrying whatever permissions the application had been granted.
Affected releases run from 1.9.1 to 1.29.1 on the 1.x line and from 2.0.0 to 2.1.1 on the 2.x line. The fixes ship in 1.30.0 and 2.2.0. The advisory scores the flaw 7.5 for machine-to-machine providers and 6.5 for the interactive provider. Security firm Cycode published its research on 28 September, the same day as the advisory, and demonstrated the complete chain. Eight researchers are credited in total.
MCP servers built with the SDK, local clients that use stdio, and clients with manually attached tokens are not affected. The Hacker News reported no known attacks using the flaw as of 29 September.
Why it matters
MCP has become the common plug that lets AI assistants reach tools and data, so its client libraries now hold real credentials for real services. This flaw shows that an AI integration is only as trustworthy as the least trustworthy server it connects to. As agents are wired into more third-party servers, the discovery steps that used to be plumbing become part of the attack surface. Expect SDK maintainers to keep tightening how clients decide which servers and login services they believe.
MCP has become the common plug that lets AI assistants reach tools and data, so its client libraries now hold real credentials for real services.
Client needs to authenticate
An MCP client connected over HTTP asks the server where its authorisation service lives.
Server answer is not verified
Affected SDK versions did not check that the answer pointed at the genuine login service.
Malicious server names its own endpoint
A compromised or hostile server points the client at an attacker-controlled address.
Client sends its secrets
The client transmits its OAuth client secret, authorisation code and PKCE proof key to that endpoint.
Attacker mints real tokens
With those values the attacker requests access tokens from the real login service, with the app's permissions.
What you can learn from this
- Discovery answers are untrusted input. Many protocols let a client ask a server "where do I authenticate?" and then follow the answer automatically. That convenience saves configuration, but it means the server gets to steer the client's most sensitive traffic. In this case the answer was followed without checking it against anything the client already knew, which is the same shape of mistake as following an unchecked redirect on a web page. The lesson is that any value a remote party supplies, including metadata and URLs, should be treated as input to validate, not as configuration to obey.
- How the OAuth authorisation code flow and PKCE work. In the authorisation code flow, the user or application proves who it is to a login service and receives a short-lived code, which is then exchanged for an access token. PKCE (Proof Key for Code Exchange) adds a secret the client invents at the start and must present again at the exchange, so someone who intercepts only the code cannot use it. PKCE protects against a thief in the middle of one step. It cannot help when the client itself sends the code and the proof key to the attacker, because the attacker then holds both halves of the puzzle.
- Issuer binding is the control that was missing. An OAuth issuer is the identity of the login service, usually expressed as a URL. Binding a client to an expected issuer means the client refuses to send secrets to any login endpoint that does not match what it was configured to trust. The fixed SDK versions add this check, and for the client credentials and private key JWT providers they accept an issuer= parameter so developers can state the legitimate login service explicitly. The control exists because discovery can be spoofed, and a pinned expectation is the only thing that makes spoofing visible.
- Long-lived secrets widen the blast radius. A client secret is a durable credential, while an access token usually expires within minutes or hours. When a durable secret leaks, the attacker can keep minting new tokens until someone rotates it. That is why the advisory tells affected users to rotate client secrets and revoke tokens, not only to upgrade. Upgrading closes the door for the future, but it does nothing about keys that may already have walked out.
- Scope limits damage even when authentication fails. Cycode's point that the stolen token carries whatever permissions the application was granted is a reminder of least privilege. An integration that only needs to read a calendar should never hold a token that can also send email. Narrow scopes do not stop a theft, but they decide how much a thief can do with what they took.
We teach this in the AI Tutor
How to use this in practice
- Check which SDK version you run. In any Python project that builds an MCP client, run pip show mcp or pip list | grep -i mcp inside its virtual environment. If the version falls in the affected ranges, upgrade with pip install --upgrade "mcp>=1.30.0" on the 1.x line, or move to 2.2.0 or later on the 2.x line, then pin the result in your requirements file. Done looks like a lock file or requirements file that names a fixed version, and a test run that still connects to your servers.
- Find the OAuth providers in your code. From the project root, search for the provider classes named in the advisory: grep -rnE "OAuthClientProvider|ClientCredentialsOAuthProvider|PrivateKeyJWTOAuthProvider|RFC7523OAuthClientProvider" . For every client credentials or private key JWT provider you find, pass the issuer= value for the login service you actually use. If you find the deprecated RFC7523 provider, write a short note to replace it. Done is every match either updated with an explicit issuer or listed with a reason.
- List the MCP servers your tools trust. Open the configuration file for each AI assistant or agent you use and write down every remote MCP server it connects to over HTTP. Mark each one as run by you, run by a vendor you know, or unknown. Remove any server marked unknown that you no longer use. Done is a short table you could hand to a colleague that explains why each remaining server is there.
- Practise the rotation step on a test app. If you have a personal OAuth app registered with a provider such as GitHub or Google, rotate its client secret today and update the app to use the new one. Time how long it takes and note every place the old secret was stored. Done is a working app on the new secret and a written list of places you would need to touch in a real incident.
Sources
Our reporting is an original summary. Full coverage is at the links above.
How do you score in Cybersecurity?
A few minutes of questions, then a report on where you stand and what to learn next.
