Skip to content

Build1 publisher2 min readPublished

MCP's Python SDK fix needs a hard-coded OAuth issuer to protect machine-to-machine clients

MCP Python SDK maintainers rated a flaw that let a connected server choose where OAuth secrets were sent High, at 7.5. For its two machine-to-machine providers, upgrading changes nothing until the client names the issuer it expects.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Illustration accompanying MCP's Python SDK fix needs a hard-coded OAuth issuer to protect machine-to-machine clients
Generated illustration

What happened

  • Cycode, which reported the flaw, showed a hostile server answering discovery with a 404 so the SDK falls back to asking that server, leaving the issuer check nothing to compare.
  • Through that path a hostile server could collect the client_secret, the authorization code and the PKCE code_verifier.
  • Fixed releases are mcp 1.30.0 and 2.2.0, covering both the 1.x and 2.x lines of the package.
  • WorkOS grouped the flaw with a Rust rmcp bug that skipped the resource-field check and a LiteLLM bug that trusted a forged Authorization header, all within 30 days.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision Each machine-to-machine client now needs an owner who knows its expected issuer and writes it into configuration, a code change a dependency bump cannot make on its own.
  • exposure A person watching the login flow has nothing to catch, so detection has to sit in the client before any token request is built.
  • constraint Pinning only works for providers known in advance; agents reaching servers nobody chose need an issuer allowlist or a client that withholds the secret.

Two documents decide where a client's secret goes. Protected resource metadata, from RFC 9728, is the MCP server describing itself and listing the authorization servers that handle its logins [10]. Authorization server metadata, from RFC 8414, is the login provider naming its issuer and its token endpoint [10]. The token endpoint is where the secret is sent [11]. "Whoever writes that URL decides who gets your credentials," the author of a dev.to post on the advisory wrote [12].

In the fallback Cycode described, one server ends up writing both answers [4]. The opening move is a 404, a response every web server already knows how to send [4]. Meanwhile the user sees a real login page at the real URL [5]. Watching the browser catches nothing. The check has to happen inside the client.

A pinned issuer gives the client something to compare against when discovery fails, and the server cannot edit it. The advisory, GHSA-qx49-fqc8-xw99 [1], is direct about the machine-to-machine providers, as quoted in the post: "upgrading changes nothing until you also pass issuer=" [7]. Upgrading gets you the fixed code [6]. On those providers, the pin is what puts the fix to use [7]. I think hard-coding the issuer is the right default for any client whose login provider is known at deploy time.

That default stops working where the post says agents now operate: against servers nobody on the team picked by hand [16]. A team cannot pin an issuer it has never seen. For those connections, I'd expect the workable options to be an allowlist of issuers or a client that refuses to send the secret.

The post includes a TypeScript model. It has two clients, one honest server and three hostile ones: one returns 404 on discovery, one names its own server, and one claims the real resource [13]. The author calls it "my small model of the bug class, not the SDK's code" [14]. So it is a result on the author's own workload, a mock network the author wrote. To carry over, a real SDK has to check the pin on every discovery path before a token request leaves. That includes the fallback path the Python SDK got wrong [4].

The two bugs WorkOS set beside this one involve different checks [8]. Pinning the issuer covers neither the resource field rmcp skipped nor the Authorization header LiteLLM trusted [8]. WorkOS summarised the three as "code accepted authentication input that nobody verified" [9].

What to watch

  • Whether the Python SDK makes issuer= mandatory for its machine-to-machine providers, so that an upgrade alone closes the hole.
  • Whether other MCP SDKs, including TypeScript, fall back to asking the MCP server when authorization metadata discovery returns a 404.
  • A published fix and affected-version range for rmcp's resource-field bug, CVE-2026-63127.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories