All Articles
CategoryAI
Reading Time
15 min read
Published
2026-09-09
Word Count
3,611words

Grab a coffee — this one is a deep dive!

MCP Security 2026: CIMD, RFC 9207, and the Exit from DCR

Summary

MCP's 2026-07-28 spec hardened authorization: RFC 9207 issuer validation, unregistered client identity via CIMD, and DCR's move into deprecation — a step-by-step guide to updating your own server.

  • MCP's 2026-07-28 spec moved from stateful to a stateless request/response model; initialize/initialized and Mcp-Session-Id were removed.
  • MCP clients using OAuth must now perform RFC 9207 issuer (iss) validation before exchanging the code (SEP-2468, against mix-up attacks).
  • Dynamic Client Registration (DCR) is officially deprecated; Client ID Metadata Documents (CIMD) is the recommended new path, with DCR kept for backward compatibility for at least 12 months.
  • Client-side priority order: pre-registered client → CIMD (if the AS supports it) → DCR fallback → manual entry; servers must also implement RFC 9728 discovery and least-privilege scope rules.
MCP Security 2026: CIMD, RFC 9207, and the Exit from DCR

MCP is no longer a protocol that holds a persistent connection and session state; the 2026-07-28 specification moved it to a stateless request/response model, and that shift also changed how you harden your server's authorization layer. RFC 9207 issuer validation, Client ID Metadata Documents (CIMD), and the retirement of Dynamic Client Registration (DCR) are all part of the same spec revision, and reading them in isolation leaves gaps. As someone writing an MCP server or client, you'll see step by step which MUST/SHOULD rule you need to follow today, and which one is just a transition-period fallback.

💡 Pro Tip: Authorization in MCP is still OPTIONAL at the protocol level — but the moment you decide to use OAuth 2.1 on your HTTP-based server, every MUST rule in this piece becomes binding for you.

Table of Contents

How the threat model changed in stateless MCP

The MCP team laid out the protocol's direction clearly in the 2026-07-28 announcement: "MCP is transforming from a bidirectional stateful protocol into a request/response stateless protocol." In practice, that means the initialize/initialized handshake and the Mcp-Session-Id header are officially retired (SEP-2575, SEP-2567). Now every request carries its own protocol version, client identity, and capabilities in the _meta field.

Here's what that means for security: any request can land on any server instance behind a plain round-robin load balancer, with no shared storage layer. So server-side session state is no longer a secret tucked away in the transport layer — authentication has to happen on every request, using that request's own context.

Another change: Streamable HTTP requests must now carry Mcp-Method and Mcp-Name headers (SEP-2243). This lets your gateway or WAF make routing/metric/rate-limit decisions without parsing the JSON body — but it also means header spoofing is a new attack surface.

http
1POST /mcp HTTP/1.1
2Host: api.example.com
3Mcp-Method: tools/call
4Mcp-Name: search_documents
5Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
6Content-Type: application/json
7 
8{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"search_documents","arguments":{"query":"invoice"}}}

Before trusting these two headers at the gateway level, you absolutely need to validate them together with the token in the Authorization header — otherwise a client could alter the Mcp-Name value to try to bypass rate-limit or authorization rules.

Don't ignore the scale of this transition: the MCP team shared that Tier-1 SDKs reach roughly half a billion downloads a month, with the TypeScript and Python SDKs each past 1 billion total downloads. Moving a protocol this large from "stateful" to "stateless" explains the care around backward compatibility — four Tier-1 SDKs (TypeScript, Python, Go, C#) speak the 2026-07-28 spec as of announcement day, while the Rust SDK offers beta support. Before adding header/iss validation, check whether your SDK has moved to this spec version first.

RFC 9207: issuer validation and mix-up attacks

MCP's "one client, many servers" deployment shape is particularly exposed to a classic OAuth "mix-up" attack: if a client talks to more than one authorization server, a malicious server can impersonate another server and steal the authorization code. The spec closed this gap with SEP-2468: "Authorization servers should return the iss parameter per RFC 9207, and clients must validate it before redeeming a code."

There's an important distinction here — authorization itself is still OPTIONAL in MCP, so not every implementation has to use OAuth. But in implementations that do, RFC 9207 issuer validation is now a MUST-level client requirement: "mandatory for MCP clients that use OAuth," not "mandatory everywhere in MCP."

In practice, this means the client has to record which issuer it's redirecting to before the redirect, and then compare the returned iss parameter against that record. But rejecting outright whenever iss is absent goes against the spec: servers that send iss MUST announce it in metadata by setting authorization_response_iss_parameter_supported to true, and the client's behavior depends on that flag:

AS flag
iss in response
Client behavior
true
present
Compare with recorded issuer
true
absent
Reject the response
false or unset
present
Compare with recorded issuer
false or unset
absent
Proceed

So whenever iss is present, it's always compared, regardless of the flag:

typescript
1// SEP-2468: behavior depends on the AS flag; if it passes, returns the code, exchange belongs to the caller
2function validateAuthorizationResponse(
3 params: URLSearchParams,
4 recordedIssuer: string,
5 issParameterSupported: boolean | undefined,
6): string {
7 const returnedIssuer = params.get("iss");
8 const code = params.get("code");
9 
10 if (returnedIssuer === null) {
11 // Absence of iss is only a rejection reason if the server declared support for it
12 if (issParameterSupported === true) throw new Error("Missing iss");
13 } else if (returnedIssuer !== recordedIssuer) {
14 // RFC 9207: issuer mismatch = possible mix-up attack
15 throw new Error(`Issuer mismatch: got ${returnedIssuer}`);
16 }
17 
18 if (code === null) throw new Error("Missing code");
19 return code;
20}

The SEP-2468 proposal was opened on 2026-03-25 and merged on 2026-05-17; conformance scenarios don't live in the spec repo but in a separate modelcontextprotocol/conformance repo, where five scenarios were added on 2026-05-19 via #220 (auth/iss-supported, auth/iss-not-advertised, auth/iss-supported-missing, auth/iss-wrong-issuer, auth/iss-unexpected); the auth/iss-normalized scenario followed on 2026-05-22 via #304 — a testable conformance criterion.

DCR's problems

Dynamic Client Registration (RFC 7591) was the method by which an MCP client sent a registration request to the authorization server at runtime and got back a client_id/client_secret pair. The 2026-07-28 spec officially deprecated it in favor of CIMD, defining DCR as no longer the primary mechanism but an option kept for backward compatibility.

There's a concrete pain point behind this. If clients registering via DCR don't specify application_type, the OIDC default is "web" — which causes the authorization server to reject localhost or native redirect URIs. The "invalid redirect_uri" errors you hit in CLI tools and desktop MCP clients are most often caused by this default — in the announcement's own words: "this is likely why." The spec ties this to a MUST: MCP clients MUST specify an appropriate application_type during DCR (SEP-837).

The second problem is credential portability. With SEP-2352, client credentials are now strictly bound to their issuer — you can't reuse a client_id/client_secret pair on another server. In multi-server MCP deployments, that means separate registration state for each AS.

The timeline isn't left to guesswork: the spec's "Deprecated Features" registry lists the earliest removal for DCR as "First revision released on or after 2027-07-28," with the deprecation recorded as PR #2858. This isn't a removal date but the threshold at which removal becomes eligible; the actual removal is decided by the Core Maintainer during release prep and can come later. The policy isn't specific to DCR — it also covers Roots/Sampling/Logging and the legacy HTTP+SSE transport.

The practical upshot: you don't have to rip out your existing DCR integration overnight. But for a new MCP client or server, choosing DCR as the primary path now goes against the spec — the gap between "it works" and "the path the spec recommends" turns into maintenance debt once the 12-month window closes.

Client identity with CIMD

Client ID Metadata Documents (CIMD) solve the "no prior relationship" problem DCR was built for, without a registration request. The idea: client_id is no longer a random string — it's directly an HTTPS URL pointing to a JSON metadata document with fields like client_id, client_name, and redirect_uris.

The spec asks MCP clients and authorization servers to support CIMD at the SHOULD level — based on the draft-ietf-oauth-client-id-metadata-document-00 draft (not yet an RFC, still at the IETF draft stage). The client_id URL must use the HTTPS scheme and include a path component, for example https://example.com/client.json.

json
1{
2 "client_id": "https://mcp-client.example.com/oauth/client.json",
3 "client_name": "Example MCP Client",
4 "redirect_uris": ["https://mcp-client.example.com/oauth/callback"],
5 "token_endpoint_auth_method": "none",
6 "grant_types": ["authorization_code"],
7 "response_types": ["code"]
8}

On the authorization server side, the normative levels differ: seeing a URL-shaped client_id, it's expected to fetch the document (SHOULD), then run two mandatory checks (MUST): the document's client_id field must match the URL exactly, and the client's redirect URIs must be checked against the document's list. Skip either check and CIMD isn't any safer than DCR — it only removes the registration step, not the validation responsibility.

The server has to add the client_id_metadata_document_supported field to its OAuth Authorization Server Metadata to announce this support; the client checks that field to decide whether it can use CIMD.

The risks of Client ID Metadata Documents

CIMD removes the registration step but adds a new responsibility: the AS now has to fetch a client-supplied URL at runtime. The spec doesn't leave this to chance — servers implementing CIMD MUST account for the security considerations in section 6 of the OAuth Client ID Metadata Document draft; the MCP spec calls out the ones it considers most important in its own security section.

The first is SSRF. A client_id URL is, in practice, a way of telling the authorization server "go fetch this address"; an attacker can point it at an internal service or a server-only endpoint. The spec ties this to a SHOULD-level warning: servers fetching metadata documents should account for SSRF risk. If you're writing your own AS, that means: don't fetch with the general-purpose app HTTP client — use a scheme/port allowlist, a redirect cap, a response-size and timeout ceiling, and, where possible, a separate egress path.

The second is subtler, from CIMD's own design. Requiring the document's client_id to match the URL exactly stops someone from copying your metadata and hosting it elsewhere — but it doesn't stop them from presenting your URL to an AS and acting as your client. The spec spells it out: CIMD alone cannot prevent localhost URL impersonation. A local callback address doesn't prove who's listening on that port; the document looks legitimate, the name looks legitimate.

That's why the spec hands the rest of the burden to the AS consent screen: an extra warning should show for clients carrying only a localhost redirect URI (SHOULD), extra attestation may be requested (MAY), and the redirect URI's hostname MUST be shown clearly to the user (MUST). Authorization servers may also enforce trust policies on which domains' metadata they'll accept (MAY) — in an enterprise deployment, restricting CIMD to your own domains is the cheapest, most effective version of this.

The lesson here: it's wrong to read CIMD as "the effort-free version of DCR." You deleted the registration database, but in exchange you took on the responsibility of hardening the fetch layer and setting up the consent screen correctly. Skip either of those two items and CIMD stops being a safer path than DCR.

Transition period: supporting both at once

An MCP client you write for production today will talk to servers that support CIMD as well as servers still stuck on DCR. The spec defines a clear priority order for this — try things in this order on the client side:

Order
Method
When it's used
1
Pre-registered client info
When a static relationship already exists between client and server
2
CIMD
When the authorization server announces support via client_id_metadata_document_supported
3
DCR (fallback)
When CIMD isn't supported, for backward compatibility
4
Manual entry
When none of the above work, ask the user for client info

To summarize the difference between DCR and CIMD:

Feature
DCR (RFC 7591)
CIMD (draft-ietf-oauth-client-id-metadata-document-00)
client_id format
Random string generated by the server
HTTPS URL hosted by the client
Registration step
POST request to the server at runtime
None — the server fetches and validates the URL
State keeping
Requires a registration database server-side
Stateless — metadata is fetched every time
Spec status (2026-07-28)
Deprecated, MAY for backward compatibility
Recommended path, SHOULD
Cross-server reuse
Forbidden (SEP-2352, bound to issuer)
Portable — no re-registration needed when the AS changes

Practical advice: for a new MCP client, make CIMD primary and fall back to DCR only if the server doesn't announce support — don't treat them as "equal priority"; follow the spec's order exactly.

Token scope and least privilege

Resolving client identity with CIMD or DCR isn't enough on its own — which scopes end up on the token is a separate decision. The spec puts a clear responsibility on the 401 response here: MCP servers should provide a scope parameter in the WWW-Authenticate header (SHOULD), and clients must treat those scopes as authorized for that request (MUST).

http
1HTTP/1.1 401 Unauthorized
2WWW-Authenticate: Bearer realm="mcp-api",
3 resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
4 scope="tools:search_documents tools:read_only"

The spec's priority order has two steps: if a scope is given in the WWW-Authenticate challenge, use that; otherwise request all of the scopes_supported in the Protected Resource Metadata document, and if that's undefined too, send no scope at all. Note: this list lives in the protected resource's metadata, not the authorization server's.

So the least-privilege mechanism isn't the client filtering the list: the spec defines scopes_supported as the minimal set needed for basic functionality, with additional scopes requested incrementally through a step-up authorization flow. The narrowing responsibility sits with the server.

This distinction matters especially for MCP servers offering multiple tools: a request calling a file-search tool shouldn't be authorized with a token that carries file-delete scope — if that token leaks, the damage stays confined to the scope tied to it.

A checklist for hardening your own server

Instead of applying the rules above one by one, you can check the following items in order when reviewing your own MCP server:

  • RFC 9728 Protected Resource Metadata: MCP servers MUST implement this; the authorization_servers field must contain at least one server.
  • Dual discovery path: The server must provide either a resource_metadata URL in the WWW-Authenticate header OR the /.well-known/oauth-protected-resource well-known URI; the client must support both.
  • Separate state per AS: Pre-registered and stored DCR credentials can't be reused across servers (SEP-2352) — CIMD client_id URLs are exempt from this rule and are portable.
  • iss validation: If you use OAuth, compare the iss parameter in the authorization response against the expected issuer, and reject the code on a mismatch (RFC 9207, SEP-2468).
  • CIMD priority: Announce the client_id_metadata_document_supported field, and keep DCR only as a fallback.
  • Header validation: Before trusting Mcp-Method/Mcp-Name headers at the gateway level, cross-check them against the scope in the Authorization token.
  • Scope challenge: Provide a scope parameter inside WWW-Authenticate on 401 responses; let the client request with least privilege.
  • SDK version: The TypeScript, Python, Go, and C# Tier-1 SDKs speak the 2026-07-28 spec; the Rust SDK offers beta support — whichever SDK your server uses, follow the migration notes from there.

This isn't a one-time audit — the spec's deprecation policy (12 months minimum) allows gradual change, so you can harden things in measurable increments too.

The checklist order isn't random either: if discovery (RFC 9728 + the dual path) doesn't work first, the client never reaches the right authorization server — and nothing later, like iss validation or CIMD prioritization, matters. Always start hardening at the discovery layer, then work toward the authentication details.

GOLDEN TIP

The most valuable insight in this article

This tip holds the article's most important takeaway.

Easter Egg

You found a hidden gem!

There's a hidden detail in this section. Want to uncover it?

Reader Reward

Rather than trying to apply every MUST/SHOULD rule covered in this article all at once, working through them in the order below gives you a transition that's both less fragile and more testable. Each item is an independent checkpoint you shouldn't start before the previous one is done.

FAQ

How do you securely authorize an MCP server?

Authorization is OPTIONAL at the MCP protocol level, but an HTTP-based server using OAuth 2.1 must follow the 2026-07-28 spec's mandatory skeleton: the server MUST implement RFC 9728 Protected Resource Metadata, and the client MUST discover the authorization server via RFC 8414/OIDC Discovery — a spec-level integration path behind enterprise identity providers.

What is CIMD, and how does it differ from DCR?

In CIMD, the client_id is itself an HTTPS URL; the JSON metadata file it hosts carries the client's identity, and the server fetches and validates it at runtime with no registration database. DCR instead sends a live registration request and gets back a client_id/secret; in the 2026-07-28 spec, it's downgraded to "MAY support" — kept for backward compatibility.

Why did RFC 9207 issuer validation become mandatory?

MCP's one-client-many-servers shape is particularly exposed to a mix-up attack where one AS impersonates another. SEP-2468 made it mandatory to compare the issuer recorded before the redirect against the response's iss parameter, and reject the code on a mismatch.

How is every request validated in stateless MCP?

The initialize/initialized handshake and Mcp-Session-Id are gone; every request carries its own protocol version, client identity, and capabilities in the _meta field, routed and authorized at the gateway level via the Mcp-Method and Mcp-Name HTTP headers. Anything session-like is no longer hidden in the transport — it comes back as an application-level handle (e.g., a basket or session identifier) passed to the model as an argument.

When will DCR be fully removed?

The spec's "Deprecated Features" registry lists the earliest removal for DCR as "First revision released on or after 2027-07-28" (PR #2858) — a threshold for eligibility, not a removal date; the Core Maintainer can push it later. In practice, assume DCR keeps working until 2027-07-28 and spread your migration across that window.

Conclusion

MCP's 2026-07-28 spec revision makes the protocol both more scalable (stateless, requests landing on any server instance) and clearer on authorization: RFC 9207 issuer validation is a MUST for OAuth clients, CIMD is the recommended (SHOULD) path for clients without a pre-existing relationship, and DCR is the backward-compatibility fallback. Handling these three through the spec's priority order (pre-registered → CIMD → DCR → manual) at one decision point keeps both security and maintenance cost under control during the transition.

If you want to look deeper into the rest of the MCP ecosystem, Claude Code MCP: AI Plugin Ecosystem with Model Context Protocol covers the protocol's plugin side, while MCP (Model Context Protocol): AI Integration Standard covers the general integration model. If you're coordinating multiple agents on the server side, take a look at Claude Code Multi-Agent Teams: Building with Parallel AI Agents, and if you want to look at tool-use and planner-loop architecture, check out Agentic AI: Tool Use, Planner Loops, and Production Agent Architecture. You can also compare general API/network hardening practices with iOS Network Security Advanced: Armored Communication from Scratch.

Sources

Tags

#MCP#OAuth 2.1#RFC 9207#CIMD#DCR#API Security#Model Context Protocol
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

iOS Development News

Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.

We respect your privacy. You can unsubscribe at any time.

Share