Repository navigation
Description
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)
- Client: claude.ai web connector; identical behaviour reported for Claude Code CLI in Remote MCP OAuth: prompt=consent hardcoded on every authorize request breaks Entra tenants with user-consent disabled #49722 / [Bug] MCP OAuth client hardcodes prompt=consent, bypassing tenant-wide admin consent #64013
- MCP server: FastMCP
RemoteAuthProvider— publishes protected-resource metadata only, has no authorize endpoint and cannot influence the parameter - Authorization server: OIDC-compliant IdP federating to Microsoft Entra ID (passes
promptthrough to the upstream IdP, as the spec allows) - Entra: user consent disabled; admin consent for the app granted tenant-wide
Reproduction (browser DevTools → Console → "Navigated to")
- 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
- 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&…
- 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_accessis 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=consentunless explicitly requested by the user/organization, or - make it configurable per server (client metadata / connector settings), or
- omit
promptwhen 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
Ben-Home commentedon Sep 16, 2026
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-Scopevalidation inmodelcontextprotocol/typescript-sdkaccepts 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.tsto cross-referenceregisteredRedirectUris— 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.
sonmaximum commentedon Sep 25, 2026
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 withAADSTS90095. Whether the CLI reproduces against non-Microsoft IdPs with similarprompt=consenthandling 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.
mbrinker-crb commentedon Sep 25, 2026
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
RemoteAuthProviderserver (its Protected Resource Metadata does not listoffline_access), (2) a Duende IdentityServer AS that listsoffline_access, and (3) a pre-registered public client with a fixedclientId.Behavior:
offline_accessand noprompt. Login worked with no consent screen, and the AS issued a refresh token.offline_accessitself (re: SEP-2207) before it builds the URL, so its existing rule appendsprompt=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.prompt=consentfrom the same URL by hand fixes the login, and the AS still issues a refresh token.Claude Code already removes
prompt=consentfor 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.