Embedded auth server rejects DCR from confidential MCP clients (`token_endpoint_auth_method` hardcoded to `none`)
@jhrozek is already working on this.
Since Aug 6, 2026.
- Dominant language
- Go
- Stars
- 2.2k
- Forks
- 300
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 184
Description
Summary
The embedded authorization server's DCR endpoint hardcodes token_endpoint_auth_method to none and refuses any client that registers with a different method or that expects a client_secret returned in the DCR response. This blocks any MCP client that implements OAuth as a confidential client.
Currently failing in production: Perplexity's MCP integration. ChatGPT's MCP support appears to follow the same confidential-client pattern (unverified in our setup — flagging as likely affected based on its public OAuth UX, would be good to confirm).
Working fine with the current implementation: Claude Code, Claude Desktop, Cursor, MCP Inspector (all public-client + PKCE).
Reproduction
- Deploy ToolHive operator v0.27.2 with a
VirtualMCPServerthat usesauthServerConfig(embedded AS, any upstream — e.g. AWS Cognito or Google via OIDC upstream provider). - In Perplexity's MCP server configuration, add the vMCP URL (e.g.
https://mcp.example.com/).
First failure (already addressed by adding offline_access to advertised scopes — separate concern, calling out for completeness):
[API_CLIENTS_ERROR] {"error":"invalid_client_metadata","error_description":"default scope not supported by server: offline_access"}
After adding offline_access to incomingAuth.oidcConfigRef.scopes, second failure:
[API_CLIENTS_ERROR] Dynamic client registration did not return a client_secret
Server-side, the AS logs failed to create access request with error: "invalid_client" once Perplexity attempts the token exchange with a client_secret it expected but never received.
Root cause
pkg/authserver/server/registration/dcr.go:219-229 at tag v0.27.2:
// 5. Validate/default token_endpoint_auth_method
authMethod := req.TokenEndpointAuthMethod
if authMethod == "" {
authMethod = "none"
}
if authMethod != "none" {
return nil, &DCRError{
Error: DCRErrorInvalidClientMetadata,
ErrorDescription: "token_endpoint_auth_method must be 'none' for public clients",
}
}
This is reinforced by /.well-known/openid-configuration, which advertises:
"token_endpoint_auth_methods_supported": ["none"]
The intent is clearly to enforce the OAuth 2.1 / RFC 7591 best practice for native clients (no shared secrets in distributed apps), which is correct for many MCP clients. However, some commercial MCP clients (Perplexity, likely ChatGPT) implement their MCP integration as a server-side confidential client and require a client_secret to be issued at DCR time. For those clients, there is no workaround on the client side; the AS must support client_secret_basic or client_secret_post.
Impact
| Client | Behaviour |
|---|---|
| Claude Code, Claude Desktop, Cursor, MCP Inspector | ✅ Work (public client + PKCE) |
| Perplexity | ❌ Fail at DCR — needs client_secret returned |
| ChatGPT | Likely ❌ — uses confidential-client OAuth in its other integrations |
| VS Code | ✅ (per #2585) |
| WindSurf | Untested per #2585 |
Without confidential-client support, the embedded AS cannot serve as the auth surface for the full set of MCP-capable clients an enterprise may want to support.
Proposed solution
Add an opt-in flag on EmbeddedAuthServerConfig (e.g. allowConfidentialClients: bool) that, when set:
- Accepts
token_endpoint_auth_methodvalues ofclient_secret_basicandclient_secret_post(in addition tonone) at/oauth/register. - Returns a freshly generated
client_secretin the DCR response when the registered method requires one (RFC 7591 §3.2.1). - Stores the secret hashed in the credential store (the work in #4979 / #5185 already provides the persistence layer).
- Validates
client_secret_basic/client_secret_postat/oauth/token. - Advertises the additional methods in
token_endpoint_auth_methods_supportedonly when the flag is set.
This keeps the safe default (PKCE-only public clients) while letting operators opt into confidential clients when they need to support clients like Perplexity. The field documentation should mention that operators are accepting the risk of leaked secrets in distributed clients.
Alternative: support Client ID Metadata Document (CIMD) as a third path — but CIMD doesn't issue client_secret either, so it doesn't directly help Perplexity unless Perplexity adds CIMD support too.
Workarounds
For users hitting this today:
- Skip Perplexity — use only public-client MCP clients (Claude Code, Cursor, etc.).
- Use a different IdP that supports both public and confidential DCR — Keycloak, Authentik, ZITADEL, Auth0 — and point
MCPOIDCConfigdirectly at it without the embedded AS. Adds infra weight and isn't a great fit if Cognito/Google is already the source of truth.
References
- RFC 7591 §3.2.1 — DCR response with
client_secretfor confidential clients - RFC 6749 §2.3.1 —
client_secret_basic - Related: #2585 (validate auth across major clients — doesn't currently include Perplexity/ChatGPT)
- Related: #4825 (CIMD support — alternative for clients that publish their own metadata)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.