A quiet off-white room where a single brass plaque is bolted to a door frame, the one fixed point in the scene, while the floor in every direction is covered edge to edge with identical blank visitor badges and their tangled lanyards.

DCR and CIMD: OAuth Client Identity for MCP9309717

By

On this page

Open the client registration table on an authorization server that has been fronting MCP servers for a year. Row after row of `client_id` values, each one minted for a single install of maybe five different applications, most of them belonging to people who reinstalled a desktop client, switched laptops, or cleared a token cache. Nothing in the data tells you which rows are the same product, and nothing tells you which ones are a script somebody pointed at the endpoint over a weekend.

That table is what Dynamic Client Registration asked every authorization server to run: an unauthenticated write endpoint, on the public internet, open to strangers by design. It was not an oversight. It was the only way to make OAuth work for a protocol where clients meet servers at runtime, and for two years it was the assumed path for MCP.

The 2026-07-28 spec deprecated it and recommended something that stores nothing at all. The replacement is better, and it is worth being precise about which problems it actually solves, because several of the ones people expect it to solve are sitting exactly where they were.

Why MCP Reached for DCR in the First Place

OAuth was built for a world where a developer signs up at a provider's portal once, copies a `client_id` and `client_secret` into a config file, and ships. MCP broke that assumption on day one. An MCP client such as Claude Desktop, an IDE, or an autonomous agent gets pointed at servers whose operators the client author has never met, at runtime, because a user typed a URL into a settings box.

That is an N by M problem. Every client would need a pre-arranged registration at every authorization server, and every server operator would need to onboard every client. With hundreds of clients and thousands of servers, out-of-band registration stops working, and a brand new MCP server sits unusable by an existing client until somebody does paperwork.

RFC 7591 Dynamic Client Registration was the obvious escape hatch.1 Let the client register itself over HTTP at the moment it first meets the server. The 2025-03-26 authorization spec said clients and authorization servers SHOULD support it, and in practice that became the only path, because a client that could not do DCR could not connect to a server it had not been hard coded for.

The 2025-06-18 revision tightened everything around registration without touching registration itself.2 The MCP server became an OAuth 2.1 resource server, discovery moved to RFC 9728 Protected Resource Metadata3, and tokens got audience bound with the RFC 8707 `resource` parameter4. Registration stayed on DCR, and that is the piece the 2026-07-28 spec replaced.

One Unauthenticated POST

DCR is a single HTTP request. The authorization server advertises a `registration_endpoint` in its RFC 8414 metadata5, the client posts a JSON description of itself, and it gets an identity back.

POST /register HTTP/1.1
Content-Type: application/json

{
  "client_name": "Example MCP Client",
  "redirect_uris": ["http://127.0.0.1:3000/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "application_type": "native"
}

The server replies 201 with a freshly minted `client_id`, optionally a `client_secret` and its expiry, and optionally a `registration_access_token` and `registration_client_uri` for managing the registration later. The client stores the pair and runs a normal PKCE authorization code flow.

Two details bite in practice. Credentials are bound to one authorization server, so the spec requires clients to key them by `issuer` and re-register if a server changes its AS. And under OIDC, `application_type` defaults to `"web"`, which rejects `localhost` redirect URIs, so a desktop MCP client that omits the field gets a registration failure that looks exactly like a server bug.

Where DCR Falls Apart

Every complaint below comes from the same root. DCR solved the scaling problem by making every authorization server run an unauthenticated write endpoint on the public internet.

It is a free row in your database, for anyone. Nothing guards `POST /register` in the MCP profile, because a client with no prior relationship is precisely the case it exists to serve. A script is also a client with no prior relationship. Operators end up bolting on rate limiting, quotas, and garbage collection that RFC 7591 never specified, with no signal available to separate a real client from noise.

The state that accumulates carries no meaning. Each install of a desktop MCP client registers separately, and clearing a token cache, reinstalling, or moving to a new machine produces another registration. The table grows without telling you anything, because nothing in the data says that ten thousand of those rows are the same application.

Client identity from DCR also does not survive anything. A `client_id` is an opaque string, unique to one authorization server and to one registration event. You cannot allowlist a client, rate limit one, revoke one across your estate, or notice that the thing asking for `files:write` today is the thing that asked yesterday. Every per-client policy an enterprise actually wants is unwritable.

Nothing in a registration is verified, either. `client_name` and `logo_uri` are strings the caller chose. An attacker registers as "Claude Desktop" with their own logo and their own redirect URI, and the consent screen renders it faithfully, which leaves the one place a user makes a trust decision displaying unvetted attacker-supplied content.

Most real identity providers will not do it, and that quietly undermined the whole design. Okta, Entra ID, and enterprise Auth0 tenants either gate RFC 7591 behind an initial access token or do not expose it at all, which is a sensible position given everything above. A stranger client has no initial access token. So the universal mechanism frequently is not there, and clients degrade to asking the user to paste a client ID, which is the manual registration DCR existed to eliminate.

The client side carries state too. Credentials have to be persisted per authorization server, keyed by `issuer`, and re-registered when a server changes its AS. When the server prunes a stale registration, the client is holding a `client_id` that no longer exists, and the user sees an opaque 401 loop with nothing in it to act on.

It also makes the confused deputy problem worse. An MCP proxy server holding a static upstream client ID, fronting dynamically registered downstream clients, can be tricked into exchanging a stolen authorization code without fresh consent.6 The spec has to say out loud that such servers MUST obtain user consent per dynamically registered client, which is the kind of rule you only write after somebody has been burned by its absence.

The Client ID Is a URL

A Client ID Metadata Document inverts who holds the state. The client publishes a small JSON file at a stable HTTPS URL, and that URL is the `client_id`. There is no registration call and nothing for the authorization server to store. The mechanism comes from draft-ietf-oauth-client-id-metadata-document by Aaron Parecki and Emelia Smith7, and it is the same idea Bluesky's AT Protocol has been running in production for its OAuth clients.

{
  "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"
}

The fields are ordinary RFC 7591 client metadata, the same vocabulary, published rather than posted. MCP requires at least `client_id`, `client_name`, and `redirect_uris`.8 Symmetric secrets are forbidden in the document, so `client_secret_basic`, `client_secret_post`, and `client_secret_jwt` are all out, as is any private key material. A client that needs to authenticate uses `private_key_jwt` with `jwks` or `jwks_uri`.

The rules that matter

The URL must use `https`, carry a path component, have no userinfo and no fragment, and avoid dot segments. Comparison is simple string comparison, with no port normalization and no trailing-slash forgiveness, so `https://app.example.com/client.json` and `https://app.example.com/client.json/` are two different clients.

The server side is where the spec gets specific. An authorization server MUST check that the `client_id` inside the fetched document matches the URL it fetched, exactly. It MUST validate that the document is well-formed JSON carrying the required fields, and it MUST validate the `redirect_uri` in the authorization request against the list in the document. It SHOULD fetch on encountering a URL-shaped `client_id`, and it SHOULD cache the result according to HTTP cache headers while never caching errors or malformed documents.

Support is advertised in authorization server metadata with a single flag, and that flag is what a client checks before choosing CIMD over DCR.

{ "client_id_metadata_document_supported": true }

What CIMD Actually Fixes

The write endpoint is gone, which is the whole point. Nothing is created on the authorization server, so there is nothing to spam, nothing to prune, and no quota to invent. The server makes an outbound GET and caches the answer.

Identity becomes stable and portable. One `client_id`, the same URL, works at every authorization server that supports CIMD, and there is no re-registration when a server switches its AS. An enterprise can finally write the policy it has wanted the whole time: allow `https://claude.ai/...`, deny everything else, or require review for unknown domains.

Attribution gets an owner. The name and logo on the consent screen come from a domain someone controls and someone can be held to. That is not trust, but it is a name you can block, report, or escalate, which a DCR registration never was.

Public clients also stop storing secrets. With `token_endpoint_auth_method` set to `"none"` and PKCE doing the work, there is no client secret to leak out of a desktop app and no per-server credential store to keep in sync.

What It Leaves You Holding

You just built an SSRF primitive. Your authorization server now fetches a URL supplied by an unauthenticated caller in the authorization request. The IETF draft is blunt about it: servers MUST NOT fetch a client ID URL, or any URL inside the returned document, that resolves to a special-use IP address.9 That means resolving DNS yourself, checking the address, and re-checking after every redirect, rather than handing the string to your HTTP client and hoping. `logo_uri` is the same hazard one hop later.

A domain is not a vetting process. Anyone can buy `claude-desktop-updates.example` and publish a metadata document naming itself "Claude Desktop". CIMD moves impersonation from free to roughly ten dollars. Domain allowlists, reputation checks, and warnings on first-seen clients are all MAY in the spec, so if you want trust rather than attribution, you build it yourself.

Loopback redirect URIs are still impersonatable, and the spec says so directly: metadata documents cannot prevent localhost URL impersonation on their own. A public client has no secret, so any local process can present someone else's `client_id` URL and receive the code at its own loopback port. Authorization servers SHOULD warn on `localhost`-only redirect URIs and MUST display the redirect hostname during authorization.

Your OAuth flow now has a CDN dependency. If the metadata URL 404s, the certificate expires, or an authorization server's egress filter blocks it, authorization fails everywhere at once, for every user. Error responses MUST NOT be cached, so an outage is felt immediately instead of ridden out on a stale copy.

Changes propagate at the speed of caches. Adding a redirect URI or rotating a `jwks_uri` goes live only when caches expire, and servers are advised to watch for key changes and MAY revoke tokens or force fresh consent when they see one.

Changing the URL is changing identity. Move the document and every authorization server sees a brand new client with no grants, no consent history, and no allowlist entry. Pick the URL as though it were permanent, because it is.

If You Build MCP Servers

Establish which half of the problem is yours before you write anything. An MCP server is an OAuth resource server. Registration, DCR or CIMD, happens entirely at the authorization server. If you delegate identity to a managed provider, you will not write a line of CIMD code.

If you delegate to an IdP

Your obligations are unchanged by the deprecation. Implement RFC 9728 Protected Resource Metadata, return 401 with a `WWW-Authenticate` header carrying `resource_metadata` and a scope hint, validate that every token names you in its audience, and never pass a client's token upstream.

What does change is a compatibility question you now have to put to your provider: does my authorization server advertise `client_id_metadata_document_supported`? A client that has dropped DCR cannot connect to a server whose AS only speaks DCR, and the failure will look to the user like your server being broken. Auth010, Okta11, Stytch12, WorkOS AuthKit13, and Descope14 all support it as of September 2026.

Provider         CIMD  Notes
Auth0            yes   Toggle under Settings > Advanced. Third-party
                       CIMD URLs are admin-imported and confirmed.
Okta             yes   CIMD identifies OIDC app integrations and AI
                       agents in place of an Okta-issued client ID.
Stytch           yes   Early full implementation. Runs the client.dev
                       test client.
WorkOS AuthKit   yes   Added specifically for the MCP use case.
Descope          yes

If your provider is missing from that list, and AWS Cognito and Entra ID are the two that come up most often, check its current documentation rather than assuming, and understand that a gap there caps which MCP clients can reach you at all.

Notice what Auth0 and Okta both did. Each added an admin approval step on top of the raw mechanism, which is the trust policy the spec leaves as MAY, and it is what enterprises are going to want. Expect "CIMD supported" to mean "CIMD supported, from domains we have approved."

If you run the authorization server

This is the path if you use an embedded OAuth provider, a framework's built-in AS, Cloudflare's Workers OAuth provider, or something you wrote. The work, in order:

1. Advertise "client_id_metadata_document_supported": true in AS
   metadata.
2. Detect a URL-shaped client_id and validate its form: https scheme,
   a path component, no userinfo, no fragment, no dot segments.
3. Fetch it through an SSRF-hardened client. Resolve the hostname
   yourself, reject special-use and private addresses, re-validate on
   every redirect, cap body size and timeout. Same for logo_uri
   before you render it.
4. Verify the document's client_id equals the fetched URL by exact
   string comparison.
5. Verify the required fields are present and reject symmetric-secret
   auth methods.
6. Exact-match the request's redirect_uri against the document's list.
7. Cache successes per HTTP cache headers within your own bounds, and
   never cache failures.
8. On the consent screen, show the client_id hostname and the redirect
   hostname, and warn on localhost-only redirects or first-seen
   domains.
9. Keep your registration_endpoint running.

Steps three and eight are where implementations go wrong, and the rest is parsing. Step nine is the one people will be tempted to skip: turning DCR off today breaks every client that has not migrated.

If You Build MCP Clients

Your work is smaller and more permanent. Publish one JSON file and never move it.

Choose the URL as a long-lived identifier, because it is your identity at every authorization server and it is the string in every enterprise allowlist anyone writes about you. Put it on a path on your primary domain, something like `https://app.example.com/oauth/client-metadata.json`, not a versioned path and not a bucket URL you might migrate off in a year. Version the contents. The URL is forever.

List every callback you will ever use. Authorization servers exact-match the request's `redirect_uri` against the document, so include both the `127.0.0.1` and the `localhost` forms if you use both. Loopback port handling varies, since OAuth 2.1 permits a server to ignore the port for loopback redirects and not every implementation does, so test against the providers you actually care about instead of assuming your ephemeral port gets accepted.

Treat the document as production infrastructure. Authorization fails for every user, everywhere, if that URL 404s or its certificate lapses. Monitor it and alert on it with the same seriousness you give your sign-in page, and set cache headers deliberately: long enough to absorb an outage, short enough to correct a mistake inside a day.

If customers self-host your client, consider a separate metadata URL per deployment. That lets their IdP admin allowlist one instance, which is exactly the control DCR could never offer.

If you are a confidential client, authenticate with `private_key_jwt`, and publish `jwks_uri` rather than inline `jwks` so you can rotate keys, overlapping old and new across the cache lifetime. Symmetric secrets are not allowed in the document at all.

Keep the fallback chain. The spec's priority order is pre-registered credentials first, then CIMD when the AS advertises `client_id_metadata_document_supported`, then DCR when it advertises a `registration_endpoint`, then prompting the user. Dropping DCR from your client right now cuts you off from every authorization server that has not shipped CIMD, and that is still a long list.

Everything else is unchanged: PKCE with `S256`, the `resource` parameter on both the authorization and token requests, and `iss` validation on the authorization response before you redeem the code.

The Deprecation Timeline

DCR is deprecated, not removed, and it keeps working for at least another year.

2025-03-26  MCP's first authorization spec. Clients and servers SHOULD
            support DCR, which becomes the de facto path.
2025-06-18  MCP server defined as an OAuth resource server, with RFC
            9728 discovery and RFC 8707 audience binding. DCR
            unchanged.
2025-11-25  CIMD added as a recommended registration mechanism. DCR
            still fully supported.
2026-07-28  DCR deprecated. CIMD is SHOULD, DCR drops to MAY. A
            feature lifecycle policy with a 12-month minimum window
            and a deprecated features registry arrive alongside it.
2027-07-28  Earliest removal. DCR becomes eligible for removal in the
            first revision released on or after this date.

The 2026-07-28 revision15 deprecated DCR in PR #285816, a year after CIMD arrived as a recommendation in 2025-11-25 through SEP-99117, and the lifecycle policy and deprecated features registry18 landed alongside the deprecation.

Three things about that last row deserve a careful reading. "Earliest" is not "scheduled": the registry states eligibility, and actual removal is a Core Maintainer decision taken during release preparation that may happen later. Given how many authorization servers still lack CIMD, later is the safe bet. It is also keyed to a revision rather than a calendar date, and MCP revisions do not ship on a fixed cadence, so the trigger is the first revision released on or after 2027-07-28, whenever that lands.

Deprecated already means something today. New implementations SHOULD NOT adopt DCR, and the spec page carries an explicit warning to that effect. If you are writing registration code this quarter, write CIMD.

DCR is not alone in the registry. Roots, Sampling, and Logging were deprecated in the same revision with the same earliest removal date, and the legacy HTTP+SSE transport was formally reclassified. Plan the migration work as one pass instead of three.

What To Do Now

CIMD is better than DCR and you should adopt it. It buys attribution rather than trust, it hands your authorization server an outbound fetch you have to secure properly, and nobody should be turning DCR off this year.

Client authors have the short list: publish a metadata document at a URL you are willing to keep forever, prefer CIMD when the AS advertises support, keep the DCR fallback, monitor the document like production, and test loopback redirect handling against every provider you support.

On a managed IdP, confirm your provider supports CIMD and turn it on, decide whether your trust policy is open or an admin-approved allowlist, and leave the `registration_endpoint` advertised until your client population has moved. While you are in there, re-check the RFC 9728 basics, because a surprising number of servers get audience validation wrong and never find out.

Running your own authorization server is the only genuinely new work, and it comes down to the SSRF hardening and the consent screen. The rest is validation code you can write in an afternoon.

The migration window runs to at least the first spec revision on or after 2027-07-28, and the engineering is small. The part that is not small is deciding whose metadata documents you will actually trust, and the spec hands that decision to you on purpose.

Frequently asked questions

Why is Dynamic Client Registration deprecated in MCP?

DCR requires every authorization server to expose an unauthenticated write endpoint on the public internet, which anyone can call. It produces opaque per-install client_id values that cannot be allowlisted, rate limited, or revoked across an estate, it verifies nothing in a registration so consent screens render attacker-chosen names and logos, and most enterprise identity providers decline to expose it at all. The 2026-07-28 MCP revision deprecated it and made Client ID Metadata Documents the recommended mechanism.

What is a Client ID Metadata Document?

A CIMD is a JSON file of RFC 7591-shaped client metadata that the client publishes at a stable HTTPS URL, where that URL is the client_id. Instead of the client posting a registration and the authorization server storing a row, the authorization server fetches the document, validates it, and uses it for that authorization request. Nothing is created or persisted on the server side.

When will DCR actually stop working?

DCR is deprecated, not removed. It becomes eligible for removal in the first MCP revision released on or after 2027-07-28, and removal is a Core Maintainer decision taken during release preparation that may happen later. MCP revisions do not ship on a fixed cadence, so the trigger is a revision, not a calendar date. Servers should keep registration_endpoint advertised and clients should keep the DCR fallback well past that date.

Does CIMD stop a client from impersonating another client?

No. CIMD gives attribution rather than trust. Anyone can register a lookalike domain, publish a metadata document naming itself after a well-known client, and have the consent screen render it. What changes is that the name belongs to a domain someone owns, so it can be blocked, reported, or escalated. Domain allowlists and reputation checks are optional in the spec, so trust is something each authorization server builds on top.

What is the main security risk of implementing CIMD?

Server-side request forgery. The authorization server fetches a URL supplied by an unauthenticated caller in the authorization request, and logo_uri is a second fetch one hop later. The IETF draft requires that servers not fetch URLs resolving to special-use IP addresses, which means resolving DNS in your own code, checking the address, and re-checking after every redirect, with body size and timeout caps.

Do I need to change my MCP server if I use a managed identity provider?

Your resource server obligations are unchanged: RFC 9728 Protected Resource Metadata, a 401 with WWW-Authenticate carrying resource_metadata, audience validation on every token, and no token passthrough upstream. The one new question is whether your authorization server advertises client_id_metadata_document_supported, because a client that has dropped DCR cannot reach a server whose AS only speaks DCR. Auth0, Okta, Stytch, WorkOS AuthKit, and Descope support CIMD as of September 2026.

Footnotes

  1. RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol the registration request and response, and the client metadata vocabulary both mechanisms share. ↩
  2. MCP Specification 2025-06-18: Authorization the revision that defined the MCP server as an OAuth 2.1 resource server while leaving DCR in place. ↩
  3. RFC 9728: OAuth 2.0 Protected Resource Metadata how a client discovers which authorization server protects a given MCP server. ↩
  4. RFC 8707: Resource Indicators for OAuth 2.0 the resource parameter that binds a token's audience to one server. ↩
  5. RFC 8414: OAuth 2.0 Authorization Server Metadata where registration_endpoint and client_id_metadata_document_supported are advertised. ↩
  6. Nuts and Bolts of a Modern MCP Server the resource server side of this picture, including discovery, audience binding, and why token passthrough is the mistake that keeps getting made. ↩
  7. draft-ietf-oauth-client-id-metadata-document Aaron Parecki and Emelia Smith's draft, including the URL form rules and the SSRF requirements. ↩
  8. MCP Authorization: Client Registration MCP's required fields, the validation rules, and the client's registration priority order. ↩
  9. MCP Authorization: Security Considerations the localhost impersonation limit, consent screen requirements, and the confused deputy guidance. ↩
  10. Auth0: Register applications with CIMD the toggle under Settings then Advanced, and the admin import flow for third-party metadata URLs. ↩
  11. Okta: About Client ID Metadata Documents using CIMD to identify OIDC app integrations and AI agents in place of an Okta-issued client ID. ↩
  12. Stytch: Stytch supports CIMD an early full implementation, plus the client.dev test client. ↩
  13. WorkOS: Client ID Metadata Documents and OAuth client registration for MCP AuthKit's support, added for the MCP use case. ↩
  14. Descope: Client ID Metadata Documents Descope's CIMD support. ↩
  15. MCP Specification 2026-07-28: Changelog the revision that deprecated DCR and made CIMD a SHOULD. ↩
  16. modelcontextprotocol PR #2858 the change that deprecated Dynamic Client Registration. ↩
  17. SEP-991: Client ID Metadata Documents the proposal that added CIMD as a recommended mechanism in the 2025-11-25 revision. ↩
  18. MCP deprecated features registry the 12-month minimum window, the 2027-07-28 earliest removal date, and the other features deprecated in the same revision. ↩

Conversation

…
    Log in to join the conversation.

    © 2026 ABWaters. Thinking out loud.