Skip to content

MCP OAuth authorize request omits 'scope' parameter for Entra ID servers with static oauthClientId, causing AADSTS900144 #4582

Description

@mikemassa84

Describe the bug

When signing in to a remote (type: http) MCP server whose authorization server is Microsoft Entra ID, configured with a statically-provided oauthClientId + oauthPublicClient: true (no Dynamic Client Registration), Copilot CLI opens the browser to Entra ID's /authorize endpoint without a scope query parameter at all, even though the server's OAuth metadata correctly advertises scopes_supported. Microsoft Entra ID's v2.0 endpoint requires scope on both the authorization request and the token exchange (unlike base RFC 6749, where it's optional), so the flow fails immediately with:

Sorry, but we're having trouble signing you in.

AADSTS900144: The request body must contain the following parameter: 'scope'.

This is very likely the same root-cause bug class as #4464 (silent refresh fails with AADSTS70011 due to mishandled scope on the refresh_token grant) — in both cases the OAuth client isn't correctly deriving/propagating the scope parameter for Entra ID flows built from server-advertised metadata. It's also functionally identical to anthropics/claude-code#69547, which reports the exact same AADSTS900144 error for the exact same scenario (pre-registered/static public client against an Entra ID-backed MCP server, since Entra ID doesn't support DCR) in a different MCP client, suggesting this is a well-known interoperability gap for MCP clients talking to Entra ID with a fixed client id.

Affected version

1.0.80 (Windows x64). Also reproduced on a locally-modified 1.0.79-era build before upgrading.

Steps to reproduce the behavior

  1. Configure a remote HTTP MCP server backed by Microsoft Entra ID with a statically-provided client id (no DCR), e.g. in ~/.copilot/mcp-config.json:
  {
     "mcpServers": {
       "example-server": {
         "type": "http",
         "url": "https://example.org",
         "oauthClientId": "<entra-app-client-id>",
         "oauthPublicClient": true
       }
     }
   }

The server's /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server documents are RFC 8414-compliant: authorization_servers matches the document's own issuer exactly, and authorization_endpoint/token_endpoint point at the real Microsoft Entra ID tenant endpoints (https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize and .../token). The authorization-server metadata's scopes_supported correctly lists the resource's App ID URI scope, e.g. ["openid", "profile", "email", "api://<app-id>/access_as_user"].
2. In the app, open MCP settings and click Sign in for that server.
3. A browser window opens and navigates to Microsoft's /authorize endpoint. The observed request URL was:

  https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
     ?response_type=code
     &client_id=<client-id>
     &state=<state>
     &code_challenge=<challenge>
     &code_challenge_method=S256
     &redirect_uri=http%3A%2F%2F127.0.0.1%3A<port>%2F
     &client_session=<session-id>

Note there is no scope= parameter anywhere in this URL.
4. Entra ID immediately rejects the request:

   AADSTS900144: The request body must contain the following parameter: 'scope'.

Expected behavior

The MCP OAuth client should build the scope parameter for the /authorize request (and again for the /token exchange) from the target server's advertised scopes_supported (from either the oauth-authorization-server or oauth-protected-resource metadata document), the same way it presumably does for servers that use Dynamic Client Registration. For Entra ID specifically, scope must be present on every leg of the flow (authorize request, token exchange, and refresh), since Entra ID's v2.0 endpoint treats scope as mandatory rather than optional.

Additional context

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