cd /news/agent-protocols/mcp-server-returns-401-how-to-debug-… · home › topics › agent-protocols › article
[ARTICLE · art-146094] src=contextiq.trango-compute.com ↗ pub= topic=agent-protocols verified=true sentiment=· neutral

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

Remote MCP server deployments most commonly fail with a 401 because of OAuth discovery problems rather than token problems, according to a debugging guide covering the four-step discovery chain defined by RFC 9728 protected resource metadata and RFC 8414 authorization server metadata. The guide instructs operators to verify that the 401 response carries a WWW-Authenticate header with an absolute resource_metadata URL, that the protected resource metadata returns JSON with a matching resource value and authorization_servers list, and that the authorization server metadata exposes authorization_endpoint, token_endpoint and registration_endpoint before the OAuth authorization code flow begins. Common failures include missing WWW-Authenticate headers, http:// or relative resource_metadata URLs, 403 responses instead of 401, SPA catch-all routes returning HTML with a 200 status, and resource values that mismatch the connected server URL.

by read6 min views1 publishedOct 6, 2026

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.

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 aWWW-Authenticate header that points toprotected 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 theauthorization_endpoint ,token_endpoint , and, if the client wants to self-register, theregistration_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:

Symptom Cause Fix
401 with noWWW-Authenticate Auth middleware rejects before your challenge logic runs Emit the header from the same layer that returns the 401
resource_metadata ishttp:// TLS terminates at a proxy and the app builds URLs from the internal scheme Trust X-Forwarded-Proto or hard-code the public origin
resource_metadata is a relative path Header built from a route name Use an absolute URL
403 instead of401 Gateway treats missing credentials as forbidden Return 401 for missing or invalid credentials; reserve403 for insufficient scope
200 with an HTML login page A web-app auth redirect is sitting in front of the API route Exempt 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 returnsindex.html with200 for every unknown path. The status looks fine; the body is HTML, so JSON parsing fails. Always check thecontent-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 yourresource_metadata parameter must point to wherever you actually serve it.
  • resource doesn't match the server URL. Clients compare the metadata'sresource to the URL they connected to and reject a mismatch. A trailing slash or a different hostname (internal vs public) is enough.
  • Redirects. A301 fromhttp tohttps , or from a bare domain towww , 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 andtoken_endpoint present and reachable.
  • code_challenge_methods_supported containingS256 . MCP clients use PKCE, and a client that finds no S256 support may refuse to proceed. (If PKCE is new to you, seeWhat 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 theinitialize call. A200 with aserverInfo object means auth works.
  • Follow with tools/list . Ifinitialize 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 theresource 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 aWWW-Authenticate: Bearer resource_metadata=... header?
  2. Is resource_metadata an absolutehttps:// 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 doescode_challenge_methods_supported includeS256 ?
  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 theresource value?

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

Follow Trango Compute on LinkedIn

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

Follow on LinkedIn

── more in #agent-protocols 4 stories · sorted by recency
── more on @mcp 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/mcp-server-returns-4…] indexed:0 read:6min 2026-10-06 · —