Repository navigation
Server OAuth metadata hardcodes token_endpoint_auth_methods_supported, breaking public client flows #2260
Description
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
build_metadata() in mcp/server/auth/routes.py hardcodes token_endpoint_auth_methods_supported to ["client_secret_post", "client_secret_basic"]:
https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/server/auth/routes.py#L168
This omits "none", which is a valid value defined in RFC 7591 Section 2 for public clients:
The client is a public client as defined in OAuth 2.0, Section 2.1, and does not have a client secret.
Spec Analysis
The MCP authorization spec (2025-06-18) requires support for public clients:
Authorization servers MUST implement OAuth 2.1 with appropriate security measures for both confidential and public clients.
And explicitly recommends dynamic client registration:
MCP clients and authorization servers SHOULD support the OAuth 2.0 Dynamic Client Registration Protocol (RFC7591) to allow MCP clients to obtain OAuth client IDs without user interaction.
The best practices section further recommends:
We strongly recommend that local clients implement OAuth 2.1 as a public client: 1. Utilizing code challenges (PKCE) for authorization requests...
OAuth 2.1 (draft-ietf-oauth-v2-1-13, Section 2.1) defines public clients:
Clients without credentials are called "public clients."
RFC 7591 (Section 2) defines token_endpoint_auth_method: "none" as the way to register public clients:
"none": The client is a public client as defined in OAuth 2.0, Section 2.1, and does not have a client secret.
RFC 7591 (Section 3.2.1) makes client_secret optional in registration responses, allowing it to be omitted for public clients.
RFC 8414 (Section 2) defines token_endpoint_auth_methods_supported as using values from token_endpoint_auth_method in RFC 7591 — which explicitly includes "none".
The Problem
The SDK's registration handler (register.py:54-60) already correctly supports public clients:
if client_metadata.token_endpoint_auth_method != "none":
client_secret = secrets.token_hex(32)But build_metadata() doesn't advertise "none" as a supported method. This creates a contradictory situation: the server accepts public client registrations but tells clients it doesn't support them.
Impact: Real-World Breakage
MCP clients (Claude Code, Cursor, etc.) that follow the spec:
- Discover the authorization server metadata via
/.well-known/oauth-authorization-server - See only
["client_secret_post", "client_secret_basic"]supported - Register a dynamic client requesting
token_endpoint_auth_method: "none"(public client, per MCP best practices) - Registration succeeds — no
client_secretreturned (correct per RFC 7591) - Token exchange fails — the client has no
client_secretto send, but the metadata says one is required
Claude Code shows: "Existing OAuth client information is required when exchanging an authorization code"
The browser OAuth flow completes successfully, the authorization code is received, but the token exchange never happens because the client cannot reconcile the metadata (secrets required) with the registration response (no secret issued).
Suggested Fix
Include "none" in the default token_endpoint_auth_methods_supported list, since the registration handler already supports it:
token_endpoint_auth_methods_supported=["client_secret_post", "client_secret_basic", "none"],The same change should apply to revocation_endpoint_auth_methods_supported for consistency.
A PR is available: #2261
Related Issues
- ClientAuthenticator ignores token_endpoint_auth_method="none" when client_secret is stored #1842 —
ClientAuthenticatorignorestoken_endpoint_auth_method="none"whenclient_secretis stored (client-side counterpart) - OAuth TokenHandler should check Authorization header for client credentials #1315 — OAuth TokenHandler should check Authorization header for client credentials
Python & MCP Python SDK
Python: 3.13
MCP SDK: 1.26.0
Activity
fix: include "none" in token_endpoint_auth_methods_supported metadata
namabile commentedon Mar 9, 2026
Cross-reference: TypeScript SDK client behavior
For additional context, the TypeScript SDK's client (
packages/client/src/client/auth.ts) has aselectClientAuthMethod()function that handles this more gracefully:When the metadata is empty, it defaults to
"none"for public clients. However, when the metadata explicitly lists only["client_secret_post", "client_secret_basic"](as the Python SDK'sbuild_metadata()does), the TS client sees the server doesn't support"none"and fails.This confirms the fix should be on the server side (metadata should include
"none") rather than only on the client side. The server's registration handler already acceptstoken_endpoint_auth_method: "none"— the metadata should reflect this.namabile commentedon Mar 9, 2026
Official MCP example server uses
"none"The official
modelcontextprotocol/example-remote-server(source) explicitly sets:This confirms that
"none"is the intended auth method for MCP servers accepting public client registrations. The Python SDK'sbuild_metadata()is the outlier here — it hardcodes["client_secret_post", "client_secret_basic"]while the official example uses["none"].Additional references
modelcontextprotocol/modelcontextprotocol#276 — "Private vs Public clients in the Authorization / OAuth 2.1 spec" — discusses how CLI/desktop clients are public clients and
client_secretis redundant when PKCE is used.MCP Authorization Spec (2025-06-18) Best Practices: "We strongly recommend that local clients implement OAuth 2.1 as a public client."
anthropics/claude-code#26675 — Open issue about Claude Code's OAuth flow for MCP servers (related but distinct — about DCR being required).
OpenAI Developer Community — ChatGPT registers with
token_endpoint_auth_method: "none"but expects aclient_secret— same class of bug, demonstrating this affects multiple MCP clients.fix: include "none" in token_endpoint_auth_methods_supported metadata
aminabedi-bentley commentedon May 21, 2026
Hi maintainers,
Flagging that this issue was closed as completed on 2026-03-09, but the linked fix #2261 is still open and unmerged (created the same day, ~2 months ago, currently REVIEW_REQUIRED).
I just verified against the latest released mcp==1.27.1 (2026-05-08) and main: build_metadata() in src/mcp/server/auth/routes.py still hardcodes:
with no "none". Downstream, fastmcp==3.3.1 (2026-05-15) calls this build_metadata() directly, so it inherits the same bug, ironically while registering its own proxy DCR clients with token_endpoint_auth_method="none" internally.
The practical impact is unchanged from the original report: public MCP clients (VS Code, Claude Code, ChatGPT, Cursor) that follow the MCP authorization spec's "implement OAuth 2.1 as a public client" guidance can't complete DCR against a Python-SDK-based server without downstream workarounds (e.g. an ASGI middleware that rewrites the metadata response).
Could this be reopened, or #2261 prioritized for review? Happy to help test if useful.