Authorization with the MCP server failed
A token was issued and the client says it is connected, but the credential never reaches your server intact: either the client does not attach it, or it carries an audience your server is right to reject.
- MCP Specification
- 2026-07-28
Is this you?
Replay the exact token your server was handed. Take it from your token endpoint's log, and POST it to the URL you registered with the client — not the URL your server thinks it lives at. One response separates a credential the client never sent from a credential your server refuses.
- This error
- Two shapes. A
301,302,307or308with aLocationon a different host — the hop that drops yourAuthorizationheader. Or401withWWW-Authenticate: Bearer error="invalid_token": the token was minted for someone else. Decode it and compareaudandissagainst the canonical MCP server URL and the issuer you advertise. - Then the fix is the next section.
- Working
- Anything that is not an auth challenge.
HTTP/1.1 200with a JSON-RPCresulton a stateless server; on a server that keeps sessions, a400complaining about a missingMcp-Session-Id— thiscurlskipsinitialize, and the spec has such servers answer400. Either way your validator accepted the token, so the credential is being lost on the way in rather than rejected on arrival. Look at the redirect and at what the client attaches, not at your validator. - Then this is not your problem — try the error reference.
6 other strings clients print for this
- Authorization with the MCP server failed. You can check your credentials and permissions. If this persists, share this reference with support: "ofid_..."
- UnauthorizedError: Unauthorized
- {"error":"invalid_token","error_description":"Authentication required"}
- Missing Authorization header
- AADSTS9010010
- invalid_target
Fix it
Replay an access token your authorization server issued against the exact URL you registered with the client. If that URL answers a 301, 302, 307 or 308 pointing at a different origin, register the redirect target instead — standard HTTP clients drop Authorization on a cross-origin hop. If it answers 401 with error="invalid_token", the token did arrive: decode it and check aud and exp before anything else. If aud names another service, audience-restrict the token at the authorization server to the canonical MCP server URL the client sends as the RFC 8707 resource parameter, rather than loosening the aud check in your server. If the replay succeeds and the server works only for whoever added the connector, nothing is broken there — each user has to authorize separately.
Have your agent fix this
Copies a prompt that sends your coding agent to this page, then has it find and apply the right fix for your project.
The cause depends on your platform. Pick yours.
Where is the token being lost?
Claude custom connectors
If the connector was added in Claude.ai, Claude Desktop or Cowork and reports itself connected
- 1 Claude follows redirects, so a URL that answers
301,302,307or308to a different host — apex towww., region routing, a vanity domain in front of a CDN — loses theAuthorizationheader on the hop. Anthropic documents this as a cause ofAuthorization with the MCP server failed, and as the reason the same server works from Claude Code and MCP Inspector: local clients fail fast on a redirect instead of following it. Register the URL your server actually listens on. - 2 Claude sends the RFC 8707
resourceparameter on both the authorization and the token request, set to the canonical form of your MCP server URL: lowercase scheme and host, no trailing slash, no fragment, no default port, path included. Issue tokens with that audience, and compareaudagainst the canonical value rather than byte-comparing what the user typed. - 3 The
resourcefield in your protected resource metadata must match the MCP server URL exactly as the user enters it in Claude, path included. Ifauthorization_serverslists more than one issuer, Claude uses the first entry and does not fall back to later ones. - 4 Each user authorizes for themselves. On Team and Enterprise plans only an Owner can add a connector, from Admin settings, and every member then finds it under Settings, Connectors and clicks Connect to authenticate individually — so a connector that works only for the person who set it up is documented behaviour, not a bug.
- 5 Claude re-runs OAuth only on a transport-level
401. A200carryingisError: trueis handed to the model as a tool result and produces no auth prompt, which is how anonymous tool calls repeat indefinitely. Claude also refreshes reactively on a401, so returninvalid_grant— notinvalid_requestor a custom code — when a refresh token is dead.
ChatGPT connectors
If the connector was added in ChatGPT or Codex
- 1 ChatGPT appends
resource=<the resource value from your protected resource metadata>to both the authorization and the token request. OpenAI's own documentation tells you to configure the authorization server to copy that value into the access token, commonly theaudclaim. If your identity provider ignores the parameter, every token you get back carries the provider's default audience and your server is correct to reject it. - 2 ChatGPT will not show its linking UI unless both halves are in place: per-tool
securitySchemesin the tool definition (oauth2with the scopes you need, ornoauth) and a runtime error result carrying_meta["mcp/www_authenticate"]with both anerrorand anerror_description. With only one half, protected tools keep being called anonymously and no sign-in is ever offered. - 3 Register the production redirect URI
https://chatgpt.com/connector/oauth/{callback_id}shown on the app management page. Apps published before the change keep working on the legacyhttps://chatgpt.com/connector_platform_oauth_redirect. - 4 There is no non-OAuth fallback to reach for. ChatGPT does not support machine-to-machine grants — client credentials, service accounts, JWT bearer assertions — and cannot present a customer-supplied API key or mTLS certificate. It does present an OpenAI-managed client certificate whose SAN
dnsNameismtls.prod.connectors.openai.com, if you want to identify the client at the transport layer. - 5 Verification is entirely yours: check the signature and
issagainst the authorization server's JWKS, reject onexpandnbf, confirmaudor theresourceclaim names your server, then enforce scopes. On failure return401with aWWW-Authenticateheader pointing back at your protected resource metadata.
Microsoft Entra ID
If your authorization server is Microsoft Entra ID and the token request fails with AADSTS9010010 or invalid_target
- 1 Entra is refusing the
resourcevalue the client sent because it matches no Application ID URI on your app registration. The defaultapi://{client-id}is not sufficient here: the client sends the full MCP server URL, path included. - 2 In the Microsoft Entra admin center, open the app registration that represents the protected API — not the OAuth client, if those are separate registrations. Under Expose an API, add your MCP server URL as an additional Application ID URI (
identifierUrisin the manifest). It must match exactly, path included, without a trailing slash. - 3 If something in front of your server validates the audience for you — Azure App Service Authentication, for instance — add the API app's Application (client) ID and its
api://URI to Allowed token audiences. That is a different list from Allowed client applications. - 4 If the OAuth client and the API are separate registrations, confirm the client holds an admin-consented API permission for the scope your API exposes.
VS Code
If the server is configured in mcp.json and VS Code keeps re-sending a token your server rejects
- 1 VS Code caches both the access token and the dynamically registered
client_id. When that credential goes stale — expiry, a rotated client, a restarted server — a401from a previously authorized server does not trigger a refresh, a re-registration, or fresh discovery. VS Code re-sends the same token and the server stays in a failed state. Reported as microsoft/vscode#321834 and still open; no fix has shipped. - 2 Recover by hand: run
Authentication: Remove Dynamic Authentication Providersfrom the Command Palette, then restart the server fromMCP: List Servers. That forces a fresh registration and OAuth flow. - 3 For GitHub or Microsoft Entra authorization servers, check which servers hold your account under Manage Trusted MCP Servers in the Accounts menu. A token bound to the wrong account is indistinguishable from a wrong audience when you are only reading your server's logs.
- 4 Your authorization server's redirect URI list must include both
http://127.0.0.1:33418andhttps://vscode.dev/redirect. VS Code tries dynamic client registration first and falls back to a client-credentials workflow if the identity provider does not support it. - 5 Read VS Code's own account of the exchange before touching your validator:
MCP: List Servers, pick the server, choose Show Output.
Cursor
If Cursor flips back to Needs authentication after a successful sign-in, and you are working over Remote SSH
- 1 Check your version first: Cursor staff attributed this to Remote SSH specifically — the callback completes on the local side while the MCP extension runs on the remote host, so the saved tokens never reach the remote extension host — and said it was fixed in v3.3. If you are past that, this branch is not your problem.
- 2 The signature, from the forum report: after a successful flow the client logs
Saving tokensandOAuth authorization completed, reloads, then immediately begins a new flow (Saving PKCE code verifier) and ends atUnauthorizedError: Unauthorized. The server sees only discovery requests — no POST ever arrives carrying the bearer. A separate report on the same forum covers Cursor not refreshing MCP access tokens at all. - 3 The workaround staff gave for the Remote SSH case is to skip the browser flow entirely on the remote host: an API key in a header, from an environment variable.
- 4 Confirm what Cursor actually sent before changing anything server-side: Output panel, then MCP Logs in the dropdown.
- 5 If your provider issues a fixed client, skip dynamic registration. Add an
authobject to the remote server entry withCLIENT_ID, an optionalCLIENT_SECRETfor confidential clients, andscopes. Omitscopesand Cursor readsscopes_supportedfrom/.well-known/oauth-authorization-server. - 6 Cursor's redirect URIs are fixed per surface:
https://www.cursor.com/agents/mcp/oauth/callbackfor web and Cursor Agents,http://localhost:8787/callbackfor the desktop app. Register both if your users authenticate from both, since the server is identified through the OAuthstateparameter rather than a per-server callback. - 7 There is no documented RFC 8707
resourcebehaviour for Cursor, so do not assume the token you get back is audience-restricted to your server. Validateissandaud, and read what actually arrives.
MCP SDKs (server side)
If you build the server on an official MCP SDK and want the audience check and the 401 to be right
- 1 Neither official SDK checks the audience for you. The Python SDK hands you a
TokenVerifierwhoseverify_tokenreturns anAccessTokenorNone; the example in the SDK docs is a stub with no verification at all. Compare the token's audience against theresource_server_urlyou set inAuthSettingsand returnNoneon a mismatch. - 2
AccessToken.resourceis the RFC 8707 resource indicator — populate it, so nothing downstream has to re-derive which resource the token was minted for. - 3 The SDK's
RequireAuthMiddlewarethen answers401withWWW-Authenticate: Bearer error="invalid_token", error_description="Authentication required", plus aresource_metadataparameter when you configureresource_metadata_url. Configure it: without the pointer the client has to fall back to probing well-known paths. - 4 In the TypeScript SDK the same job is
requireBearerAuth({ verifier, requiredScopes, resourceMetadataUrl }), and yourOAuthTokenVerifier.verifyAccessTokenmust throw anOAuthErrorwithOAuthErrorCode.InvalidTokenfor a wrong audience. Note it also rejects any token whoseexpiresAtis unset, so populate it from the introspection response or the JWTexp. - 5 Keep the status codes straight:
401witherror="invalid_token"for a missing, expired or wrong-audience token;403witherror="insufficient_scope"and ascopeparameter only for a scope shortfall. Never answer200with a tool error that says "please sign in" — clients pass that to the model as a result and no auth prompt appears.
Something else
Two questions settle this for any client and any identity provider, and the curl above answers both. If the credential never arrived, find the hop that rewrote the URL — a redirect to www., a region router, a vanity domain in front of a CDN — and register the host that actually serves /mcp, because an HTTP client drops Authorization when a redirect crosses origins. If it arrived and you rejected it, decode it: the MCP specification requires you to accept only tokens issued for you as the audience, so make the client send the RFC 8707 resource parameter naming your canonical MCP server URL on both the authorization and the token request, and make the authorization server audience-restrict to that value — a custom audience, an API registration, a resource identifier, whatever it calls the setting. Fix the audience where the token is minted, never by widening the aud check in your server.
Why it happens
Three different failures leave the same shape in your access log. A cross-origin redirect on the MCP endpoint — the client attaches Authorization, follows the 3xx, and standard HTTP client behaviour drops the header, so the redirect target logs an unauthenticated request. An audience mismatch — the client sends the RFC 8707 resource parameter naming your canonical MCP server URL, the authorization server ignores it and mints a token for its own default audience, and your aud check correctly refuses it. Per-user credential scoping — each user authorizes a connector for themselves, so a server that works for whoever set it up and returns 401 to everyone else is doing what the client documents.
The exchange
Each step in the exchange, in order.
- MCP client holds a valid token
- Redirect at the edge apex to www.
- MCP server the redirect target
-
MCP client to Redirect at the edge
POST /mcp Authorization: Bearer eyJ... -
A redirect that crosses to a different origin makes an HTTP client drop the Authorization header. The retried request is the one your logs show, and it carries no credential.
Redirect at the edge to MCP client
308 Permanent Redirect Location: www.example.com/mcp -
MCP client to MCP server
POST /mcp (retried) no Authorization header -
The same 401 appears when the token does arrive but was minted for another audience. Compare the aud claim against the canonical MCP server URL before you go looking for a redirect.
MCP server to MCP client
401 Unauthorized error="invalid_token"Flow stops here
What lands on you next
A token now arrives on every call. What it says is who the caller is — which is less than it sounds, and it is the last thing this page can tell you.
-
A valid token is a yes-or-no bit. Some callers should get less.
-
require-user-claims-inboundRules over the caller's claims — groups, roles, tenant, service-account subject — decide whether the call proceeds, with a 403 when it does not.
-
Which tools should exist for this caller in the first place?
-
mcp-capability-filter-inboundA per-route allow-list of tools, prompts and resources. A capability the route does not expose is refused with
MethodNotFoundbefore the request is forwarded. -
Is this token meant for us, or for some other service?
- RFC 8707 resource indicators
A gateway-issued token is bound to one route's canonical resource URI, so a token minted elsewhere is rejected here rather than accepted because it parses.
-
What did the agent do with it?
-
capability_invocationEvery tool call emits an event carrying the caller, the capability, the upstream, the outcome and the latency. Tokens are never logged.
All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out.
Stop debugging auth on every MCP server
A gateway in front of your MCP servers handles discovery, audience binding, and token validation once — for every server and every client.