The MCP Server Chose Where Your OAuth Secret Went
OAuth is supposed to make delegated access safer by keeping a client’s credentials inside a carefully defined flow.
An MCP client can quietly undo that boundary if the server it connects to gets to choose where those credentials are sent.
That is the failure described in GHSA-qx49-fqc8-xw99, a high-severity advisory for the official MCP Python SDK. The advisory was published on September 28, 2026. It affects SDK versions before 1.30.0 in the 1.x line and before 2.2.0 in the 2.x line.
The bug is not that MCP servers can call tools. The deeper problem is that an OAuth-enabled client trusted server-provided discovery data at the moment it should have been enforcing credential provenance.
The server controlled the destination
The affected SDK code lives in mcp.client.auth, the client-side OAuth support used when an application connects to an MCP server over HTTP.
According to the advisory, affected versions did not consistently validate the authorization server’s issuer across discovery paths. They also did not bind stored or pre-provisioned client credentials to the authorization server they belonged to.
That combination gave a malicious or compromised MCP server a dangerous choice. It could advertise an authorization server of its own, or omit protected resource metadata and present the legitimate server as the issuer through a fallback path. The client could then send sensitive OAuth material to a token endpoint selected by the MCP server.
The exposed material depended on the provider, but the advisory names the client secret, authorization code and PKCE code_verifier. With PrivateKeyJWTOAuthProvider, a signed client assertion could also be exposed.
This is a destination integrity failure. The credential may be valid, the identity provider may be real, and the user may have followed the expected login flow. The untrusted server still influenced the endpoint that received the result.
Why PKCE did not save the client
PKCE is useful because it binds an authorization code to a verifier held by the client. It is not a general guarantee that a client will send the verifier only to the correct authorization server.
If a compromised MCP server can redirect the token exchange to an endpoint it controls, the code_verifier becomes part of the stolen transaction rather than a protection against the server that initiated the flow.
The same distinction applies to client secrets and signing keys. OAuth protects a protocol exchange. It does not repair a client that accepts an untrusted peer’s account of who should receive the exchange.
The advisory rates the issue High with a CVSS base score of 7.5. It says the affected condition requires an HTTP MCP client using one of the named OAuth providers while holding credentials for a legitimate authorization server and connecting to a server the application does not fully trust.
Interactive login changes the user interaction requirement, but it does not turn an untrusted server into an identity boundary. The advisory scores that interactive case separately at 6.5 because a person must start the sign-in.
The version boundary is only half the fix
The official fix is to upgrade to mcp 1.30.0 or later on the 1.x line, or 2.2.0 or later on the 2.x line.
That upgrade is necessary, but some deployments have a second migration step. For ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider, the application must also pass the expected issuer, such as:
ClientCredentialsOAuthProvider(
client_id="...",
client_secret="...",
issuer="https://auth.example.com",
)
The advisory says that without this value, those providers still follow whichever authorization server the MCP server advertises. In the fixed releases, omission produces a deprecation warning and the option becomes required in version 3.0.
Stored OAuth registrations matter too. A record created before the fix may have no issuer binding. The maintainers recommend clearing that stored client information so the client registers again, or adding the issuer to a pre-registered record.
If an affected client may have connected to an untrusted MCP server, the response is not just a package upgrade. Rotate the client secret and revoke tokens at the authorization server.
MCP servers are peers, not configuration files
The common mental model for MCP is still too passive. A server publishes tools, the client invokes them, and authentication is treated as setup plumbing around the interesting agent work.
This advisory shows why that model fails. An MCP server can provide discovery input that changes the security meaning of the client’s next request. It is not merely a catalog of functions. It is a network peer participating in the authentication control loop.
That means the client must treat server metadata as untrusted input, even when the server is the thing that supplies the metadata. The expected issuer, credential binding and allowed destinations have to come from a higher trust layer than the connection being authenticated.
The distinction is operationally important:
| Control | Weak assumption | Safer boundary |
|---|---|---|
| Issuer | The server tells us which issuer to use | The client configuration or trusted registration supplies the expected issuer |
| Credentials | A stored registration works for any discovered issuer | Credentials are bound to one issuer and rejected elsewhere |
| Discovery | Metadata is configuration | Metadata is an untrusted claim that must be checked |
| Remediation | Upgrade the package | Upgrade, configure the issuer, clear old registrations, rotate exposed secrets |
What operators should change
First, inventory MCP clients, not just MCP servers. Find every application using HTTP transport and OAuth provider support, then identify the SDK version and provider type.
Second, make the issuer explicit. Do not let a remote MCP server be the only source of truth for where client credentials may go. Treat an omitted issuer as a deployment error, not a harmless default.
Third, bind stored registrations to their issuer and invalidate old unbound records after upgrading. A patched parser cannot retroactively add provenance to credentials that were stored without it.
Fourth, put an egress policy around the client. OAuth token exchanges should have a narrow destination set that is independently auditable. Network policy is not a replacement for issuer validation, but it can stop a second failure from becoming credential exfiltration.
Finally, log the authentication path. The useful audit record includes the MCP server identity, discovered issuer, expected issuer, token endpoint, provider type and approval event. If an incident review can only show that an agent connected to a server, the record is not detailed enough.
The trust boundary is outside the model
The MCP Python SDK advisory is a reminder that agent security failures do not need a malicious prompt or a dramatic tool call. A server can influence a credential flow through ordinary protocol metadata.
The fix is not a better instruction to the model. It is a client that refuses to let an untrusted peer select the destination for a secret.
In an agent system, authentication discovery is part of the control plane. If the peer can rewrite that control plane, the secret was never safely delegated.