Microsoft Entra ID publishes no RFC 7591 registration endpoint, and its v2.0 discovery document carries neither registration_endpoint nor client_id_metadata_document_supported, so an MCP client that registers itself against a test identity provider has nothing to call against Entra. The MCP 2026-07-28 specification already deprecated Dynamic Client Registration and gives clients a four-step order: pre-registered credentials, then Client ID Metadata Documents, then DCR, then prompting the user. On Entra the first and last steps are all that remain, which means one app registration or one authorized client ID per approved client. Server-side, set api.requestedAccessTokenVersion to 2 before the HTTPS Application ID URI, keep the resource value free of a trailing slash to avoid AADSTS9010010, and list the v2.0 issuer in Protected Resource Metadata. Self-hosted Keycloak 26.8.1 supports CIMD only as an experimental feature behind --features=cimd, and its four-part SSRF and self-provisioning issue #53812 was fixed on main on 13 October 2026 but is not in any release. A DCR facade in front of Entra is allowed only with a per-client consent page, and it moves the client inventory out of Entra into a store you must audit. Start by running the three-field metadata check against your production issuer.
If you searched for "dynamic client registration not supported" after pointing an MCP client at your corporate tenant, neither the client nor your server is broken. Microsoft Entra ID publishes no RFC 7591 registration endpoint. Its v2.0 discovery document has no registration_endpoint key, so an MCP client that registered itself against a test identity provider without complaint has nothing to call once the authorization server is Entra. Microsoft says so in its Microsoft 365 Copilot documentation: DCR is not available for an MCP server protected by Entra ID, so register the OAuth client statically.
The MCP 2026-07-28 specification had already moved on. It deprecated Dynamic Client Registration and gave clients a four-step order: pre-registered credentials first, then Client ID Metadata Documents (CIMD), then DCR, then asking the user to type client details in. Entra advertises neither DCR nor CIMD, so on Entra that order collapses to its first step. The real decision is not how to make DCR work against Entra. It is whether you pre-register each client, or put a facade in front of Entra that fakes a registration endpoint.
That facade is the configuration MCP's own security guidance lists as the precondition for a confused-deputy attack. It is allowed, with a per-client consent page, but it moves the record of which application holds a token out of Entra and into a store you now have to audit. For a bank, insurer or hospital whose access review reads Entra, that cost decides the answer. Self-hosted Keycloak is where CIMD becomes a real option, and it comes with a public hardening issue that no release had fixed on the date of this post.
Why MCP dynamic client registration fails on Entra ID
Start with the discovery document your MCP client reads. Entra's v2.0 OpenID configuration publishes authorization_endpoint, token_endpoint, device_authorization_endpoint, jwks_uri, token_endpoint_auth_methods_supported and an issuer of the form https://login.microsoftonline.com/<tenant-id>/v2.0. Three keys are absent: registration_endpoint, client_id_metadata_document_supported and code_challenge_methods_supported. The RFC 8414 oauth-authorization-server well-known paths return 404, and so does the path-inserted OpenID variant; only /v2.0/.well-known/openid-configuration answers. The MCP spec requires clients to try three well-known URLs in order, ending with that one, so discovery succeeds. Registration has nowhere to go.
Microsoft's Copilot page spells out the rest. Entra "doesn't publish an RFC 7591 registration endpoint, so there's nothing for the Enterprise token store to register a client against." Copilot does support DCR for MCP plugins against other authorization servers, with three conditions: DCR without a client secret is not supported yet, so the authorization server must issue one at registration; PKCE is on by default; and the Teams developer portal does not support DCR yet. None of that applies to Entra, where the instruction is static registration, full stop.
VS Code users met the same wall from the other side. Issue #257415 in the VS Code repository asked for a way to skip DCR and supply static client information, quoting the failure "Dynamic registration failed: Registration to ... failed." It collected 47 comments between July 2025 and May 2026 and closed at milestone 1.122.0, after a static oauth.clientId setting had landed in 1.116.
Microsoft's September 2026 how-to for securing an MCP server with Entra covers the server side and does not say how an interactive third-party client obtains a client_id. Its client section is about Entra Agent ID for autonomous agents, a different problem covered in OAuth and identity for autonomous agents.
The client registration order in the MCP 2026-07-28 spec
The 2026-07-28 revision names three registration mechanisms and tells clients that support all of them to try four steps in order. We cover the rest of that revision in the MCP stateless spec migration guide; only the registration rule matters here.
The spec's warning on step 3 is blunt: "Dynamic Client Registration is deprecated. New implementations should use Client ID Metadata Documents instead. This option remains available for backwards compatibility with authorization servers that do not support Client ID Metadata Documents." DCR is now a MAY. Pre-registration gets a SHOULD in the other direction: MCP clients should support static client credentials, either hardcoded for a known authorization server or through a UI where the user enters details after registering an OAuth client themselves.
One binding rule shapes how you roll this out. Pre-registered and persisted DCR credentials must be keyed by the authorization server's issuer, and a client must not reuse credentials from a different authorization server: it registers again. CIMD client IDs, by contrast, are portable across authorization servers. If you run separate Entra tenants for production and pre-production, plan one registration per tenant per client, and expect clients to treat them as unrelated.
| Step | Client uses | Condition | Microsoft Entra ID | Keycloak 26.8.1 |
|---|---|---|---|---|
| 1 | Pre-registered client information | Available for this authorization server | Yes, the only automatic path | Yes |
| 2 | Client ID Metadata Document | AS metadata has client_id_metadata_document_supported | Not advertised | Only with --features=cimd |
| 3 | Dynamic Client Registration | AS metadata has registration_endpoint | Not published | Always published |
| 4 | Prompt the user for client information | No other option available | Manual pre-registration | Not needed |
How to check if your IdP supports MCP client registration
Before choosing a path, read three fields from the issuer your MCP server names in its Protected Resource Metadata. They decide whether steps 2 and 3 exist and whether a conforming client starts at all.
# Entra ID (v2.0 issuer). All three print null.
curl -s https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration \
| jq '{registration_endpoint, client_id_metadata_document_supported, code_challenge_methods_supported}'
# Any other IdP: use the issuer listed in your MCP server's PRM authorization_servers
curl -s https://idp.internal.example/realms/<realm>/.well-known/openid-configuration \
| jq '{registration_endpoint, client_id_metadata_document_supported, code_challenge_methods_supported}'On Entra, all three come back null. On Keycloak 26.8.1, registration_endpoint is always published, code_challenge_methods_supported lists plain and S256, and client_id_metadata_document_supported is true only when the server runs with the CIMD feature enabled. That flag is realm metadata: it appears whether or not you have created the client policy that makes CIMD actually work. For Okta, or anything else, run the same command and trust the output.
The third field deserves a closer look. The 2026-07-28 security considerations say that if code_challenge_methods_supported is absent, the authorization server does not support PKCE and MCP clients MUST refuse to proceed; for OpenID discovery, clients must verify the field is present. Entra omits it. A generic client that follows that rule literally will stop at Entra before registration ever comes up. The clients Microsoft documents against Entra connect through Microsoft's own authentication integration (VS Code's ships with its own default client ID), so test any other client against your tenant before promising it to a team.
PKCE is also not a registration property: none of the 15 client metadata fields in RFC 7591 carries a code challenge method. It is enforced per authorization request, through code_challenge_method=S256 on each authorize call.
Path 1: pre-register each MCP client on Entra ID
Pre-registration has two halves: the MCP server's own app registration, and the client side, where the registration question gets answered.
{
"api": {
"requestedAccessTokenVersion": 2
}
}{
"identifierUris": [
"https://mcp.contoso.com",
"https://mcp.contoso.com/mcp"
]
}{
"resource": "https://mcp.contoso.com",
"authorization_servers": [
"https://login.microsoftonline.com/<tenant-id>/v2.0"
],
"scopes_supported": ["tool.read", "tool.execute"],
"bearer_methods_supported": ["header"]
}{
"servers": {
"contoso-mcp": {
"type": "http",
"url": "https://mcp.contoso.com",
"oauth": {
"clientId": "<your-entra-public-client-app-id>"
}
}
}
}The server app registration: v2 tokens before the Application ID URI
Entra matches the client's RFC 8707 resource value against your server app's Application ID URI and, on a match, issues a token whose aud is your MCP server. Order matters. Microsoft's guide states that you must enable v2 access tokens before you can set an HTTPS Application ID URI that matches your server URL. These are two saves of the manifest, in this order, not one paste. First save: Second save, once the first has been accepted: The list holds every URL a client might use to reach the server, with no trailing slash on any of them. Then expose delegated scopes. Microsoft's example uses tool.read and tool.execute, with consent set to "Admins only" or "Admins and users"; the fully qualified form a client requests is https://mcp.contoso.com/tool.execute. Creating this registration requires at least the Application Developer role.
Protected Resource Metadata with the v2.0 issuer
MCP servers must implement RFC 9728 Protected Resource Metadata and advertise it through resource_metadata in the WWW-Authenticate header on a 401, or at the well-known URI. Serve it at https://mcp.contoso.com/.well-known/oauth-protected-resource: resource must be identical to an Application ID URI. authorization_servers must be the v2.0 issuer; the v1 issuer https://sts.windows.net/<tenant-id>/ does not line up. Use organizations or common in place of the tenant ID only for a multitenant server. On the validation side, Microsoft's minimum is signature, iss, tid, aud equal to your server's application, expiry, then authorization on oid or sub through roles, and scp for delegated tokens. The rest of the server checklist is in our MCP server security hardening checklist.
Authorizing a known client, or registering your own
For VS Code you have two choices. VS Code's built-in Microsoft authentication uses a default client ID, aebc6443-996d-45c2-90f0-388ff96faa56, unless a different one is passed. Microsoft's App Service MCP guide adds that ID under Expose an API, Authorized client applications, with the user_impersonation scope, and in App Service's own allowed client applications list. That guide uses an api:// Application ID URI and an App Service app setting for PRM, so treat it as the App Service variant of the pattern, not a copy-paste for a server you host elsewhere. The alternative is your own public client app registration in Entra, handed to VS Code in mcp.json. The oauth object on http and sse server entries accepts clientId from VS Code 1.116, and it is present in 1.142.0: No secret goes in the file. A public client needs none; if a confidential client is unavoidable, VS Code sets the secret through a "Set Client Secret" code lens and keeps it in the operating system's secret store, a change that shipped with 1.122. Register the redirect URI your client documents for this setup on the Entra app; we do not print one here because it depends on the client. For Microsoft 365 Copilot the instruction is static OAuth registration. For other interactive clients, check whether the client exposes a static client ID setting. The spec says it should; if it does not, that client cannot reach an Entra-protected server without a facade.
What the access review gets
Pre-registration gives the reviewer an inventory held by the IdP itself: one registration or one authorized client ID per approved MCP client, changed through the same process as any other enterprise application. Entra's sign-in logs attribute each token to that application. When a client is retired, you remove one registration or one authorized client ID. No other path in this post leaves the evidence where the auditor already looks.
MCP on Entra ID errors: AADSTS9010010 and how to fix it
Six failures cover the configuration problems that Microsoft's MCP and App Service guides document. AADSTS9010010 alone has three causes, so read the cause column before you change anything.
The use_guid route is for apps stuck on v1 tokens: the aud optional claim with additionalProperties set to ["use_guid"] relaxes the Application ID URI restriction, and your server must then expect a GUID as audience.
| Symptom | Cause | Fix |
|---|---|---|
| AADSTS9010010 | resource differs from the Application ID URI, for example a trailing slash (https://mcp.contoso.com/ vs https://mcp.contoso.com) | Send the exact URI; add every URL clients use to identifierUris |
| AADSTS9010010 | Requested scope belongs to a different app registration than the resource | Request the scope from the same app as the resource |
| AADSTS9010010, or the HTTPS Application ID URI cannot be set | App registration still issues v1 access tokens | Set api.requestedAccessTokenVersion to 2, save, then set the URI |
| Discovery or issuer checks fail after PRM is live | authorization_servers lists the v1 issuer https://sts.windows.net/<tenant-id>/ | Use https://login.microsoftonline.com/<tenant-id>/v2.0 |
| Audience validation fails after the v1 workaround | The aud optional claim with use_guid makes aud the server app's client ID GUID | Validate the server app's client ID GUID as audience; v2 tokens carry the same GUID in aud |
VS Code asks you to sign in at /authorize on your own host | Protected Resource Metadata is not being served | Serve PRM at the well-known path and in WWW-Authenticate |
Path 2: Client ID Metadata Documents for MCP, the Keycloak case
CIMD replaces the registration call with a URL. The client sends an https client_id with a path component, for example https://example.com/client.json. The authorization server fetches it, requires the client_id inside to match the URL exactly, caches it according to HTTP cache headers, and validates the request's redirect URI against the redirect_uris the document lists. The document must also carry client_name. No registration endpoint, no stored registration, and the same client_id works against any authorization server that supports the scheme.
Keycloak 26.8.1, released 13 October 2026, documents MCP support per spec version: 2025-03-26 is Supported, while 2025-06-18, 2025-11-25 and 2026-07-28 are Experimental. DCR is Supported. CIMD is Experimental, enabled with --features=cimd; RFC 8707 resource indicators are separately Experimental behind --features=resource-indicators.
CIMD also needs a client policy with the client-id-uri condition (URI scheme and trusted domains; empty domains evaluate false) and a client profile with the client-id-metadata-document executor. The executor's options are where a regulated deployment is won or lost:
Keycloak's guide documents real client documents that collide with tight settings. VS Code desktop sends https://vscode.dev/oauth/client-metadata.json with a http://127.0.0.1:<port>/callback redirect, so Restrict same domain must be off for it. Claude Desktop's metadata includes the jwt-bearer grant type, which requires the "Accept Public Client" toggle; Claude Code and Claude Desktop share one domain, so they need a single policy and profile rather than two. ChatGPT's document is not supported, because it carries token_endpoint_auth_methods_supported instead of token_endpoint_auth_method. Keycloak also rejects client_id URLs with a query string, fragment, userinfo, dot segments or no path. System-wide defaults sit in the SPI: min-cache-time 300 seconds, max-cache-time 259200 seconds and upper-limit-metadata-bytes 5000, each set with --spi-client-policy-executor--client-id-metadata-document--<property>=<value>.
The SSRF and self-provisioning issue: Keycloak #53812
MCP's own security best-practices page names the risk class: an authorization server that supports CIMD "takes a URL as input from an unknown client and fetches that URL", so a malicious client can point it at private administration endpoints. Mitigations include blocking private IP ranges and routing fetches through an egress proxy. Keycloak issue #53812, opened 9 October 2026 and reproduced on 26.8.0 and nightly builds, documents four weaknesses in the experimental implementation: All four require --features=cimd plus a matching client policy and executor, which is why the project handled it publicly as hardening rather than as an advisory. The dates are what matter. Keycloak 26.8.1 was published at 08:58 UTC on 13 October 2026. The fix, PR #53912, merged to main at 14:06 UTC the same day and is not in 26.8.1. The issue carries a release/27.0.0 label, which is a label, not a release. A separate weakness issue, #50529, about an SSRF allowlist bypass through sector_identifier_uri in fetched metadata, was still open on 15 October 2026. Our recommendation for a realm in a regulated network: keep CIMD disabled on production realms until a published release contains the #53812 fix. If you pilot it, use a non-production realm, restrict trusted domains to exact vendor domains, and send the Keycloak node's outbound traffic through an egress proxy that refuses private ranges. A SaaS IdP fetching attacker URLs reaches the vendor's network; your Keycloak reaches yours.
- 1. The executor fetches the client-supplied
client_idURL before client resolution or authentication, with no loopback or private-address blocking. - 2. A single anonymous authorization request can register a confidential client with
serviceAccountsEnabled=trueand attacker-controlled JWKS, without an initial-access token, which the issue notes "bypasses the registration-token boundary that Dynamic Client Registration (DCR) enforces." - 3. Attacker-registered
request_urisandsector_identifier_urivalues are later fetched with no address or scheme validation. - 4. Existing clients whose
clientIdis a URL matching the policy are routed into the CIMD create path and get HTTP 400 "Client Identifier in use", which permanently breaks their logins.
| Executor option | What it controls | Setting for a regulated pilot |
|---|---|---|
| Trusted domains | Which client_id hosts may be fetched; empty denies all | Exact vendor domains only, no wildcards |
| Allow http scheme | Whether non-TLS client_id URLs are accepted | Off |
| Restrict same domain | Whether redirect URIs must share the client_id domain | On, except for clients that need loopback callbacks |
| Only Allow Confidential Client | Rejects public clients | Off for desktop clients, which are public |
| Accept Public Client with Confidential-only Grant Types | Tolerates grant types like jwt-bearer on a public client | Off unless a named client needs it |
| Resource indicator allow list | Which resource values a CIMD client may request | Your MCP server URLs only |
Path 3: a DCR proxy in front of Entra ID, and what it costs
A DCR proxy, or facade, is the workaround for clients that only speak DCR. Your MCP server, or a gateway in front of it, exposes its own /register and authorization endpoints, hands each client its own client_id, and uses one static Entra client ID upstream. MCP's security best practices describe a confused-deputy attack that becomes possible when all four of these hold:
client_id.A facade on Entra satisfies the first two by design. Whether the third holds depends on how consent to the facade's app is granted in your tenant, which is not something to bet on. The fourth is yours to close. The 2026-07-28 security considerations make it a MUST: MCP proxy servers using static client IDs must obtain user consent for each dynamically registered client before forwarding to the third-party authorization server, on a consent page the proxy owns, shown before the redirect.
The audit cost is our analysis rather than a documented property, and it follows from the architecture. Entra sees one application, the facade's, for every caller. Which MCP client holds which token now lives only in the facade's registration store. Revoking one client means revoking it in the facade, because Entra has nothing finer than the shared app to disable. The facade's static client must also be allowed to request your server's scopes, through the same Authorized client applications mechanism or admin consent, so Entra's own inventory shows one approved client where there are really many.
If you run one anyway, require five things of it:
client_id, its name and its redirect URIs.client_id to the Entra sign-in event, so an investigator can walk from one to the other.If the facade is a gateway product, our MCP gateway comparison covers the vendors; the list above is what to test regardless of vendor.
Which path to pick for MCP on Entra ID
An access review asks which applications can obtain tokens for a system, and pre-registration is the only path where the identity provider itself answers that question. A facade moves the answer into a store you now have to protect, review and log. CIMD turns your authorization server into something that fetches URLs chosen by unauthenticated callers from inside your network, which is exactly why #53812 matters more on a realm that sits next to internal admin endpoints than on a SaaS IdP. If Entra is already your control plane for AI agents, as in Microsoft's Zero Trust for AI framework, pre-registration keeps MCP clients in the same plane. Registration says nothing about whether a client's tools are safe; that is the MCP supply chain audit. The wider stack is in our AI security guide, and protocol basics in the MCP developer guide.
This week, run the three-field metadata check against your production issuer and save the output with the ticket. List the MCP clients people actually use against your servers. For each one you approve, create one Entra registration or authorize one known client ID, and record the owner. If a DCR facade is already in place, open it in a private browser window as a new client and confirm you see its consent page before Entra's sign-in screen.
| IdP | Client type | Recommended path | Audit evidence | Main risk |
|---|---|---|---|---|
| Entra ID | VS Code | Pre-register: authorize the default client ID, or own app via oauth.clientId | Entra app list and sign-in logs | Default ID is shared by every VS Code user |
| Entra ID | Microsoft 365 Copilot | Static OAuth registration | Entra app list and sign-in logs | One registration per tenant to maintain |
| Entra ID | Other interactive clients with a static client ID setting | Pre-register one public client app each | Entra app list and sign-in logs | One more registration to own per client |
| Entra ID | Clients that only do DCR | DCR facade with per-client consent, or decline the client | Facade store joined to Entra logs | Confused deputy without consent; attribution moves out of Entra |
| Self-hosted Keycloak | Clients that send CIMD | Pre-register now; CIMD only on a non-production realm until a fixed release ships | Keycloak client list and events | #53812 SSRF and self-provisioning |
| Any | Autonomous agents with no user | Agent identity with client credentials and app roles | IdP app roles and service principals | Treating an agent as an interactive client |
FAQ
Quick answers to the questions this post tends to raise.



