CIMD vs Dynamic Client Registration for AI Agents
How does an AI agent you've never seen become an OAuth client? Two answers — Dynamic Client Registration (RFC 7591) and Client ID Metadata Documents — and why MCP picked CIMD.
You're running an OAuth authorization server, and an AI agent nobody pre-registered shows up wanting a token. In the "Sign in with Google" world this never happens — every client is registered by hand, once, in a developer console. But the agent ecosystem doesn't work that way: there are thousands of MCP clients, they update constantly, and no human is going to file a registration ticket for each one. So how does an agent you've never seen get a client_id?
There are two answers, and the MCP authorization spec recently switched which one it recommends. The old answer is Dynamic Client Registration (RFC 7591) — you run a /register endpoint and hand out client IDs on demand. The new answer is Client ID Metadata Documents (CIMD) — the agent's client_id is just an HTTPS URL you can dereference. The difference looks small and isn't: one makes you run a stateful registration service with an abuse problem, the other makes the client_id self-describing and lets you skip the endpoint entirely. Here's the comparison, and how to support CIMD without opening an SSRF hole.
The problem: registration doesn't scale to agents
A client_id is how an authorization server recognizes the application asking for a token. Traditionally it's minted once: a developer registers their app, gets an ID, and reuses it forever. That model assumes a small, known set of clients onboarded by humans.
Agents break both assumptions. The set is large and open-ended, and no human is in the loop. You need a way for a client to become known at first contact, automatically, without you trusting it more than a public client deserves.
Option 1 — Dynamic Client Registration (RFC 7591)
RFC 7591 defines a /register endpoint. A client POSTs its metadata — name, redirect URIs, grant types — and gets back a freshly minted client_id (and optionally a secret):
POST /register HTTP/1.1
Content-Type: application/json
{
"client_name": "Example Agent",
"redirect_uris": ["https://agent.example.com/callback"],
"grant_types": ["authorization_code"],
"token_endpoint_auth_method": "none"
}HTTP/1.1 201 Created
{ "client_id": "namoid_client_live_9fK2…", "redirect_uris": ["https://agent.example.com/callback"] }It works, but look at what you now own. The endpoint writes a row to your database for every caller — and the caller is unauthenticated by definition (the whole point is onboarding strangers). That's an open write endpoint, which means:
- Abuse surface. Anyone can spray registrations; you need rate limits, quotas, and probably gating, or your client table fills with junk.
- State you have to manage. Every agent that ever connected leaves a record. Duplicates, staleness, and "which of these 40 registrations is the real one?" become your problem.
- No live source of truth. Once registered, the metadata is a snapshot. If the agent changes its redirect URI, it re-registers and you have two records.
DCR isn't wrong — it's just a registration service, with all the operational weight that implies.
Option 2 — Client ID Metadata Documents (CIMD)
CIMD (draft-ietf-oauth-client-id-metadata-document) makes one move: the client_id is an HTTPS URL, and that URL serves the client's metadata. There's no registration call. The agent just uses its URL as the client_id in a normal authorization request:
GET /authorize?response_type=code
&client_id=https://agent.example.com/client.json
&redirect_uri=https://agent.example.com/callback
&code_challenge=…&code_challenge_method=S256
The first time your server sees that client_id, it fetches the document:
// GET https://agent.example.com/client.json
{
"client_id": "https://agent.example.com/client.json",
"client_name": "Example Agent",
"redirect_uris": ["https://agent.example.com/callback"]
}The client_id inside the document must equal the URL it was fetched from — that self-reference is the trust anchor. HTTPS proves the document came from whoever controls that origin; the matching client_id proves the document is claiming itself, not impersonating another client. No secret is issued (CIMD clients are public clients, so PKCE does the authenticating). The agent's metadata lives at its own URL, so it's always current — change the redirect URI in the document and the next fetch sees it.
Head to head
| Dynamic Client Registration | CIMD | |
|---|---|---|
| Onboarding | POST to /register first | use the URL as client_id directly |
| Endpoint to run | yes (/register, write path) | none |
| Per-agent DB rows | one per registration | optional (JIT cache only) |
| Abuse surface | open write endpoint | a guarded GET fetch |
| Source of truth | your snapshot | the client's live URL |
| Client secret | optional | none (public + PKCE) |
| New risk to manage | registration spam | SSRF on the fetch |
The trade isn't "more secure vs less" — it's what kind of work you take on. DCR gives you a stateful service to defend; CIMD gives you a single outbound fetch to defend.
Why MCP moved to CIMD
The 2025-11-25 MCP authorization revision made DCR optional and pointed implementations at CIMD instead. The reasoning maps exactly to the table: in an ecosystem of thousands of agents, making every authorization server run an abuse-prone registration endpoint was the wrong default. A dereferenceable client_id lets any agent present a verifiable identity with zero pre-provisioning, and lets the authorization server stay stateless about who it has "met" before. For agent identity — open-ended, machine-driven, constantly changing — that's the better fit.
Update: DCR is now deprecated, not just optional
Since this post was first published the spec has gone further. The current MCP authorization specification states that authorization servers and clients SHOULD support Client ID Metadata Documents, and that they MAY support RFC 7591 — with an explicit note that "Dynamic Client Registration is deprecated and retained for backwards compatibility with authorization servers that do not support Client ID Metadata Documents."
That's a meaningful escalation. Optional meant "pick either." Deprecated means CIMD is the path forward and DCR is a compatibility shim. If you're choosing today, choose CIMD; if you already run a DCR endpoint, keep it for the servers that haven't migrated, but don't build new behaviour on it.
Supporting CIMD without opening an SSRF hole
The one new risk CIMD introduces is real: your server now makes an outbound HTTP request to a URL an attacker controls. Done naively, that's a server-side request forgery primitive — point the client_id at http://169.254.169.254/… and your server fetches cloud metadata. So the fetch must be guarded. The checks that matter:
- Scheme + host allow/deny. HTTPS only. Resolve the host and refuse private, loopback, and link-local ranges (RFC 1918,
127.0.0.0/8,169.254.0.0/16,::1). This is the load-bearing check. - No redirects. Don't follow them — a public URL that 302s to
http://169.254.169.254defeats a naive allowlist. Fetch exactly the URL or fail. - Size + time caps. Read at most a few KB with a short timeout; a metadata document is tiny, and an unbounded read is a DoS.
- Exact
client_idmatch. Reject the document unless itsclient_idequals the URL you fetched. - Register as a public client. No secret; require PKCE. Cache the result so you fetch once, not on every request.
That's how NamoID resolves CIMD client_id URLs — an SSRF-guarded, no-redirect, size-capped fetch, an exact URL match, then a just-in-time public-client registration scoped to the environment that authorized it. The agent shows up with a URL; we verify it and move on. (For the resource-server side of the same flow, see securing an MCP server.)
When DCR still makes sense
Deprecated isn't removed, and RFC 7591 remains a published standard outside MCP. DCR is still the right tool when a client genuinely needs a confidential registration (a real secret, server-to-server), when you want to issue and control the credential rather than dereference the client's, or when you're integrating with an existing ecosystem that already speaks RFC 7591. For the agent case — public clients, open set, no human onboarding — CIMD wins, and the spec now says so directly. Expect to support both for a while: CIMD for anything new, DCR for the long tail that hasn't moved.
The shift from DCR to CIMD is small in spec terms and large in operational terms: it turns "run a registration service and defend it" into "dereference a URL and verify it." If you're building an authorization server for the agent era, how MCP authorization works covers the full flow, and NamoID ships CIMD resolution plus audience-bound tokens so the agents your customers have never seen can still onboard safely.