You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 VirtualMCPServer that uses authServerConfig (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_methodauthMethod:=req.TokenEndpointAuthMethodifauthMethod=="" {
authMethod="none"
}
ifauthMethod!="none" {
returnnil, &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
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_method values of client_secret_basic and client_secret_post (in addition to none) at /oauth/register.
Returns a freshly generated client_secret in the DCR response when the registered method requires one (RFC 7591 §3.2.1).
Validates client_secret_basic / client_secret_post at /oauth/token.
Advertises the additional methods in token_endpoint_auth_methods_supported only 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 MCPOIDCConfig directly 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_secret for confidential clients
Summary
The embedded authorization server's DCR endpoint hardcodes
token_endpoint_auth_methodtononeand refuses any client that registers with a different method or that expects aclient_secretreturned 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
VirtualMCPServerthat usesauthServerConfig(embedded AS, any upstream — e.g. AWS Cognito or Google via OIDC upstream provider).https://mcp.example.com/).First failure (already addressed by adding
offline_accessto advertised scopes — separate concern, calling out for completeness):After adding
offline_accesstoincomingAuth.oidcConfigRef.scopes, second failure:Server-side, the AS logs
failed to create access requestwitherror: "invalid_client"once Perplexity attempts the token exchange with aclient_secretit expected but never received.Root cause
pkg/authserver/server/registration/dcr.go:219-229at tagv0.27.2:This is reinforced by
/.well-known/openid-configuration, which advertises: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_secretto be issued at DCR time. For those clients, there is no workaround on the client side; the AS must supportclient_secret_basicorclient_secret_post.Impact
client_secretreturnedWithout 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:token_endpoint_auth_methodvalues ofclient_secret_basicandclient_secret_post(in addition tonone) at/oauth/register.client_secretin the DCR response when the registered method requires one (RFC 7591 §3.2.1).client_secret_basic/client_secret_postat/oauth/token.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_secreteither, so it doesn't directly help Perplexity unless Perplexity adds CIMD support too.Workarounds
For users hitting this today:
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
client_secretfor confidential clientsclient_secret_basic