Support pre-configured OAuth client credentials without requiring Dynamic Client Registration

Open 💬 16 comments Opened Feb 18, 2026 by danwritecode

Problem

Claude Code's OAuth implementation requires Dynamic Client Registration (DCR) even when a clientId is pre-configured in .mcp.json. This makes Azure AD/Entra ID (and other enterprise OAuth providers) incompatible with Claude Code's MCP client.

Enterprise OAuth providers like Azure AD do not support DCR - they require pre-registered applications with fixed client IDs. This is standard practice in enterprise environments for security and compliance reasons.

Current Behavior

Even with this configuration:

{
  "mcpServers": {
    "my-server": {
      "type": "http",
      "url": "http://localhost:5042/mcp",
      "oauth": {
        "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}

Claude Code still:

  1. Fetches authorization server metadata
  2. Looks for registration_endpoint
  3. Fails with "Incompatible auth server: does not support dynamic client registration"

The pre-configured clientId is ignored.

Expected Behavior

When oauth.clientId is provided in the configuration, Claude Code should:

  1. Skip the DCR check entirely
  2. Use the pre-configured client ID for the OAuth flow
  3. Proceed directly to authorization

VS Code Comparison

VS Code's MCP implementation handles this correctly:

  • Reads /.well-known/oauth-protected-resource from MCP server
  • Uses a pre-registered client ID
  • Redirects to Azure AD login
  • Works without any additional endpoints or workarounds

Workarounds Attempted

To make Azure AD work with Claude Code, we had to implement:

  1. A fake /.well-known/oauth-authorization-server endpoint proxying Azure AD metadata
  2. A /register DCR endpoint returning pre-configured credentials
  3. Even then, failures occur due to resource parameter / scope mismatches (AADSTS9010010)

This adds significant complexity and still doesn't fully work.

Proposed Solution

When oauth.clientId is present in the MCP server configuration:

  1. Skip checking for registration_endpoint in authorization server metadata
  2. Skip the DCR call to /register
  3. Use the configured clientId directly in the authorization flow

This aligns with the MCP spec which states DCR is a "SHOULD", not a "MUST":

"MCP clients and authorization servers SHOULD support the OAuth 2.0 Dynamic Client Registration Protocol"

Impact

This limitation affects:

  • All Azure AD/Entra ID users
  • Enterprise environments with pre-registered OAuth apps
  • GitHub's official MCP server (see github/github-mcp-server#549)
  • Any OAuth provider that doesn't support DCR

Related Issues

  • #2527 (closed due to inactivity, but issue persists)

Environment

  • Claude Code CLI
  • MCP server with Azure AD/Entra ID OAuth
  • .mcp.json with pre-configured oauth.clientId

View original on GitHub ↗

16 Comments

github-actions[bot] · 4 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/2527
  2. https://github.com/anthropics/claude-code/issues/3273
  3. https://github.com/anthropics/claude-code/issues/3433

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

danwritecode · 4 months ago

Even in the official MCP spec, DCR is a fallback option "if supported", furthermore clients SHOULD support provided client information (first bullet):

<img width="390" height="275" alt="Image" src="https://github.com/user-attachments/assets/01bc0b54-64c2-45e8-8bce-f62e034d114f" />

ajbest0070 · 4 months ago

I am also encountering this issue.

W00DY22 · 4 months ago

having the same issue as well

yeahthankscoach · 4 months ago

+1 please look into a solution for this, DCR should not a blocker for standard app registration through OATH

keblato · 4 months ago

I am having the same issue

guinatal · 4 months ago

I have this issue too!

sighphyre · 4 months ago

Throwing my hat in the ring - also affected by this

joelclimbsthings · 4 months ago

Same issue here.

cchen-motion · 4 months ago

Commenting because this is silly that Anthropic, the progenitor of MCP, cannot properly implement a written protocol specification in their own clients with an unlimited supply of tokens, access to frontier models, and extremely well-paid, top-tier engineers. Every part of this feature is a mature, documented spec.

Anthropic: too busy hyping random, useless agent-built code to get their own protocol working on their own clients....

keblato · 4 months ago

There is any update on this ?

vladmsnk · 3 months ago

<img width="599" height="359" alt="Image" src="https://github.com/user-attachments/assets/ca37f41e-0ce2-42cb-b82d-3398cf5fa7c6" />

I found that you can manually specify client_id and client_secret for claude code instance. It might be helpful

shivarammysore · 3 months ago

Adding context from our duplicate (#38102) — we hit this with AWS Cognito as the OAuth provider for a custom MCP server.

Workaround that works today: Use ${ENV_VAR} expansion in .mcp.json headers instead of the OAuth flow:

{
  "mcpServers": {
    "my-server": {
      "type": "http",
      "url": "https://my-mcp-server.example.com/mcp/",
      "headers": {
        "Authorization": "Bearer ${MY_TOKEN}"
      }
    }
  }
}

With a helper script that completes the OAuth PKCE flow externally and exports the token as an env var. Launch claude from that shell.

Additional UX issue: After connecting via the env var workaround, the /mcp menu shows the server as connected but with Auth: ✘ not authenticated and an "Authenticate" button. Clicking "Authenticate" triggers the broken dynamic registration flow and disconnects the working session. The Authenticate option should not appear (or should be safe) when the server is already connected.

Server-side setup (in case it helps others with Cognito):

  • /.well-known/oauth-authorization-server (RFC 8414) — points to Cognito oauth2/authorize + oauth2/token
  • /.well-known/oauth-protected-resource (RFC 9728) — advertises the resource server
  • 401 on unauthenticated requests with WWW-Authenticate: Bearer resource_metadata="..." header
  • code_challenge_methods_supported: ["S256"], token_endpoint_auth_methods_supported: ["none"] (public client)

All the server-side plumbing works — the issue is purely that Claude Code ignores the configured clientId and attempts dynamic registration.

shivarammysore · 2 months ago

+1 — production non-DCR scenario (AWS Cognito) and scope note across related issues

Adding a production data point and a scope clarification for triagers consolidating across #43000 / #3273 / #57674 / this issue.

  • Setup: self-hosted MCP server at https://example.com/mcp/ fronts AWS Cognito for OAuth. Cognito does not implement RFC 7591 (DCR) — architectural, not a config gap. Bug-flavor repro posted in #3273.
  • Scope note re: the #43000 wildcard-loopback suggestion: the wildcard 127.0.0.1 loopback redirect URI per OAuth 2.1 §7.5.2 — a great fix for DCR-capable servers that mint a new client_id per launch — does not help non-DCR providers. Cognito (and similar: Okta in some configurations, Azure AD without DCR add-ons, custom OIDC servers) refuses DCR before any redirect URI is checked. Honoring a pre-configured clientId from .mcp.json — this issue — is the only fix that closes the non-DCR gap. The #43000 fix and this one are complementary, not equivalent.

Workarounds in production: get-token.sh env-var injection; mcp-stdio as a standby Python stdio-to-HTTP relay that honors --client-id directly.

dustin-flow · 2 months ago

+1 from the Cowork (Claude desktop) side — full context at anthropics/claude-plugins-official#283 (comment) and a Cowork-specific repro just posted at anthropics/claude-code#3273 (comment).

Claude Code already has a partial story here (the claude mcp add --client-id ... --client-secret ... flag flow noted earlier in this thread, plus the ${ENV_VAR} header trick in user-authored .mcp.json). The remaining gaps to keep in scope when this lands:

  1. Plugin marketplace surface. Plugin-bundled .mcp.json files (e.g. cowork_plugins/marketplaces/knowledge-work-plugins/engineering/.mcp.json) live at app-managed paths that get overwritten on plugin update. So even with full SDK support for pre-configured creds, plugin-bundled servers have nowhere stable to put a clientId / clientSecret. Some kind of per-plugin user-overlay config (~/.claude/plugins/<plugin-id>/overrides.json or similar) would let users inject creds without forking the plugin.
  2. Cowork has no UI surface for any of this. Its Connectors panel exposes only a "Connect" button, which goes straight to the DCR-only OAuth path. No fields for client_id / secret, no Authorization-header textbox, no way to point at a .env file. The Claude Code CLI workarounds aren't reachable from Cowork at all — see the #3273 comment for what users actually have to do (abandon the plugin's connector entirely and shell out to gh).
  3. No-clientId config case. Today when a plugin's .mcp.json declares a remote MCP server URL with no clientId field at all, the SDK goes straight to DCR and fails on servers like GitHub's that don't support it. Worth deciding: should the SDK fall back to a hosted proxy / static-creds path in this case, or hard-fail at server-init with a clear "this plugin needs pre-configured credentials" error instead of exposing perpetual authenticate stub tools?

Net: the fix needs to ship a config path that's reachable from both surfaces (CLI and desktop) and survives plugin updates. Otherwise Cowork users in particular remain stuck even after Claude Code is unblocked.

aholland · 5 days ago

This looks like it works now, at least on 2.1.206. I connected to GitHub's remote MCP server (a non-DCR provider) using a pre-configured client ID:

claude mcp add --scope user --transport http \
--client-id <client-id> --client-secret --callback-port 8080 \
github https://api.githubcopilot.com/mcp

The clientId was honoured and DCR wasn't attempted. --callback-port matters — GitHub needs an exact redirect URI registered on the OAuth App (http://localhost:8080/callback), and the port is random otherwise.