← All posts

Your MCP client says registration isn't supported. It probably is.

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:

  1. Pre-registered credentials, if the client has them
  2. Client ID Metadata Documents, if the server advertises client_id_metadata_document_supported
  3. Dynamic Client Registration, if the server advertises a registration_endpoint
  4. 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.

If your authorization server can register clients but does not say so in its metadata, say so on its behalf. One field, and the whole flow starts working.

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.

OdooConsole gives you a dedicated Odoo instance with an MCP endpoint your agent can reach — and nothing else can.

Create an instance More posts