quickoauth
← Guides

Debugging "unable to connect": a failure map

Where MCP OAuth connections actually break, ordered by how often they happen, with the test that isolates each one.

generic · updated 2026-08-22


“Unable to connect” is one error message covering at least seven distinct failures. Work down this list — it’s ordered by frequency, and each entry has a test that gives you a definite answer.

1. Hostname not publicly resolvable

The most common failure for self-hosted servers, and invisible in your logs because no request ever arrives.

  • Tailscale Funnel (*.ts.net), split-horizon DNS, and LAN-only names all fail the connector’s DNS pre-flight even when the URL opens fine in your own browser.
  • Test: dig +short yourdomain.com @1.1.1.1 — no answer means no connector.
  • Fix: a real domain with a public record. A $10 domain pointed at the same box beats any tunnel hack.

2. Certificate not from a public CA

Self-signed certs and internal CAs fail the TLS pre-flight.

  • Test: curl -sI https://yourdomain.com/mcp -o /dev/null -w '%{http_code}\n' from a machine outside your network, no -k flag.
  • Fix: Caddy or a Cloudflare-proxied record — both give you a publicly trusted cert with zero maintenance.

3. Wrong response to the unauthenticated probe

claude.ai’s first MCP call carries no token on purpose. Your server must return 401 with a WWW-Authenticate header naming the resource metadata URL — or, for a no-auth server, just answer the request.

  • Test: curl -si -X POST https://yourdomain.com/mcp | head -5
  • A 403, a redirect to a login page, or an HTML error page all kill discovery.

4. Metadata documents missing or inconsistent

  • /.well-known/oauth-protected-resource resource field must cover the MCP URL — the exact endpoint URL or its origin both pass; anything that doesn’t prefix-match it fails.
  • /.well-known/oauth-authorization-server must list S256 under code_challenge_methods_supported and advertise a registration_endpoint.
  • Test: curl both and read them. Field-by-field expectations are in the connector checklist.

5. Dynamic client registration rejected

claude.ai POSTs to your registration_endpoint and needs a client_id back. Providers with DCR disabled (Auth0 default) or absent return 403 or 404 here.

  • Test: the /register curl in the checklist.
  • Fix: a proxy that answers /register itself — the Auth0 proxy guide shows the pattern.

6. Token audience mismatch

The browser flow completes, consent succeeds, and then every tool call gets 401. Almost always: your server validates aud against a value the token doesn’t carry, because the /authorize redirect didn’t pass audience (or RFC 8707 resource) to the provider.

  • Test: decode the access token at the server (jwt.io or jq -R 'split(".")[1] | @base64d | fromjson') and compare aud with what your validator expects.

7. Streamable HTTP transport mismatches

Older servers speaking only SSE, or servers that reject Accept: application/json, text/event-stream, connect and then fail on the initialize round-trip.

  • Test:
    curl -s https://yourdomain.com/mcp -X POST \
      -H 'content-type: application/json' \
      -H 'accept: application/json, text/event-stream' \
      -H 'authorization: Bearer YOUR_TOKEN' \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
    You want an initialize result, JSON or SSE-framed.

Still stuck after all seven? The MCP Inspector (npx @modelcontextprotocol/inspector) runs the same handshake with a full request log on screen — the fastest way to see which step diverges.

Metadata

Commit
650e1b8
Browser
Current Time
Dimensions
Source
guides
Last Updated
2026-08-22