Repository navigation
Server OAuth metadata hardcodes token_endpoint_auth_methods_supported, breaking public client flows #2260
Description
Activity
- added a commit that references this issue
on 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:// If server metadata omits token_endpoint_auth_methods_supported, RFC 8414 §2 says the // default is client_secret_basic. RFC 6749 §2.3.1 also requires servers to support HTTP // Basic authentication for clients with a secret, making it the safest default. if (supportedMethods.length === 0) { return hasClientSecret ? 'client_secret_basic' : 'none'; }
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.Official MCP example server uses
"none"The official
modelcontextprotocol/example-remote-server(source) explicitly sets:token_endpoint_auth_methods_supported: ['none'],
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.
Reacted by Kuninoto-
- added a commit that references this issue
on Mar 9, 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:
token_endpoint_auth_methods_supported=["client_secret_post", "client_secret_basic"],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.
Initial Checks
Description
build_metadata()inmcp/server/auth/routes.pyhardcodestoken_endpoint_auth_methods_supportedto["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:Spec Analysis
The MCP authorization spec (2025-06-18) requires support for public clients:
And explicitly recommends dynamic client registration:
The best practices section further recommends:
OAuth 2.1 (draft-ietf-oauth-v2-1-13, Section 2.1) defines public clients:
RFC 7591 (Section 2) defines
token_endpoint_auth_method: "none"as the way to register public clients:RFC 7591 (Section 3.2.1) makes
client_secretoptional in registration responses, allowing it to be omitted for public clients.RFC 8414 (Section 2) defines
token_endpoint_auth_methods_supportedas using values fromtoken_endpoint_auth_methodin RFC 7591 — which explicitly includes"none".The Problem
The SDK's registration handler (
register.py:54-60) already correctly supports public clients: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:
/.well-known/oauth-authorization-server["client_secret_post", "client_secret_basic"]supportedtoken_endpoint_auth_method: "none"(public client, per MCP best practices)client_secretreturned (correct per RFC 7591)client_secretto send, but the metadata says one is requiredClaude 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 defaulttoken_endpoint_auth_methods_supportedlist, since the registration handler already supports it:The same change should apply to
revocation_endpoint_auth_methods_supportedfor consistency.A PR is available: #2261
Related Issues
ClientAuthenticatorignorestoken_endpoint_auth_method="none"whenclient_secretis stored (client-side counterpart)Python & MCP Python SDK