MCP OAuth: cannot connect to Microsoft Entra ID-protected servers (RFC 8707 resource must be App ID URI, conflicts with RFC 9728 canonical-resource check)
What happened
Claude Code's built-in remote MCP OAuth client cannot connect to an MCP server protected by Microsoft Entra ID (Azure AD), because Entra's resource-indicator requirement is mutually exclusive with the RFC 9728 canonical-resource check that the client enforces.
There appears to be no configuration (CLI flag, .mcp.json oauth field, or env var) to bridge the two, so Entra-protected MCP servers are effectively unusable with Claude Code today.
Version: Claude Code 2.1.174
The conflict
An Entra-protected resource server exposes a scope like api://<app-id-guid>/mcp.invoke. Two hard requirements collide:
- Entra (RFC 8707) — the
resourceparameter sent to the/authorize+/tokenendpoints must be the resource's App ID URI (api://<app-id-guid>). If it's anything else (e.g. the server's own URL), Entra rejects the request:
> AADSTS9010010: The resource parameter provided in the request doesn't match with the requested scopes.
- Claude Code (RFC 9728) — the client validates that the
resourcefield in the server's/.well-known/oauth-protected-resourcemetadata equals the MCP server's own URL / origin, and it echoes that same value as the RFC 8707resourceparam. If the metadata advertises the App ID URI, the client refuses to proceed:
> SDK auth failed: Protected resource api://<app-id-guid> does not match expected https://<server-host>/mcp (or origin)
So whichever value the server advertises, exactly one side fails:
| Metadata resource value | Entra /authorize | Claude Code check |
|---|---|---|
| https://<server-host>/mcp (server URL) | ❌ AADSTS9010010 | ✅ passes |
| api://<app-id-guid> (Entra App ID URI) | ✅ passes | ❌ "does not match expected …" |
The only value that could satisfy both is one where the server's URL is itself a registered Entra App ID URI — which requires hosting the server on a tenant-verified custom domain and registering that HTTPS URL as the app's identifier URI. That's infeasible for common cases (Azure Functions *.azurewebsites.net, *.azure-api.net, and internal-only DNS names are not Entra-verifiable domains).
Reproduce
- Register an Entra resource-server app; expose a scope
api://<guid>/mcp.invoke. - Host an MCP server (e.g. Azure Function) that serves RFC 9728 metadata and validates the Entra JWT.
- Register a public-client app with a loopback redirect;
claude mcp add --transport http --client-id <public-client> --callback-port 8000 my-server https://<host>/mcp. /mcp→ authenticate.
- If metadata
resource= server URL → browser sign-in, then AADSTS9010010. - If metadata
resource= App ID URI → "Protected resource … does not match expected …" before the browser even opens.
Evidence the server is otherwise correct
Acquiring a token for the App ID URI out-of-band (a pre-authorized first-party client) and calling the server directly returns a valid MCP initialize result over HTTP 200 — so the server's Entra metadata, JWT/audience validation, and scope gate are all correct. The gap is solely in the client's inability to send a resource param that differs from the metadata/server URL.
Requested fix (any one would unblock)
- A config option (e.g.
oauth.resourcein.mcp.jsonor a--resourceflag) to set the RFC 8707resourceparameter independently of the server URL — so it can be sent asapi://<guid>while the client still connects to theazurewebsites.nethost. - An explicit Entra / Azure AD provider mode that knows to use the App ID URI (derivable from
scopes_supported) as the resource. - An opt-in flag to relax the RFC 9728 canonical-resource equality check to accept the value advertised in
scopes_supported.
Microsoft's own guidance for Entra-protected MCP servers uses the App ID URI as the resource, so interoperating with Entra will likely require one of the above. Thanks!
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗