anthropics / anthropics/claude-ai-mcp

Custom connector OAuth fails a few seconds after successful login — Entra rejects automated follow-up request with mismatched resource/scope (AADSTS 9010010)

Open
#851 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
No language data
Stars
471
Forks
77
PR merge metrics
No merged PRs in 30d

Description

### What happened?

A Custom Connector pointing to an MCP server secured with Azure Entra ID (external IdP) fails a few seconds after a fully successful interactive OAuth login.

**Environment:** claude.ai web (Team/Enterprise plan), Organization-level custom connector ("Kielikone internal data"), custom MCP server (TypeScript, `@modelcontextprotocol/sdk`) hosted on AWS EKS, auth via Azure Entra ID following the MCP Authorization spec (2025-11-25), authorization_code + PKCE, SSE transport.

The initial authorization_code flow completes correctly — the user authenticates with Microsoft, is redirected back to Claude, and the connector briefly appears connected — but shortly afterward Claude's backend makes an automated follow-up request to Entra that fails, and the connector then shows a generic error toast:

> Authorization with Kielikone internal data failed. You can check your credentials and permissions.

Server-side Protected Resource Metadata (PRM), token audience configuration, and Entra app registrations have all been independently verified correct via manual `curl`/browser-based OAuth testing. The failing request is not one we issue — it originates from Anthropic's backend (`User-Agent: python-httpx/0.28.1`, confirmed via Entra sign-in logs) and uses a resource/scope pairing that Entra rejects, despite the PRM being served with `Cache-Control: no-store` and re-verified across a full disconnect/reconnect of the connector.

This looks related to the caching/refresh issues already tracked in #228, #247, and #82 — filing separately since the specific failure mode (mismatched `resource`/`scope` on an automated follow-up request, confirmed via Entra sign-in logs with error code 9010010) hasn't been documented with this level of detail yet, but may share the same root cause in the `mcp-proxy.anthropic.com` bridge layer.

### What did you expect to happen?

After successful interactive login, Claude's backend should either (a) use the access token obtained during the authorization_code exchange as-is for subsequent MCP requests, or (b) if it performs an additional/refresh token request, construct that request using the `resource`/`scope` values currently advertised by the server's Protected Resource Metadata — not a mismatched pairing.

### Steps to reproduce

1. Server publishes PRM at `/.well-known/oauth-protected-resource`:
```json
{
"resource": "https://mcp.sanakirja.fi",
"authorization_servers": ["https://login.microsoftonline.com/{tenant_id}/v2.0"],
"scopes_supported": ["https://mcp.sanakirja.fi/mcp.tools.invoke"],
"resource_name": "Kielikone MCP Gateway"
}
```
served with headers confirmed via direct GET inspection:
```
HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: no-store, no-cache, must-revalidate
pragma: no-cache
etag: W/"f9-CdnRiJOFQyWMF5StK6aRnB3Pqdw"
```
2. Azure Entra ID app registrations: a resource server app (Application ID URI `https://mcp.sanakirja.fi`, verified custom domain, scope `mcp.tools.invoke` exposed under "Expose an API") and a public client app (PKCE, no secret, redirect URI `https://claude.ai/api/mcp/auth_callback`, admin-consented for the resource's scope).
3. In Claude, **Organization settings → Connectors**, add the custom connector with the server's MCP URL and the client app's Client ID under Advanced settings (no secret).
4. Click **Connect** on the connector.
5. Redirected to Microsoft login — authenticates successfully, consent already admin-granted.
6. Redirected back to `https://claude.ai/api/mcp/auth_callback` — Claude briefly shows the connection succeeding.
7. After a few seconds, with no further action from the user, an error toast appears: "Authorization with Kielikone internal data failed. You can check your credentials and permissions."

No further requests reach the MCP server itself at the point of failure — the failure is visible only in Entra sign-in logs, as a request from Anthropic's backend, not in the MCP server's own request logs.

**Manual reproduction that succeeds (for comparison):** Building the same `authorize` → `token` exchange manually (browser + curl, identical tenant/client/resource/scope values) completes successfully end-to-end, and the resulting access token works correctly against the MCP server via `curl`. This isolates the failure to Claude's backend's own request construction, not the Entra configuration.

### Area

OAuth / Authentication

### MCP Server (if applicable)

Custom server — TypeScript (`@modelcontextprotocol/sdk`), hosted on AWS EKS, SSE transport, Azure Entra ID (external IdP) bearer token validation via `jose`/JWKS.

### Error messages or logs

```shell
Claude UI:
Authorization with Kielikone internal data failed. You can check your credentials and permissions.
Entra Trace ID: a0fa76fd-35f6-48cc-a7f6-62a28ecac400
ofid_4dcdebb86aaf48d4

Most recent occurrence (post PRM cache-header fix, clean disconnect/reconnect):
Timestamp: 2026-08-14T09:58:25Z

Entra sign-in log entry retrieved via Microsoft Graph (GET /beta/auditLogs/signIns?$filter=correlationId eq '...'),
corresponding to the automated follow-up request:

{
"appId": "",
"appDisplayName": "",
"resourceId": "",
"resourceDisplayName": "Kielikone MCP Gateway",
"authenticationProcessingDetails": [
{ "key": "Is Legacy Store Used", "value": "0" },
{ "key": "Legacy Store Use Information", "value": "" },
{ "key": "wids", "value": "[]" }
],
"status": {
"errorCode": 9010010,
"failureReason": "The resource parameter provided in the request doesn't match with the requested scopes.",
"additionalDetails": "MFA requirement satisfied by claim in the token"
}
}

Note: "MFA requirement satisfied by claim in the token" is characteristic of a silent/non-interactive
token acquisition (e.g. refresh_token grant or cached-session reuse) rather than a fresh authorization_code
exchange — suggesting the failing request is a follow-up/refresh call issued automatically by Claude's
backend shortly after the successful interactive login, consistent with #228.

Server-side request logging on the MCP server showed no corresponding request at the time of failure —
the failure occurs entirely between Claude's backend and Entra, before any call reaches our server.
```

### Additional context

Timeline of elimination during debugging (all Entra-side causes ruled out before concluding this is a Claude-backend issue):

1. Initial `api://` vs `https://` Application ID URI scheme mismatch between PRM `resource` and `scopes_supported` — identified and fixed (Entra error 650053, then 9010010).
2. Verified custom domain (`sanakirja.fi`) added to allow an `https://` Application ID URI, since Entra requires domain verification for non-`api://` schemes.
3. PRM updated to consistently use `https://mcp.sanakirja.fi` across `resource`, `scopes_supported`, and server-side token `audience` validation — confirmed correct via manual OAuth flow and a working `curl` test with a manually obtained token.
4. PRM caching investigated and ruled out: initial PRM response had no `Cache-Control` header; added `Cache-Control: no-store, no-cache, must-revalidate` and `Pragma: no-cache`; confirmed via direct GET header inspection; issue persisted after a full connector removal and re-add.
5. OAuth2 consent grant object inspected directly via Microsoft Graph (`oauth2PermissionGrants`) — confirmed `resourceId` correctly points to the MCP server's service principal and `scope` correctly lists `mcp.tools.invoke` (bare scope names are expected here; the grant object doesn't encode the Application ID URI scheme, so it cannot be a source of staleness).
6. Cross-referenced against Microsoft's own official documentation for App Service + Entra MCP authorization (`WEBSITE_AUTH_PRM_DEFAULT_WITH_SCOPES`), which documents and recommends a working configuration using a **different** scheme between `resource` (HTTPS) and `scope` (`api://`) than what we ultimately used — confirming Entra itself tolerates this combination fine with other MCP clients (e.g. VS Code), further pointing at Claude's backend request construction rather than an Entra-side constraint.

Happy to provide additional Entra sign-in log exports, PRM response captures, or a temporary read-only test connector if that helps reproduce this on Anthropic's side.

**How this differs from other open Entra-related reports in this repo**

Several other issues describe claude.ai failing against Entra ID-backed MCP servers, but each involves a different failure point earlier in the flow than what we're seeing here:

- **#632** and **#754** both report that Claude's backend *never calls `/token` at all* after a successful interactive Entra sign-in (confirmed via DevTools/server logs showing zero requests to the token endpoint). In our case, the initial code exchange **does** succeed — the connector briefly shows as connected — and the failure is a distinct, automated follow-up request several seconds later.
- **#786** is further upstream still: it never reaches Microsoft's login screen at all, apparently because Entra's OIDC discovery omits `code_challenge_methods_supported`. Not applicable here, since our flow gets through login successfully.

So this appears to be a fourth, distinct failure mode in the same general area (Claude.ai custom connectors + Entra ID as external IdP): initial auth succeeds, but a subsequent automated request from Claude's backend sends a resource/scope pairing Entra rejects.

**Related issue in `anthropics/claude-code`**

`anthropics/claude-code#55993` ("Custom MCP connector OAuth fails with Azure DevOps Remote MCP Server (AADSTS9010010 resource/scope mismatch)") reports the same Entra error code and a similar resource/scope construction issue, though against Microsoft's first-party Azure DevOps MCP server rather than a self-hosted one, and via a different Claude client surface. Given the shared error code and underlying mechanism (resource and scope parameters that Entra considers inconsistent), this may be the same root cause manifesting in two different repos/clients — worth cross-referencing if a maintainer picks this up.

**Related Issues**

- #228 — OAuth token refresh never attempted for custom connectors via `mcp-proxy.anthropic.com`
- #247 — Claude Enterprise — Custom Connector OAuth Token Not Refreshing
- #82 — Claude.ai ignores `authorization_endpoint`/`token_endpoint` from OAuth metadata when MCP server uses external authorization server
- #632 — Custom connector cannot authenticate to first-party Microsoft Entra MCP servers — OAuth succeeds but Claude never exchanges the code at /token
- #754 — Custom connector for Entra ID-backed MCP server: OAuth sign-in succeeds, but Claude never exchanges the authorization code at /token
- #786 — Custom connector never redirects to authorization_endpoint when the AS (Microsoft Entra ID) omits `code_challenge_methods_supported` (closed)
- `anthropics/claude-code#55993` — Custom MCP connector OAuth fails with Azure DevOps Remote MCP Server (AADSTS9010010 resource/scope mismatch)

Contributor guide

No contributing guide indexed for this repository

Research direction

No repository file or test is named; start with the mcp-proxy.anthropic.com bridge layer, the automated /token follow-up, and the Entra sign-in log entry showing error 9010010. Compare the resource and scope values used after the successful authorization_code exchange with the server's Protected Resource Metadata, and consider related issues #228, #247, #632, and anthropics/claude-code#55993. Done means the follow-up request no longer sends a mismatched resource/scope pair.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, azure, kubernetes, typescript
Domain
authentication, backend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.