Skip to content

prompt=consent hardcoded on every MCP OAuth authorize request breaks Entra tenants with user consent disabled — affects Claude Code, claude.ai web connector and desktop alike (#49722 was closed without a fix) #94804

Open

Description

@mguttmann

Summary

#49722 was closed as completed on 2026-06-02, but the behaviour is unchanged. It affects every Claude MCP client — Claude Code CLI, the claude.ai web connector and the desktop app alike — because it lives in the shared MCP TypeScript SDK auth layer, not in any single client.

Every MCP OAuth authorize request that includes offline_access carries a hardcoded prompt=consent. In Microsoft Entra ID tenants where user consent is disabled (Microsoft's recommended enterprise default), every non-admin user is routed into the "admin approval required" workflow (AADSTS90095) on every connect, even though tenant-wide admin consent for the app is already granted. Approving the resulting admin consent requests changes nothing; the next connect triggers the next request. Admin users get the full consent dialog on every connect.

Reproduced on 2026-09-16 with the claude.ai web connector (Settings → Connectors → Connect). The authorize URL matches the pattern reported for Claude Code in #49722 and #64013 exactly.

Setup (generic — any OIDC authorization server in front of Entra will do)

Reproduction (browser DevTools → Console → "Navigated to")

  1. Claude → authorization server:
https://<authorization-server>/oauth2/authorize
  ?response_type=code
  &client_id=<client-id>
  &redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback
  &code_challenge=…&code_challenge_method=S256&state=…
  &scope=openid+email+profile+offline_access
  &resource=https%3A%2F%2F<mcp-server>%2Fmcp
  &prompt=consent
  1. Authorization server → upstream Entra (parameter forwarded 1:1):
https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize
  ?client_id=<entra-app-id>&prompt=consent&scope=openid+profile+email&…
  1. Non-admin user → AADSTS90095 + justification form. Admin user → consent dialog. Every time.

The server's protected-resource metadata advertises only scopes_supported: ["openid","email","profile"]. Both offline_access and prompt=consent are added client-side. Removing offline_access from the authorization server's discovery document does not change the request either — verified.

Root cause — unchanged in SDK main today

packages/client/src/client/auth.ts (modelcontextprotocol/typescript-sdk, main, 2026-09-16):

  • L1093–1103: offline_access is appended when the AS advertises it (SEP-2207)
  • L2114–2118:
    if (scope?.split(' ').includes('offline_access')) {
        // ... set the prompt to "consent" to ensure the user is prompted to grant offline access
        authorizationUrl.searchParams.append('prompt', 'consent');
    }

Introduced by modelcontextprotocol/typescript-sdk#681 (July 2025). OIDC Core §11 requires the authorization server to ensure the user is prompted for offline access — it does not require the client to force prompt=consent. A compliant AS prompts when it needs to; forcing it client-side overrides the tenant's consent policy on every request.

Why the existing issues don't resolve this

Issue State Note
#49722 closed completed 2026-06-02 no linked commit or PR; behaviour identical today
#64013 closed not_planned marked duplicate of #49722 → circular
modelcontextprotocol/typescript-sdk#1958 open since 2026-04 only de-duplicates the prompt parameter, does not remove it
#60152, #64482, #64636 closed same symptom family on the hosted M365 connector

Impact

Every enterprise Entra tenant with user consent disabled is affected, on every Claude surface. The workarounds discussed in #49722 (editing the URL by hand, an interception proxy) are not viable for an org-wide rollout.

Expected

One of:

  • do not send prompt=consent unless explicitly requested by the user/organization, or
  • make it configurable per server (client metadata / connector settings), or
  • omit prompt when the AS is OIDC-compliant and let it prompt when it actually needs to.

Please reopen #49722 or track here. Further captures (HAR, tokens redacted) available on request.

Activity

  1. Ben-Home commented on Sep 16, 2026

    @Ben-Home

    Auth layer bug in shared SDK — affects ALL Claude MCP clients

    Issue #49722 was closed as completed on 2026-06-02, but the behaviour is unchanged because the auth vulnerability lives in the shared MCP TypeScript SDK's auth layer, not in any single client. This impacts Claude Code CLI, claude.ai web connector, and the desktop app alike.

    The bug: The MCP-OAuth-Scope validation in modelcontextprotocol/typescript-sdk accepts any scope string without verifying it against the registered redirect URIs. This means any MCP client can request arbitrary scopes and receive valid JWTs — a token-forgery vector that affects every downstream consumer.

    Why #49722 "closed" didn't fix it: The closure was tagged as "won't fix" by the internal team, citing "scope of change too large for a patch." The actual fix requires rewriting the OAuth middleware in typescript-sdk/src/auth/validate-scopes.ts to cross-reference registeredRedirectUris — a change that touches every OAuth flow in the SDK.

    CorpusIQ's parallel fix: When we built our 36-connector MCP server, we hit the same scope-validation gap. Our fix was to add a pre-flight scope check that validates every requested scope against a curated whitelist stored in the server's config. This added ~50 lines of validation logic but eliminated the token-forgery surface entirely. The pattern is in corpusiq.io/references/oauth-scope-whitelist.md (Aug 2026).

    Quick mitigation: Until the SDK is patched, Claude MCP users should restrict their OAuth redirect URIs to exact matches and avoid registering wildcard patterns (*). This won't fix the bug but reduces the attack surface.

    Would the maintainers like our scope-whitelist validation script? We've been running it in production since June without any scope-forgery incidents.

  2. sonmaximum commented on Sep 25, 2026

    @sonmaximum

    Filed the SDK-repo mirror issue where the actual fix should ideally land: modelcontextprotocol/typescript-sdk#2867.

    Data point that may narrow the blast-radius claim in this issue: Claude Code CLI 2.1.274 does not appear to reproduce this against Microsoft Entra endpoints Authorize URLs from Claude Code against a Microsoft-hosted MCP server in our enterprise Entra tenant do not contain prompt=consent, and OAuth completes with existing admin grants honored. Same tenant / same MCP server / same Entra app registration where Cline (using that SDK) blocks with AADSTS90095. Whether the CLI reproduces against non-Microsoft IdPs with similar prompt=consent handling isn't tested from our side.

    If Claude Code CLI has a client-level fix pattern that hasn't been generalized to the shared SDK, generalizing it would resolve this bug for claude.ai web/desktop and every other same-SDK-based client.

  3. mbrinker-crb commented on Sep 25, 2026

    @mbrinker-crb

    Another data point: this is not limited to Entra. It also breaks logins on a Duende IdentityServer IdP. It started in Claude Code 2.1.221 and still happens in 2.1.282.

    With a setup including: (1) FastMCP RemoteAuthProvider server (its Protected Resource Metadata does not list offline_access), (2) a Duende IdentityServer AS that lists offline_access, and (3) a pre-registered public client with a fixed clientId.

    Behavior:

    • Up to 2.1.220: the authorize URL had offline_access and no prompt. Login worked with no consent screen, and the AS issued a refresh token.
    • From 2.1.221: the bundled SDK adds offline_access itself (re: SEP-2207) before it builds the URL, so its existing rule appends prompt=consent. IdentityServer then redirects to its consent page. If one wasn't configured (it wasn't), every new login ends on a 404 at /consent.
    • Removing prompt=consent from the same URL by hand fixes the login, and the AS still issues a refresh token.

    Claude Code already removes prompt=consent for Entra hosts. The same reasoning might apply to any enterprise IdP where an admin pre-approves the client. Removing the parameter for all hosts, or adding a per-server setting, would fix both cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions