Trango ComputeContextIQ
MCPOAuth 2.0RFC 9728RFC 8414debuggingClaudeCursorAI agents

MCP Server Returns 401? How to Debug OAuth Discovery (RFC 9728, RFC 8414) for Claude, Cursor, and ChatGPT Connectors

Debug a failing MCP server OAuth flow for Claude, Cursor and ChatGPT: WWW-Authenticate, RFC 9728 and RFC 8414 metadata, PKCE and dynamic client registration.

October 6, 2026Trango Compute Inc.

You deployed a remote MCP server, added it to Claude, Cursor, or a ChatGPT connector, and the client says "authorization failed", "could not connect", or just spins. The server logs show a 401 and nothing else. This is the most common failure in remote MCP deployments, and it is almost always a discovery problem, not a token problem.

Before any token is issued, an MCP client has to discover where to authenticate. That discovery is a chain of four HTTP responses. If any link in the chain is wrong, the client gives up with a vague error. This post walks the chain in order, with a curl command for each link and the failure modes we see most often.

The Discovery Chain

  1. Client sends an unauthenticated request to the MCP endpoint.
  2. Server answers 401 with a WWW-Authenticate header that points to protected resource metadata (RFC 9728).
  3. Client fetches that metadata, which names one or more authorization servers.
  4. Client fetches the authorization server's metadata (RFC 8414, or OpenID Connect discovery) to find the authorization_endpoint, token_endpoint, and, if the client wants to self-register, the registration_endpoint.

Only after step 4 does the OAuth authorization code flow begin. Steps 1–4 involve no user and no credentials, which means you can test every one of them from a terminal.

Step 1: Does the 401 Carry a Usable Challenge?

curl -si -X POST https://mcp.example.com/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'

You want a 401 and a header like:

WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"

Common failures:

SymptomCauseFix
401 with no WWW-AuthenticateAuth middleware rejects before your challenge logic runsEmit the header from the same layer that returns the 401
resource_metadata is http://TLS terminates at a proxy and the app builds URLs from the internal schemeTrust X-Forwarded-Proto or hard-code the public origin
resource_metadata is a relative pathHeader built from a route nameUse an absolute URL
403 instead of 401Gateway treats missing credentials as forbiddenReturn 401 for missing or invalid credentials; reserve 403 for insufficient scope
200 with an HTML login pageA web-app auth redirect is sitting in front of the API routeExempt the MCP path from browser redirects

Step 2: Does the Protected Resource Metadata Resolve?

curl -si https://mcp.example.com/.well-known/oauth-protected-resource

Expect 200, content-type: application/json, and a body like:

{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"]
}

Failures to look for:

  • SPA catch-all. A single-page app or CDN rule returns index.html with 200 for every unknown path. The status looks fine; the body is HTML, so JSON parsing fails. Always check the content-type, not just the status.
  • Path mismatch. If your resource lives at a path (/mcp), RFC 9728 metadata can be served path-suffixed at /.well-known/oauth-protected-resource/mcp. The URL in your resource_metadata parameter must point to wherever you actually serve it.
  • resource doesn't match the server URL. Clients compare the metadata's resource to the URL they connected to and reject a mismatch. A trailing slash or a different hostname (internal vs public) is enough.
  • Redirects. A 301 from http to https, or from a bare domain to www, on the well-known path breaks clients that don't follow redirects on discovery requests.
  • Empty authorization_servers. The array must name at least one issuer.

Step 3: Does the Authorization Server Publish Metadata?

Take the first entry in authorization_servers and fetch both discovery documents. They are two separate documents and a spec-compliant issuer may serve either or both:

curl -si https://auth.example.com/.well-known/oauth-authorization-server
curl -si https://auth.example.com/.well-known/openid-configuration

If your issuer has a path component (https://auth.example.com/tenant1), the RFC 8414 path is inserted between the well-known prefix and the path: /.well-known/oauth-authorization-server/tenant1. OpenID Connect discovery instead appends: /tenant1/.well-known/openid-configuration. Mixing these up is a classic multi-tenant bug (Keycloak realms and Auth0 custom domains both hit it).

Check the response for:

  • issuer exactly equal to the URL you fetched from, with no trailing-slash difference.
  • authorization_endpoint and token_endpoint present and reachable.
  • code_challenge_methods_supported containing S256. MCP clients use PKCE, and a client that finds no S256 support may refuse to proceed. (If PKCE is new to you, see What is PKCE.)

For the difference between an OpenID Connect provider and bare OAuth 2.0 here, and why it matters, see Is Your MCP Server Using OpenID Connect or Just OAuth 2.0.

Step 4: Can the Client Register Itself?

Hosted clients like Claude and ChatGPT connectors typically don't have a pre-provisioned client ID for your server. They rely on dynamic client registration (RFC 7591) via a registration_endpoint in the authorization server metadata, unless you give them a client ID manually.

If registration_endpoint is absent, the client has two options: use a client ID you supplied in its connector settings, or fail. Many "authorization failed" reports end here. Either add dynamic registration at the authorization server, or document the client ID and redirect URI each client must use.

Also verify the redirect URIs your authorization server allows. Each client has its own callback URL, and an allowlist containing only localhost will pass your manual tests and fail every hosted client.

After Discovery: Quick Sanity Checks

Once the four steps return clean responses and you can obtain a token, confirm the server accepts it:

  • Send the token as Authorization: Bearer <token> to the MCP endpoint and repeat the initialize call. A 200 with a serverInfo object means auth works.
  • Follow with tools/list. If initialize succeeds but tools are empty, the problem is scope or tool registration, not authentication.
  • If the token is accepted by the authorization server but rejected by the MCP server, check the aud claim against the resource value from step 2. Audience mismatches are the usual cause.

Run the Whole Chain in One Request

Doing this by hand is useful once. For regular checks, MCP Inspector runs the handshake against a URL, detects OAuth-protected servers from the WWW-Authenticate challenge and RFC 9728 metadata, and enumerates tools/list where the server allows it. It tests from outside your network with no cookies, which is exactly the position a hosted client is in, so it catches the proxy, redirect, and catch-all failures that don't show up when you test from your own machine.

Debugging Order Cheat Sheet

  1. Is the response a 401 with a WWW-Authenticate: Bearer resource_metadata=... header?
  2. Is resource_metadata an absolute https:// URL?
  3. Does it return JSON (not HTML) with a resource matching your MCP URL?
  4. Does authorization_servers[0] serve RFC 8414 or OIDC metadata at the correct path?
  5. Does issuer match exactly, and does code_challenge_methods_supported include S256?
  6. Is there a registration_endpoint, or a documented client ID for hosted clients?
  7. Do the registered redirect URIs include each hosted client's callback?
  8. Does the token's aud match the resource value?

Work down the list and stop at the first "no". That is your bug.

Try ContextIQ free

Free tools for AI engineers.

Follow Trango Compute on LinkedIn

We post updates on new tools, context engineering patterns, and LLM cost research.

Follow on LinkedIn