A vulnerability in the official MCP Python SDK exposed a simple but important security rule. Protecting a credential is not enough. A client must also know which authorization server is allowed to receive it.
A credential can be completely valid and still be sent to the wrong place.
The secret does not have to be guessed. Encryption does not have to be broken. The OAuth flow can even look normal.
The failure can happen because the wrong system was trusted to answer one question:
Where should this credential be sent?
A security vulnerability disclosed on September 28, 2026 in the official Model Context Protocol (MCP) Python SDK exposed exactly this problem. In affected OAuth client configurations, an MCP server could influence which authorization server received the client’s credentials. A malicious or compromised MCP server could therefore redirect sensitive OAuth material to an endpoint it controlled.
The immediate fix matters.
The more durable lesson is:
The system asking you to authenticate should not automatically be trusted to decide where your credentials are delivered.
Where the extra trust decision appears
Consider a remote MCP client connecting to a protected MCP server.
The MCP server can publish metadata telling the client which authorization servers are associated with it.
That flexibility is useful. Different MCP servers can use different identity systems.
But discovery and trust are not the same thing.
A client holding credentials for one authorization server must not automatically send them somewhere else simply because the MCP server points there.
What failed in the Python SDK
The vulnerability was in the SDK’s OAuth client support.
According to the GitHub advisory, two protections were incomplete.
- Authorization-server metadata did not have its
issuervalidated on every discovery path. - Stored or pre-provisioned client credentials were not consistently bound to the authorization server they belonged to.
A malicious or compromised MCP server could therefore direct the client to an attacker-controlled token endpoint.
Depending on the OAuth flow, the attacker might receive a client_secret, an authorization code, a PKCE code_verifier, or a signed client assertion.
The attacker does not defeat the credential.
They change its destination.
This is why the issue is better understood as a credential-boundary failure rather than simply an OAuth bug.
Accessible text alternative for Figure 1
Expected flow: 1. MCP client holds a credential for one authorization server. 2. Expected issuer, the client already knows which issuer it trusts. 3. Authorization server validated, discovered metadata is checked against it. 4. Trusted token endpoint, credential sent only if the identity matches. Broken flow: 1. MCP client holds a credential for one authorization server. 2. MCP server influences destination, advertising where authorization should happen. 3. Client accepts that destination, the issuer was never independently checked. 4. Attacker-controlled endpoint, the credential is exposed, not merely misused.
What does issuer protect?
OAuth terminology can make this sound more complicated than it is.
For this discussion, the issuer answers a practical question:
Which authorization server do we trust for this relationship?
Imagine a client secret belongs to auth.example.com.
The client then connects to an MCP server that tries to redirect authentication toward another authorization server.
The presence of valid-looking OAuth metadata should not be enough.
The client needs an independent expectation that says: this credential belongs to this issuer.
The fixed SDK determines the expected issuer before processing authorization-server metadata, rejects metadata with a different issuer, and binds stored registrations to that issuer.
The July 2026 MCP specification also reinforces the same principle. Persisted client credentials must remain associated with the authorization server they belong to rather than being reused when the authorization server changes.
Who is actually affected?
This issue does not mean every MCP client or server is vulnerable.
The advisory applies when both of these conditions are true.
- The application uses the Python SDK as an MCP client over HTTP with one of the affected built-in OAuth providers.
- The client may connect to an MCP server it does not fully trust while holding credentials for a legitimate authorization server.
Affected providers include OAuthClientProvider, ClientCredentialsOAuthProvider, PrivateKeyJWTOAuthProvider, and the deprecated 1.x RFC7523OAuthClientProvider.
The advisory specifically says the issue does not affect MCP servers built using the SDK, stdio clients, or clients that attach their own tokens or headers rather than using the affected OAuth handlers.
| SDK line | Affected | Patched |
|---|---|---|
| 1.x | >=1.9.1, <1.30.0 | 1.30.0 |
| 2.x | >=2.0.0a1, <2.2.0 | 2.2.0 |
The correct conclusion is therefore not that MCP authentication is insecure.
It is that a specific OAuth client implementation failed to preserve an authorization-server trust boundary.
Why upgrading may not be enough
Teams using affected versions should upgrade to 1.30.0 or later on the 1.x line, or 2.2.0 or later on the 2.x line.
But there is an important configuration detail.
Applications using ClientCredentialsOAuthProvider or PrivateKeyJWTOAuthProvider must also provide the expected issuer=.
Without it, those providers can still follow whichever authorization server the MCP server advertises.
This exposes another useful security principle:
A patched library cannot infer a trust relationship the application has never defined.
The library can enforce issuer binding.
The application still has to tell it which issuer is trusted.
Old registrations may remain a problem
An upgrade changes future behaviour. It does not automatically repair every credential relationship created in the past.
OAuth registrations stored without issuer information can remain unbound after upgrading.
Applications may therefore need to clear those records and register again, or add the correct issuer where they manage pre-registered client information themselves.
If an affected client may previously have connected to an untrusted MCP server, teams should also consider rotating its client secret and revoking associated tokens.
The distinction is important. Patching the software does not always mean removing previous exposure.
Issuer and audience protect different boundaries
There is another MCP authorization control that is easy to confuse with issuer binding.
The MCP authorization specification requires clients to identify the intended resource and requires servers to verify that access tokens were issued for that resource.
This is audience or resource binding.
The two controls protect different stages.
Issuer binding asks: which authorization server is trusted to receive and manage these credentials?
Audience or resource binding asks: which MCP service is the resulting access token meant for?
Accessible text alternative for Figure 2
Six-step chain: 1. Client credential, a secret, code or signed assertion. 2. Trusted issuer, the authorization server the client expects. 3. Authorization server, validated against that trusted issuer. 4. Access token, issued after the issuer check succeeds. 5. Expected resource, the MCP server the token names as audience. 6. MCP server, accepts the token only if it is the named audience. Steps 1 to 3 are grouped as issuer binding. Steps 4 to 6 are grouped as audience or resource binding. A closing note states issuer binding protects where a credential is sent, while audience binding protects where the resulting token can be used.
A simplified relationship looks like this: client credential, trusted issuer, authorization server, access token, expected resource, MCP server.
Issuer binding protects the first trust relationship.
Audience binding protects the token after it has been issued.
Both matter.
Why MCP makes this especially important
MCP clients may connect to many servers and interact with different authorization systems.
That creates opportunities for authorization-server mix-up. Credentials or authorization responses from one relationship can accidentally be used in another.
The MCP specification includes protections for this class of problem, including validating authorization responses against the issuer recorded earlier in the flow and keeping persisted client credentials associated with the correct issuer.
This reinforces the larger lesson:
OAuth security is not only about keeping credentials secret. It is also about preserving the relationships those credentials belong to.
Which client? Which authorization server? Which resource? Which MCP server?
A credential can be cryptographically valid and still be dangerous if one of those relationships changes without independent verification.
What should MCP teams check now?
The response can remain focused.
1. Check the SDK version
Move affected installations to 1.30.0 or later on 1.x, or 2.2.0 or later on 2.x.
2. Identify the OAuth provider
Check whether the client uses one of the affected built-in OAuth providers.
3. Configure the expected issuer
For ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider, explicitly define the authorization server the credentials belong to.
4. Review stored registrations
Clear or repair older registrations that do not contain issuer binding.
5. Consider previous exposure
If the client may have connected to an untrusted MCP server while vulnerable, consider credential rotation and token revocation.
6. Verify token audience separately
Confirm that access tokens are requested for and accepted only by their intended MCP resource.
Protect the relationship, not only the secret
This SDK vulnerability will eventually become an old advisory.
The security principle behind it will remain relevant.
Modern systems increasingly discover services dynamically. Clients follow metadata. Agents connect to tools. Authentication crosses several independent components.
That makes one question increasingly important: who is allowed to define where a credential goes?
For this Python SDK flaw, the answer is enforced through authorization-server issuer binding.
For the resulting access token, MCP separately requires resource or audience binding.
The durable security rule is therefore broader than protect the credential.
Bind the credential to the relationship it belongs to.
A secret is not safe merely because nobody can guess it.
It also has to arrive at the right place.
References and further reading
- MCP Python SDK Security Advisory GHSA-qx49-fqc8-xw99: GitHub, published September 28, 2026. Primary source for affected versions, exploit conditions, affected OAuth providers, unaffected configurations, patched versions,
issuer=guidance, stored-registration remediation and credential rotation guidance. - Model Context Protocol: Authorization: current MCP authorization specification covering authorization-server discovery, resource indicators and token validation.
- Model Context Protocol: Authorization Security Considerations: primary reference for resource and audience binding and token-validation requirements.
- Model Context Protocol: Security Best Practices: additional guidance covering mix-up attacks, authorization boundaries, token handling and related MCP security risks.
