MCP OAuth authorize request omits 'scope' parameter for Entra ID servers with static oauthClientId, causing AADSTS900144
Nobody has claimed this yet.
- Dominant language
- Shell
- Stars
- 11.2k
- Forks
- 1.9k
- Avg merge
- 14h 16m
- Merged PRs (30d)
- 6
Description
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
- 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
- The corresponding server-side OAuth metadata was independently verified as fully RFC 8414 §3.3-compliant via repeated direct HTTP requests (issuer exactly matches
authorization_servers, byte-for-byte, across 15 parallel requests) before this bug surfaced, ruling out a server-side metadata problem. - This may be related to how the client resolves scope when a server config supplies a static
oauthClientId/oauthPublicClient: trueand skips theregistration_endpoint(Dynamic Client Registration) flow — DCR responses may carry scope information that the static-client path doesn't otherwise obtain fromscopes_supported. - Related issues in this repo: #4464 (refresh-token grant mishandles Entra ID scope, AADSTS70011), #4439 / #4480 (separate RFC 8414 issuer-mismatch bugs, now fixed/in progress).
- Related issue in a different MCP client with an identical error and scenario: anthropics/claude-code#69547.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the MCP settings Sign in flow for a remote HTTP server configured in ~/.copilot/mcp-config.json, then trace how the oauth-authorization-server and oauth-protected-resource metadata are used with a static oauthClientId. Reproduce against Entra ID and verify that scope is propagated through authorization, token exchange, and refresh, using issue #4464 as related context.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure
- Domain
- api, authentication
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100