An “Unauthorized” error from a remote MCP server doesn’t necessarily mean your credentials are wrong. The first 401 may be the server asking the client to start an OAuth login. If the client has already logged in and sent an access token, that same status points to a different problem.
Before rotating keys or signing in again, find the request that failed. Was it the first call to /mcp, the token exchange, or a tool calling another API? That distinction will save you a lot of guessing.
The examples here cover OAuth on remote HTTP servers, using the 2026-07-28 MCP revision. Local stdio servers handle credentials differently. Check your client and SDK versions before copying anything; older implementations may use an earlier flow. Replace the example URLs with your own. The snippets are diagnostic examples, not a tested integration for a particular desktop client.
The debugging path follows the request from the first 401 through discovery, login, token validation, and retry, with red marking the failure points this guide covers.
Find the last request that worked
Open the client’s network trace or the server’s access logs and look for the last successful request. Keep the request IDs so you can follow the same attempt across services. Remove tokens, authorization codes, and cookies before sharing logs.
Where it stops
What to check first
First request returns 401; login never opens
Authentication challenge and metadata discovery
Metadata request returns HTML or 404
Public routes, reverse proxy, and URL paths
Login page rejects the client
Client registration and redirect URI
Login succeeds; token exchange fails
PKCE verifier, callback configuration, and client identity
Token is issued; MCP still returns 401
Token validation and whether the header reaches your application
Authentication passes; operation returns 403
Required scopes and application permissions
Terminal works; browser client fails
Preflight response and exposed response headers
One easy mix-up: a tool can connect successfully, call an upstream API, and return that API’s authorization error. If the failure is inside a tool result, investigate the downstream credentials first.
Start with the response headers
A protected endpoint can answer an unauthenticated request with this challenge:
Look for WWW-Authenticate. A body containing only Unauthorized gives you very little to work with; this header points the client toward the login configuration.
To see what your server actually returns, save the body of a failing read-only request as mcp-request.json and replay it without credentials:
# Run in Bash. Use your server URL and the captured read-only request body.
MCP_URL='https://mcp.example.com/mcp'
curl --silent --show-error \
--dump-header - \
--output /dev/null \
--header 'Content-Type: application/json' \
--header 'Accept: application/json, text/event-stream' \
--data-binary @mcp-request.json \
"$MCP_URL"
Add the protocol-specific headers from the original request. This command supplies only the common content headers and leaves redirects visible.
Use the original request method. curl -I sends HEAD and may hit a different route. A 400 caused by a missing protocol header won’t tell you whether OAuth works.
Without resource_metadata, the client must fall back to well-known URLs. For /mcp, it tries /.well-known/oauth-protected-resource/mcp, then /.well-known/oauth-protected-resource. Check those before blaming a missing challenge parameter. Discovery requirements
Follow the metadata to the login server
Discovery happens in two hops. Your MCP server identifies the authorization server. That server publishes the endpoints used for login and token exchange.
Fetch the resource metadata URL from the challenge:
Keep this endpoint reachable before login. Otherwise the client needs a token to discover how to get a token. The resource value must identify the resource it’s connecting to. Protected resource metadata, RFC 9728
With https://auth.example.com as the issuer, the next request goes here:
The provider may use OIDC discovery instead. Either way, inspect issuer, authorization_endpoint, and token_endpoint. The issuer must match exactly. Providers with tenant paths need the discovery URL construction described in the spec; appending a suffix to the issuer won’t always produce the right URL. Authorization server discovery
If either metadata URL returns your application’s login page, fix routing first. Look for a catch-all frontend route or authentication middleware covering the metadata endpoint.
A rejected client ID is a registration problem
Your account can be valid while the MCP client’s registration is wrong. Compare the request’s client ID and callback URL with the provider’s registration. Pay attention to the callback path and hostname: localhost and 127.0.0.1 aren’t interchangeable.
MCP 2026-07-28 supports pre-registration and Client ID Metadata Documents (CIMD), plus Dynamic Client Registration (DCR) for compatibility. DCR is deprecated and optional, so a missing registration_endpoint isn’t necessarily an error.
An HTTPS URL used as the client ID is a clue to check CIMD support. If the provider doesn’t support it, use a registration method both sides understand. For DCR with an OIDC provider, desktop clients may also need application_type: "native".
If you’ve changed issuers, reset the affected connection’s registration through the client’s settings. Don’t reuse the old issuer’s client credentials. MCP client registration
Login can work while the code exchange fails
After the browser returns to the client, look for a successful token response. A completed callback alone doesn’t mean the exchange worked.
PKCE ties the authorization request to the code exchange. The client sends a challenge first, then the original verifier when it requests a token. Losing that verifier breaks the exchange. Generating another one at the token step won’t help. PKCE, RFC 7636
Read the provider’s error response before retrying. Check the callback URI and whether another process owns the callback port. If the client runs in a container or on a remote machine, make sure the browser can reach its callback listener.
After fixing the configuration, start a fresh login instead of replaying the captured code.
A token for the wrong API should fail
The client must include the target resource in both the authorization request and the token request. Check that value when a newly issued token still gets a 401. The MCP server must reject a token issued for another resource. MCP resource and token requirements
For JWT access tokens, work through the validator’s checks:
Check
Question
Signature
Is the signature valid under the trusted issuer's keys and allowed algorithm?
Issuer
Did the configured authorization server issue this token?
Audience
Is this MCP resource an intended recipient?
Expiry
Is the access token still valid?
Token type
Is this an access token, rather than an ID token from the login flow?
A decoded JWT payload is only data until you validate it. Use a maintained validator configured for the provider’s access-token profile. For opaque tokens, use the provider’s supported validation mechanism. JWT access-token validation, RFC 9068
Also confirm that Authorization reaches the application. Logging authorization_header_present: true is enough for that check. Keep the token itself out of the logs.
If a tool calls another API, handle that API’s credentials separately. Don’t forward the incoming MCP bearer token to it. MCP token-passthrough guidance
A 403 needs a permission check
When the token is valid but lacks a required scope, the response can look like this:
Bearer authentication uses 401 for invalid or expired tokens and 403 for insufficient scope. Compare what the operation requires with what the token grants. Signing in again with the same permissions won’t resolve a scope mismatch. Bearer-token errors, RFC 6750
Your application still needs its own access checks. reports:read can permit the operation without granting access to every customer’s reports. Restrict the database query to the authenticated user’s tenant.
Test this with two tenants: a valid token for one must not read the other’s report.
On AWS, find out whether the request reached Lambda
For an API Gateway and Lambda deployment, start with the request path:
MCP client -> API Gateway -> authorizer, if configured -> Lambda
Add CloudFront or a WAF if your setup uses them. Match the gateway access logs to the Lambda logs. When there’s no corresponding Lambda invocation, investigate the gateway or the layer in front of it.
Keep the public discovery routes reachable. If an upstream authorizer returns a generic rejection without the MCP challenge, the client still needs working well-known metadata routes.
If AgentCore Gateway fronts a private MCP server instead, it rejects a private CA certificate during the TLS handshake by default, before any token is checked; here’s how I got it to trust a private CA without an ALB.
Browser clients add another place to look: CORS. On HTTP APIs, API Gateway can handle preflight requests itself and override the integration’s CORS headers. If your $default route requires authorization, you may need an explicit unauthenticated OPTIONS /{proxy+} route. AWS HTTP API CORS documentation
Allow the request headers the client sends, and expose the response’s challenge header:
Access-Control-Expose-Headers: WWW-Authenticate
Authorization belongs to the request; WWW-Authenticate belongs to the response. Allowing one doesn’t expose the other. Error responses need the appropriate allowed origin too. A successful curl request won’t catch this problem because curl doesn’t enforce browser CORS. Response-header exposure
For protocol-version and routing failures, see the Lambda MCP migration post. Those can interrupt the connection before OAuth gets a chance to work.
Retest the original connection
Use the client that originally failed, with its version recorded alongside your server SDK and protocol revision:
Test
Evidence to keep
Fresh connection without a token
Client discovers the authorization server and starts login
Successful login
Token exchange completes and a read-only MCP operation succeeds
Expired or wrong-audience token
Protected request is rejected with 401
Valid token lacking a required scope
Operation is denied with the expected scope challenge
Cross-tenant request
Application denies access even with valid authentication
Browser connection, if supported
Preflight passes and the client can read the challenge header
Once those checks pass, reconnect from a fresh session and run the original read-only operation. Keep its request ID with the server logs so the next failure has a useful comparison.
On this post
Comments
A reply stays under the note it answers.
No comments yet.
If you have a note on MCP Authentication Explained: Fix 401 Errors in a Remote MCP Server, sign in and leave it.