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.

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