Skip to content

Remote MCP OAuth: silent refresh fails with AADSTS70011 (refresh reuses granted scope mixing .default + resource-specific scopes), forcing repeated interactive sign-in #4464

Description

@madhavdeshpande

Describe the bug

Remote (type: http) MCP servers that authenticate via Microsoft Entra OAuth repeatedly force an interactive "Sign in" — roughly once per access-token lifetime (~60–75 min). The silent refresh path never succeeds.

Root cause is a scope bug in the refresh request. On the refresh_token grant, the client replays the granted scope string that Entra echoes back in the token response. That granted string contains the resource-specific scopes plus the resource's .default scope. Entra rejects that combination:

AADSTS70011: invalid_scope — "The scope <resource>/<Scope1> ... <resource>/.default is not valid.
.default scope can't be combined with resource-specific scopes."

The native OAuth layer maps this failure to browser_required, which the SDK surfaces as MCPOAuthBrowserRequiredError → interactive re-auth. The refresh token itself is valid; only the malformed scope parameter fails.

Affected version

1.0.79-9 (bundled SDK; buildMetadata.gitCommit 65f2c0f). Windows x64. The failing logic is in the SDK/native OAuth layer, so it is expected to be platform-independent.

Steps to reproduce the behavior

  1. Configure one or more type: http remote MCP servers whose OAuth authorization server is Microsoft Entra and whose tokens use resource-specific scopes (e.g. an agent365 / M365 Agents MCP server: https://agent365.svc.cloud.microsoft/agents/tenants/{TENANT_ID}/servers/mcp_*).
  2. Sign in interactively — succeeds; the server works.
  3. Wait for the ~70-minute access token to expire, then make another MCP call.
  4. The server returns 401; the client attempts a silent refresh and immediately logs:
    [ERROR] MCP server <name> still needs interactive re-auth:
    MCPOAuthBrowserRequiredError: Browser-based OAuth required for
    https://agent365.svc.cloud.microsoft/agents/tenants/{TENANT_ID}/servers/<server>
    
  5. You must click Sign in again for every such server, repeatedly.

Isolated reproduction against the Entra token endpoint, using the stored refresh token from ~/.copilot/mcp-oauth-config/<hash>.tokens.json and varying only the scope parameter of a grant_type=refresh_token POST:

scope sent on refresh Result
Stored/granted scope = <resource>/<Scope1> … <ScopeN> <resource>/.default (what the client replays) HTTP 400 · AADSTS70011 invalid_scope
<resource>/.default only HTTP 200 — new access token + rotated refresh token
<resource>/<single resource scope> only (no .default) HTTP 200

So the refresh token is valid and refresh works — only the .default + resource-specific-scopes combination is rejected.

The persisted scope field in the token file contains the .default-contaminated list, which is exactly what gets replayed on refresh. This also explains why an interactive sign-in only fixes it until the next refresh: the fresh interactive grant re-stores the same contaminated scope.

Expected behavior

Silent refresh should succeed without interactive re-auth. On the refresh_token grant the client should send a valid scope — e.g. request <resource>/.default (optionally with openid profile offline_access), or strip .default when resource-specific scopes are present — instead of echoing back the granted scope string that mixes .default with resource-specific scopes.

Additional context

  • Symptom cadence: re-auth is required every time the access token expires; all remote MCP servers sharing the same Entra broker/client id drop to "needs auth" together on each expiry.
  • Call path: 401 from the resource → silent refresh (reads creds from OS keyring service copilot-mcp-oauth, POSTs to login.microsoftonline.com) → Entra AADSTS70011 → native MCP_OAUTH_ERROR[browser_required] → SDK MCPOAuthBrowserRequiredError.
  • Token store: ~/.copilot/mcp-oauth-config/*.tokens.json, each holding accessToken, scope, expiresAt, refreshToken; the scope field holds the .default + resource-scope mix that is replayed.
  • OS: Windows x64.
  • Sensitive values (tenant id, access/refresh tokens, Entra trace/correlation ids, machine paths, token-file hashes) have been redacted. Entra trace/correlation IDs from the reproduced 400s are available on request.

Activity

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

    area:authenticationLogin, OAuth, device auth, token management, and keychain integrationarea:mcpMCP server configuration, discovery, connectivity, OAuth, policy, and registry

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions