Stop assuming your MCP client is secure just because you updated the package. A new analysis of the Model Context Protocol (MCP) SDK reveals that standard OAuth discovery flows leave agents vulnerable to credential theft by hostile servers. The core issue isn't a broken library; it's a trust model that lets the server dictate where the client sends its secrets. For developers building autonomous agents, this means patching the SDK is only half the battle. You must hardcode the trusted issuer before the client ever asks for directions.
The High-Severity Gap in MCP Auth
On September 28, 2026, the MCP Python SDK maintainers published advisory GHSA-qx49-fqc8-xw99, rating the issue High with a CVSS score of 7.5. The vulnerability allows a connected MCP server to decide where the client's OAuth credentials are sent. In affected versions, a malicious server could intercept the client_secret, the authorization code, and the PKCE code_verifier. Cycode, who reported the bug, demonstrated that when a server returns a 404 for discovery, the SDK falls back to asking the MCP server itself. The issuer check never runs because there is no reference point to compare against, leaving the user staring at a login page that looks real but sends data to an attacker.
Why Upgrading Alone Isn't Enough
The fixed versions, mcp 1.30.0 and 2.2.0, address the code path but not the configuration default. The advisory explicitly states that for machine-to-machine providers, "upgrading changes nothing until you also pass issuer=." Without this parameter, the client continues to follow the server's lead. WorkOS highlighted this pattern alongside other recent bugs, such as the Rust SDK (rmcp) failing to check the resource field (CVE-2026-63127) and LiteLLM trusting fabricated Authorization headers (CVE-2026-59822). The common thread is code that accepts authentication input without verifying its origin. If you don't pin the issuer, you are effectively letting a stranger decide where your bank account details go.
Building a Pinned-Issuer Client in TypeScript
To prove the fix works, developer Bobby Hall built a tiny MCP client in TypeScript that enforces issuer pinning before any network request occurs. The implementation refuses connections if the issuer credential is missing, rather than falling back to server-provided metadata. It validates that the resource field in the Protected Resource Metadata (RFC 9728) matches the server URL and that the server explicitly names the pinned issuer in its authorization_servers list. Login metadata is always fetched from the pinned issuer's origin, ensuring the token endpoint is never chosen by the MCP server. This shifts the trust anchor from the remote server to the local configuration.
Key Takeaways
- Pin the Issuer: Always pass
issuer=in your MCP client configuration; relying on SDK defaults leaves you open to fallback exploits. - Validate Resource Metadata: Ensure the
resourcefield in the server's response matches the actual server URL to prevent token audience confusion. - Fetch Metadata from Trusted Origins: Never fetch OAuth Authorization Server Metadata from the MCP server itself; only use the pinned issuer's
.well-knownendpoint. - Rotate Secrets After Exposure: If your client previously talked to untrusted servers without pinning, rotate your
client_secretimmediately.
The Bottom Line
Your agent's credentials belong to its lane, not to whoever asks for them. Stop treating the MCP server as a trusted guide for authentication; treat it as an untrusted peer that must prove its identity against your pinned configuration.