Repository navigation
Revocation requires client_secret to be present #3508
Description
Activity
- addedv2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)v1Affects the v1.x maintenance lineAffects the v1.x maintenance line
on Sep 15, 2026 Confirmed the bug on 2.2.0. RevocationRequest.client_secret is defined as str | None with no default, so Pydantic v2 treats it as required — public clients that omit the field entirely get a 400 {"error":"invalid_request","error_description":"client_secret: Field required"}.
Fix is one line in src/mcp/server/auth/handlers/revoke.py:
client_secret: str | None = NoneClientAuthenticator already handles token_endpoint_auth_method == "none" correctly — this is purely a model validation gap. I'd like to fix it if you're open to an outside PR.
I confirmed this behavior on 2.x. Omitting
client_secretfrom a/revokerequest for a client registered withtoken_endpoint_auth_method: "none"currently results in a400response withclient_secret: Field required.The cause is that
RevocationRequest.client_secretis defined asstr | Nonebut does not have a default value, making the field required.ClientAuthenticatoralready handles public clients correctly, so the fix is to define it asclient_secret: str | None = None.I’ve implemented this fix and added a regression test that fails before the change and passes after it. The changes are included in #3512.
Same issue here.
A dynamically registered public client (
token_endpoint_auth_method=none) can complete authorization-code + PKCE, obtain and rotate access/refresh tokens, and call the protected MCP resource.Calling
/revokewithoutclient_secretthen returns: 400 invalid_request, client_secret: Field requiredAdding one related detail from running a public-client flow with claude.ai (CIMD) on 2.3.0, in case it helps whoever picks this up.
Making
client_secretoptional inRevocationRequestfixes the 400, but the authorization server metadata built bybuild_metadatastill advertises:"revocation_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"]
without
"none". A public client (Claude registers withtoken_endpoint_auth_method: "none"through CIMD or DCR) that reads the metadata can conclude it isn't allowed to revoke, so sessions may stay open after disconnecting. It's the same situation #2260 described fortoken_endpoint_auth_methods_supported.So the fix probably needs both:
client_secret: str | None = NoneinRevocationRequest."none"inrevocation_endpoint_auth_methods_supported(and intoken_endpoint_auth_methods_supported, for public clients).
Our workaround on 2.3.0, in case it's useful to others: we replace the
/revokeroute with a small handler that reusesClientAuthenticator(it already acceptsnone), and add"none"to both lists withOAuthMetadata.model_copy(update=...)before serving the metadata.
Initial Checks
Release line
2.x (current stable)
Description
What happened?
When Claude Code (or any other public client) triggers revocation - revoke handler requires client_secret to be present, as seen here:
python-sdk/src/mcp/server/auth/handlers/revoke.py
Line 23 in 9972c21
Per OAuth 2.0 specifications (RFC 6749 Section 2.3 and RFC 7009 Section 5), public clients do not have a
client_secretand identify themselves using onlyclient_id.This leads to issue when public clients do not sent client secret at all.
In Pydantic v2, defining a field as
str | Nonewithout a default value still marks the field as required (i.e., nullable value, but the key must exist in the request body).Steps to reproduce
400 Bad Requestwith:{"error":"invalid_request","error_description":"client_secret: Field required"}Expected behavior
client_secretshould be optional so public clients can revoke tokens without sending aclient_secretkey.Fix
Adding default value for client_secret:
Workaround
As workaround I'm overriding Request type:
Example Code
Python & MCP Python SDK