Register a connector
An AI client adding a custom connector registers with no prior arrangement — nobody creates it in an admin UI first. Without dynamic client registration that flow cannot be set up at all, which is why DCR is v1 here rather than v2.
Register
Section titled “Register”curl -X POST https://jons.auth.example/register \ -H 'Content-Type: application/json' \ -d '{ "client_name": "Claude", "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"], "grant_types": ["authorization_code", "refresh_token"], "scope": "openid profile email offline_access", "token_endpoint_auth_method": "none" }'{ "client_id": "2bb32482-4552-440c-a097-8d6ce5d72e48", "client_name": "Claude", "token_endpoint_auth_method": "none", "registration_access_token": "…", "registration_client_uri": "https://jons.auth.example/register/2bb32482-…"}token_endpoint_auth_method: "none" makes it a public client — no secret is issued, and PKCE
becomes mandatory rather than optional.
Leaving scope off entirely is fine: a registration that names no scopes gets all four masks
defines, offline_access included. A connector that cannot refresh has to send the person back
through sign-in to keep working, which is worse than the token it was denied.
Manage it afterwards (RFC 7592)
Section titled “Manage it afterwards (RFC 7592)”The registration_access_token is the only credential that works against registration_client_uri:
curl -H "Authorization: Bearer $REGISTRATION_TOKEN" \ https://jons.auth.example/register/$CLIENT_IDPUT updates the metadata, DELETE archives the client. A wrong token, or the right token against
another tenant, is 401.
Redirect URIs
Section titled “Redirect URIs”Validated at registration, not at authorize time. They must be absolute, carry no fragment, and use
https unless the host is loopback — so http://localhost:3000/callback is accepted and
http://example.com/callback is not.
At /authorize the comparison is exact string equality against a registered URI. There is no prefix
or wildcard matching.
Then the normal flow
Section titled “Then the normal flow”GET /authorize ?response_type=code &client_id=2bb32482-… &redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback &scope=openid+profile+email+offline_access &state=… &code_challenge=…&code_challenge_method=S256 &resource=https%3A%2F%2Fjons.things.example%2FmcpThe resource is what makes the resulting access token usable at your API and useless anywhere else
— see tokens and audiences.
Sign-in and consent happen in the browser; the redirect back carries code, state, and iss.
Then exchange it:
curl -X POST https://jons.auth.example/token \ -d grant_type=authorization_code \ -d code=… \ -d client_id=… \ -d redirect_uri=https://claude.ai/api/mcp/auth_callback \ -d code_verifier=… \ -d resource=https://jons.things.example/mcpFrom Ruby
Section titled “From Ruby”masks does the whole thing:
issuer = Masks::Client.issuer("https://jons.auth.example")
registration = Masks::Client::Registration.create( issuer, name: "things", redirect_uris: ["https://jons.things.example/auth/callback"], scope: %w[openid profile email offline_access])
session = registration.session(redirect_uri: "https://jons.things.example/auth/callback")started = session.start(resource: "https://jons.things.example/mcp")
# redirect the browser to started[:url], keep started[:verifier] and started[:state]
tokens = session.complete(code: params[:code], verifier: started[:verifier])