In this article
October 2, 2026
October 2, 2026

Three MCP auth bugs in 30 days, one root cause: Trusting what the other side said

The MCP Python SDK, the Rust SDK (rmcp), and LiteLLM each accepted authentication input that nobody verified. Here is what broke, who is affected, and the checklist that closes the gap.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

Three authentication flaws in the MCP ecosystem reached public attention in a 30-day window that started on September 2, 2026. They hit different projects, and one of them sits on the other side of the connection from the other two. But all three come down to the same mistake: code accepted authentication input that nobody verified.

The short version:

  • MCP Python SDK (mcp 1.9.1 to 1.29.1 and 2.0.0 to 2.1.1): a malicious MCP server could make the OAuth client send its client secret, authorization code, and PKCE verifier to an attacker's token endpoint. Fixed in 1.30.0 and 2.2.0. Machine-to-machine providers also need an explicit issuer= argument. GHSA-qx49-fqc8-xw99, CVSS 7.5, no CVE assigned when the advisory was published on September 28.
  • MCP Rust SDK, rmcp (before 2.0.0): the client never checked that the resource field in a server's protected resource metadata matched the server it connected to, so a malicious server could point the OAuth flow at a real authorization server and walk away with a token. Fixed in 2.0.0. CVE-2026-63127, published September 16, CVSS 8.2.
  • LiteLLM (before 1.84.0): the opposite direction. The gateway's MCP endpoint trusted a client's made-up Authorization header and let the request through unauthenticated. Fixed in 1.84.0. CVE-2026-59822, CVSS 8.8. The bug was disclosed in the summer, and CISA added it to its Known Exploited Vulnerabilities catalog on September 2, which is what puts it in this window.

If you build an MCP client, run one of these SDKs, or operate a LiteLLM gateway, upgrade first and read the checklists below second.

The three bugs side by side

The three MCP auth bugs compared: Python SDK, rmcp, and LiteLLM
Python SDK rmcp (Rust SDK) LiteLLM
Direction Client trusted the server Client trusted the server Server trusted the client
Advisory GHSA-qx49-fqc8-xw99 (no CVE) CVE-2026-63127 CVE-2026-59822
Affected mcp 1.9.1 to 1.29.1, 2.0.0 to 2.1.1 rmcp before 2.0.0 LiteLLM before 1.84.0
Fixed in 1.30.0, 2.2.0 2.0.0 1.84.0
Severity CVSS 7.5 CVSS 8.2 CVSS 8.8, in CISA KEV (actively exploited)
What went unverified The authorization server the MCP server named, and which server a credential belonged to The resource in the server's protected resource metadata The Authorization header on MCP requests
Attacker gains Client secret, auth code, PKCE verifier, signed assertions An access token usable against the real resource Access to MCP tools and connected services
Timeline Advisory Sept 28 CVE Sept 16 Disclosed in the summer, KEV Sept 2

‍

Two of these fixes shipped well before the write-ups. The rmcp 2.0.0 release is dated June 29. The Python SDK advisory went public with no CVE, and the issuer checks appeared in the 1.30.0 and 2.2.0 release notes under behavior changes. That matters because vulnerability scanners and patch-triage queues key off CVEs.

Two bugs where the client trusted the server

An MCP client connects to servers it does not control, and those servers supply the metadata the client then acts on. That makes the client the party that has to do the checking. Two SDKs did not.

Bug 1: The Python SDK had two separate gaps

An MCP client starts by asking the MCP server where to log in. The server answers with the address of an authorization server. The client fetches that server's metadata and, if it is doing its job, confirms that the issuer in the metadata matches the address it was told to use (RFC 8414).

The Python SDK failed in two distinct ways.

Gap 1: The issuer was not validated consistently

The two version lines failed differently:

  • In 1.9.1 to 1.29.1, there was no issuer check on any path.
  • In 2.0.0 to 2.1.1, the check existed but was skipped in two cases: when the server publishes no protected resource metadata (the legacy fallback, which a 404 triggers), and when the server returns 403 insufficient_scope.
Gap 2: Credentials were not tied to the server that issued them

The SDK did not record which authorization server a client registration or secret belonged to, so it could present them to whichever server it was pointed at.

A malicious server only has to return a 404 or a 403 at the right moment. The SDK then sends the client secret, authorization code, and PKCE verifier to a token endpoint the attacker controls. The two unattended providers, ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider, are the worse case because no person is present to notice anything odd. On every version, those two providers had no way to name their authorization server at all.

Upgrading is necessary but not sufficient:

  1. Upgrade to 1.30.0 or 2.2.0 or later.
  2. For the two machine-to-machine providers, pass issuer= explicitly (for example issuer="https://auth.example.com"). Since 1.30.0, leaving it out triggers a DeprecationWarning, and the advisory says it becomes mandatory in 3.0.
  3. Clear any registrations stored before 1.30.0 so clients re-register against the expected issuer.
  4. Rotate secrets if a vulnerable client ever connected to a server you do not control.
  5. Move off the deprecated 1.x RFC7523OAuthClientProvider, which has no issuer= option.

Bug 2: rmcp believed what the server said about itself

MCP servers advertise their authorization servers through protected resource metadata (RFC 9728). That document includes a resource field naming the resource it describes, and the client is supposed to check that it matches the MCP server URL it actually connected to.

rmcp did not. A malicious MCP server could publish metadata that pointed at a real, legitimate authorization server. The user completes a normal-looking OAuth flow, and the attacker ends up holding a token that works against the legitimate resource within its scope. The fix requires the metadata to include a matching resource and rejects it before the client trusts any advertised authorization server or scopes. The weakness class is CWE-345, insufficient verification of data authenticity.

This is the same family as the token confusion attacks that resource indicators (RFC 8707) exist to prevent. See our post on MCP resource indicators for the audience-binding half of the story.

One bug where the server trusted the client

Bug 3: LiteLLM let a made-up header through

LiteLLM is a gateway, and the bug is in its MCP endpoint. The Streamable HTTP route supported OAuth2 passthrough. A fabricated Authorization header pushed a request into that passthrough fallback, which replaced the failed key check with an empty UserAPIKeyAuth object. The request then went through as if it were authenticated.

No client trusted a server here. The gateway trusted a request it never checked, which is the same mistake from the other direction.

The vulnerability was disclosed in the summer. What happened in September is that CISA added it to the Known Exploited Vulnerabilities catalog on September 2 with a September 16 federal deadline. That listing is the strongest signal of the three: attackers are using it. Reporting on the in-the-wild activity describes a chain with a separate Starlette flaw (CVE-2026-48710) to reach model configurations and upstream provider keys. The fix is LiteLLM 1.84.0. Until you can upgrade, block the /mcp/ routes at your reverse proxy or API gateway.

The common root cause: Input nobody verified

All three bugs trusted data that came from the other side of the connection:

Two-panel diagram comparing the direction of trust in three MCP auth bugs. Left panel, "Client trusts the server" (MCP Python SDK and rmcp): a malicious MCP server advertises a different authorization server or an unrelated resource, the MCP client SDK accepts that metadata without verifying it (the Python SDK skipped the issuer check on the 404 fallback and 403 paths, and rmcp never checked that the metadata resource matched the server URL), and the attacker gets a client secret, authorization code, PKCE verifier, or a token for the real resource. Right panel, "Server trusts the client" (LiteLLM): any caller sends a made-up Authorization header, the LiteLLM MCP endpoint's OAuth2 passthrough fallback swaps the failed key check for an empty auth object and treats the request as authenticated, and the attacker reaches MCP tools (CVE-2026-59822, listed in CISA's Known Exploited Vulnerabilities catalog).

In each case a verification step was missing, optional, or skipped on one code path, and the code accepted the input instead of rejecting it. A check that lives only on the happy path will eventually be missing from some other path.

  
# trusts what the other side said: the check lives on the happy path only
metadata = fetch(discovery_url)        # validates issuer
metadata = fetch(fallback_url)         # does not
use(metadata)

# verifies what the other side said: every path ends at the same check
metadata = fetch(any_path)
if metadata.issuer != expected_issuer:
    reject()
use(metadata)
  

Our posts on OAuth mix-up attacks and RFC 9207 and how MCP authorization breaks in production cover other places the same pattern appears.

Checklist for MCP client authors

  1. Derive the expected issuer and resource first. Compute them from the URL the user configured, before fetching any metadata. Never take the expected value from the response you are about to validate.
  2. Validate the issuer on every discovery path. That includes fallbacks, retries, redirects, and 403 insufficient_scope step-up. The issuer in authorization server metadata must match the issuer used to build the well-known URL (RFC 8414).
  3. Validate the resource in protected resource metadata. It must match the MCP server URL you connected to. Reject metadata where it is absent (RFC 9728).
  4. Send the resource parameter and bind tokens to it (RFC 8707).
  5. Check iss on the authorization response. Compare it to the issuer you recorded before sending the code anywhere (RFC 9207).
  6. Bind stored credentials to an issuer. Key client registrations and secrets by issuer, never reuse them with a different authorization server, and re-register when the server changes.
  7. Treat absence as failure. A missing field is a rejection, not a cue to substitute a default.
  8. Test the unhappy paths. Write tests for a 404 on discovery, a 403 step-up to a different provider, a mismatched issuer, and a missing resource.

Checklist for MCP server and gateway operators

  1. Deny by default. If token validation fails, the request fails. Never substitute a default identity or an empty auth object.
  2. Test with fabricated tokens. Send arbitrary non-empty Authorization values at every MCP route, including passthrough and fallback modes.
  3. Publish correct metadata. Make sure your authorization server issuer and your protected resource resource values match exactly what clients will use to build the discovery URLs, so strict clients accept them.
  4. Inventory what is exposed. Know which gateways, MCP routes, and SDK versions you run, and who can reach them.

How to check whether you are affected

How to check whether you are affected by the MCP auth bugs
Component Check Action
Python SDK pip show mcp Upgrade to 1.30.0 or 2.2.0 or later, pass issuer= for M2M providers, clear old registrations
rmcp cargo tree -i rmcp Upgrade to 2.0.0 or later
LiteLLM Check the deployed version Upgrade to 1.84.0 or later, or block /mcp/ routes until you can

Frequently asked questions

Is the MCP Python SDK vulnerability assigned a CVE?Not when the advisory was published on September 28. It is tracked as GitHub advisory GHSA-qx49-fqc8-xw99 with a CVSS score of 7.5. Without a CVE, scanners that rely on CVE feeds may not flag it.

Which MCP Python SDK versions are affected?mcp 1.9.1 through 1.29.1 and 2.0.0 through 2.1.1. The fixed versions are 1.30.0 and 2.2.0.

Is upgrading the MCP Python SDK enough?Not for ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider. You must also pass an explicit issuer= argument, otherwise those providers still follow whichever authorization server the MCP server names. Omitting it triggers a DeprecationWarning today, and it becomes mandatory in 3.0. Clear stored registrations from before 1.30.0 as well.

What is CVE-2026-63127?A flaw in the MCP Rust SDK (rmcp) before version 2.0.0. The client did not validate the resource field in RFC 9728 protected resource metadata, so a malicious MCP server could capture an access token meant for another resource. It was published September 16, is rated CVSS 8.2, and is fixed in 2.0.0.

What is CVE-2026-59822?An authentication bypass in LiteLLM's MCP Streamable HTTP endpoint before version 1.84.0. A fabricated Authorization header pushed requests into an OAuth2 passthrough fallback that replaced the failed key check with an empty auth object, so the request went through unauthenticated. CISA added it to its Known Exploited Vulnerabilities catalog on September 2, 2026.

What do these MCP vulnerabilities have in common?Each trusted authentication input from the other side of the connection without verifying it. In the two SDK bugs a client trusted a server. In LiteLLM a server trusted a client. The fix is to verify on every code path and treat missing values as failures.

Do these bugs affect MCP servers built on AuthKit?[Verify with the AuthKit team. The two SDK bugs live in MCP clients, not in authorization servers. Suggested answer: the client-side flaws affect the applications that connect to your MCP server, not the server itself, but correct and consistent metadata helps strict clients connect cleanly.]

Sources