A connector we had tested with curl for days refused to connect from Claude:
Automatic client registration isn’t supported by OdooConsole. Edit the connector and add an OAuth Client ID.
Registration was supported. POST /oauth2/register answered 201 with a
usable client, and had done all along. What was missing was one optional field
in a metadata document — and that single omission is indistinguishable, from
the client’s side, from a server that cannot register clients at all.
If you are standing up an MCP server behind your own authorization server, this is the failure you are most likely to hit, and the error message points away from the cause.
What the client is actually doing
The MCP specification defines a discovery chain, and every step is machine-driven. There is no configuration for the user to get wrong, which is why a break in the chain surfaces as a request for something no one can supply.
POST /mcp → 401 + WWW-Authenticate: resource_metadata="…"
GET /.well-known/oauth-protected-resource (RFC 9728) → names the issuer
GET <issuer>/.well-known/oauth-authorization-server (RFC 8414) → endpoints
POST <registration_endpoint> (RFC 7591) → client_id
The MCP spec requires servers to implement Protected Resource Metadata and requires clients to use it for discovery. It then gives clients a priority order for obtaining a client id:
- Pre-registered credentials, if the client has them
- Client ID Metadata Documents, if the server advertises
client_id_metadata_document_supported - Dynamic Client Registration, if the server advertises a
registration_endpoint - Prompt the user to enter the client information
Read that list against the error. No pre-registration, no CIMD advertised, no
registration_endpoint in the metadata — so the client fell through to step
four and asked a human for a client id. It was not confused. It followed the
spec exactly, and the spec’s last resort is a dialog box.
Why the field was missing
Our authorization server is Ory Hydra, with dynamic client registration enabled:
oidc:
dynamic_client_registration:
enabled: true
That switch makes /oauth2/register work. It does not add
registration_endpoint to either discovery document. We checked both:
$ curl -s https://auth.example.com/.well-known/oauth-authorization-server | jq .registration_endpoint
null
$ curl -s https://auth.example.com/.well-known/openid-configuration | jq .registration_endpoint
null
Hydra is not violating anything. In RFC 8414, the field is defined as:
registration_endpoint— OPTIONAL. URL of the authorization server’s OAuth 2.0 Dynamic Client Registration endpoint.
Optional means a conforming server may omit it. But for a client, absence is
not ambiguity — it is a negative answer. There is no probe for “maybe try
/oauth2/register anyway”, and there should not be: guessing endpoint URLs is
how you end up POSTing credentials at something that is not an authorization
server.
We already ran a small proxy in front of /oauth2/register — it strips the
null and empty-string fields Hydra emits, which strict client schemas reject.
Extending it to serve both discovery documents was a dozen lines. It now adds
three things on the way past:
| Field | Before | After |
|---|---|---|
registration_endpoint |
absent | https://auth…/oauth2/register |
scopes_supported |
openid, offline_access |
plus the resource scopes |
code_challenge_methods_supported |
plain, S256 |
S256 |
The second matters because the MCP spec tells clients to fall back to
scopes_supported when the WWW-Authenticate challenge carries no scope
parameter. If your real scopes are not listed, a client cannot ask for them.
The third is unrelated but free: advertising plain invites a downgrade to a
PKCE mode that protects nothing. Advertise S256 only — every client that
matters supports it.
Check yours in one command
Walk the chain the way a client does, rather than calling the endpoint you
already know exists. That distinction is what hid this from us: our own
registration test posted straight to /oauth2/register, so it passed while
every real client failed.
curl -s "$(curl -si https://mcp.example.com/mcp -X POST \
| grep -io 'resource_metadata="[^"]*"' | cut -d'"' -f2)" \
| jq -r .authorization_servers[0] \
| xargs -I{} curl -s {}/.well-known/oauth-authorization-server \
| jq '{registration_endpoint, scopes_supported, code_challenge_methods_supported}'
If registration_endpoint comes back null and your server can in fact
register clients, that is your bug.
One caveat, and where this is going
Dynamic Client Registration is now
deprecated in the MCP spec.
It has moved from the default mechanism to a MAY, retained for backwards
compatibility with authorization servers that do not support Client ID
Metadata Documents — where the client id is an HTTPS URL pointing at a JSON
document the authorization server fetches on demand.
CIMD is the better model. It removes per-server registration entirely, the client id is portable across authorization servers, and there is no row in your database for every client that ever tried to connect.
So the fix above has a shelf life. If you are choosing an authorization server
today, client_id_metadata_document_supported is the capability to ask about.
Until your server has it, advertise the registration endpoint you already
have — otherwise every user meets a dialog box asking for a client id that
nothing can issue.