Client Registration
The problem OAuth never had to solve
Section titled “The problem OAuth never had to solve”Classic OAuth assumes the client developer visited the provider’s developer console and registered an app by hand. MCP breaks that assumption: a general-purpose host like Claude Code or an IDE will connect to servers its authors never heard of, run by organisations with their own authorization servers. Neither side can pre-arrange a client_id.
MCP therefore supports three registration mechanisms and tells clients which to prefer:
- Pre-registered credentials, if the client has them for this server.
- Client ID Metadata Documents (CIMD), if the authorization server advertises
client_id_metadata_document_supported: true. - Dynamic Client Registration (DCR) as a fallback, if the AS exposes a
registration_endpoint. This path is deprecated. - Ask the user to paste client details if nothing else works.
Client ID Metadata Documents
Section titled “Client ID Metadata Documents”CIMD is the mechanism the spec now recommends (SHOULD) for both clients and authorization servers. The idea is that the client’s identifier is an HTTPS URL, and that URL serves a JSON document describing the client:
{ "client_id": "https://app.example.com/oauth/client-metadata.json", "client_name": "Example MCP Client", "client_uri": "https://app.example.com", "logo_uri": "https://app.example.com/logo.png", "redirect_uris": [ "http://127.0.0.1:3000/callback", "http://localhost:3000/callback" ], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "none"}Requirements on the client:
- The
client_idURL MUST usehttpsand MUST contain a path component (https://example.com/client.json, not a bare origin). - The document MUST contain at least
client_id,client_name, andredirect_uris. - The
client_idvalue inside the document MUST match the document’s URL exactly. - Public clients (desktop apps, CLIs) use
token_endpoint_auth_method: "none". Clients that can hold a key MAY useprivate_key_jwtwith a JWKS referenced from the document.
Requirements on the authorization server:
- SHOULD fetch the document when it sees a URL-formatted
client_id. - MUST validate the fetched
client_idequals the URL, MUST validate the document is well-formed JSON with the required fields, and MUST validate theredirect_uriin each authorization request against the document’sredirect_uris. - SHOULD cache the document respecting HTTP cache headers.
sequenceDiagram participant U as User participant C as MCP Client participant A as Authorization Server participant D as Metadata URL (client's HTTPS host) C->>A: authorize (client_id = https://app.example.com/oauth/client.json) A->>U: authenticate A->>D: GET client metadata document D-->>A: JSON (client_id, client_name, redirect_uris) Note over A: validate client_id equals URL, redirect_uri allowed A->>U: consent screen showing client_name U-->>A: approve A-->>C: code via redirect_uri
Why this design wins for MCP:
- No prior relationship needed. Any AS can verify any client by fetching a document from a domain the client controls.
- Portable. Because the identifier is a self-hosted URL, the same
client_idworks with every authorization server. If a server switches to a different AS, the client needs no re-registration. - Accountable. The AS can apply trust policies by domain (allowlist, reputation, certificate checks) and display the domain prominently on the consent screen.
The AS advertises support in its metadata:
{ "client_id_metadata_document_supported": true }Clients SHOULD check that flag and MAY fall back to DCR or pre-registration when it is absent.
Pre-registration
Section titled “Pre-registration”Clients SHOULD still support static credentials, in one of two forms:
- A
client_id(and secret, if confidential) hard-coded for a specific authorization server, the way a first-party app would ship. - A settings UI where the user pastes a
client_idthey obtained by registering the client themselves in the provider’s console.
Claude Code exposes the second form on the command line:
claude mcp add --transport http \ --client-id your-client-id --client-secret --callback-port 8080 \ my-server https://mcp.example.com/mcpPre-registered credentials are tied to one issuer. If the server’s Protected Resource Metadata starts pointing at a different AS, the client SHOULD surface an error rather than try the old credentials there.
Dynamic Client Registration (deprecated)
Section titled “Dynamic Client Registration (deprecated)”RFC 7591 lets a client POST its metadata to a registration_endpoint and receive a client_id without user interaction. Early MCP relied on it heavily. It is now deprecated in favour of CIMD and remains only for authorization servers that do not support metadata documents.
If you do fall back to DCR, one field is now mandatory:
{ "client_name": "Example MCP Client", "redirect_uris": ["http://127.0.0.1:3000/callback"], "grant_types": ["authorization_code"], "token_endpoint_auth_method": "none", "application_type": "native"}application_type MUST be set. Under OpenID Connect Dynamic Client Registration, omitting it defaults to "web", and OIDC servers then reject localhost and custom-scheme redirect URIs. Use:
"native"for desktop apps, mobile apps, CLI tools, and locally hosted web apps reached vialocalhost."web"for remote browser-based applications served from a real host.
Non-OIDC servers ignore the parameter, so there is no cost to always sending it. Clients MUST be prepared for registration to fail on redirect-URI constraints, SHOULD show a meaningful error, and MAY retry with an adjusted application_type or conforming redirect URIs.
Why DCR lost: every AS ends up with an unbounded table of anonymous registrations, each client gets a different client_id per AS, and the combination of static upstream client IDs with dynamic downstream registration is exactly what enables the confused-deputy attack described in Threats and Mitigations.
Credentials are bound to their issuer
Section titled “Credentials are bound to their issuer”Whichever way a client got its credentials, they belong to the authorization server that issued them:
- Clients MUST key persisted credentials (client IDs, secrets, tokens) by the AS
issueridentifier. - Clients MUST NOT reuse credentials with a different authorization server.
- When the server’s Protected Resource Metadata names a new AS, clients MUST re-register there (or, with CIMD, simply present the same URL, which the new AS resolves on demand).
The practical consequence: a token store is a map from issuer to credentials, never a flat “the token for this server”.
Localhost redirect URIs and trust
Section titled “Localhost redirect URIs and trust”Native clients redirect to http://127.0.0.1:PORT/callback. CIMD proves the client’s authors control app.example.com; it cannot prove which local process is listening on that port. An attacker can therefore:
- Use the legitimate client’s metadata URL as their
client_id. - Bind any localhost port and supply it as
redirect_uri(it is in the allowed list). - Receive the authorization code when the user approves what looks like the real client.
The spec’s countermeasures live on the authorization server: display additional warnings when a client’s metadata contains only localhost redirect URIs, show the redirect hostname clearly during consent, and apply trust policies to URL-based client IDs. Protected servers may allowlist domains; open servers may accept any HTTPS client_id but still check reputation, domain age, or certificates, and must always display the client’s hostname so phishing stands out.
Choosing, as a client author
Section titled “Choosing, as a client author”| Situation | Use |
|---|---|
| You ship a first-party client for your own AS | Pre-registered client_id |
| General-purpose host connecting to arbitrary servers | CIMD, hosted on your product’s domain |
AS is old and has only a registration_endpoint | DCR with application_type set, and log that you fell back |
| Nothing works | Ask the user for client details |