quickoauth
← All guides

The endpoints claude.ai actually probes before connecting

A verified checklist of every discovery request claude.ai makes when you add a remote MCP server with OAuth, and what each response must contain.

Updated

On this page

When you add a custom connector, claude.ai runs a discovery sequence before it ever shows your consent screen. If any step fails, you get a generic “unable to connect”, with no server-side log line, because some checks never reach your server. This checklist is the sequence, verified against a live connector.

0. DNS and TLS pre-flight

Before any HTTP request, the connector backend resolves your hostname and validates the certificate chain.

  • The hostname must resolve on public DNS. Tailscale Funnel (*.ts.net) hostnames fail this pre-flight even though they are reachable from a browser, but the connector is rejected before a single request is made.
  • Use a real domain with a public A/AAAA or CNAME record and a certificate from a public CA (Let’s Encrypt via Caddy or Cloudflare-managed certs both work).

1. POST /mcp without a token → 401 with WWW-Authenticate

claude.ai first calls your MCP endpoint unauthenticated. A protected server must answer 401 with a WWW-Authenticate header pointing at your protected resource metadata:

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

If you return 403, 404, or a 200 with an error body, discovery stops here. (A server that needs no auth should just answer the MCP request; claude.ai supports no-auth connectors too.)

2. GET /.well-known/oauth-protected-resource

Protected resource metadata (RFC 9728). Minimum viable body:

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

resource must cover the URL the client is calling: the exact endpoint URL works, and so does its origin; a live passing deployment serves "resource": "https://yourdomain.com" for an endpoint at /mcp. What fails is a value that doesn’t prefix-match the MCP URL at all.

3. GET /.well-known/oauth-authorization-server

Authorization server metadata (RFC 8414), fetched from each entry in authorization_servers. Required fields in practice:

{
  "issuer": "https://yourdomain.com",
  "authorization_endpoint": "https://yourdomain.com/authorize",
  "token_endpoint": "https://yourdomain.com/token",
  "registration_endpoint": "https://yourdomain.com/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"]
}

code_challenge_methods_supported must include S256; the client uses PKCE unconditionally.

4. POST /register: dynamic client registration

claude.ai registers itself as an OAuth client (RFC 7591). It sends its redirect_uris and expects a client_id back. If you use a provider that does not support open DCR (Auth0 requires enabling it, some providers lack it entirely), put a thin proxy in front that handles /register itself; see the Auth0 proxy guide.

Redirect URIs to allow: https://claude.ai/api/mcp/auth_callback and https://claude.com/api/mcp/auth_callback.

5. Browser flow: /authorize → callback → POST /token

Standard authorization-code + PKCE exchange. Two failure points worth checking:

  • The state parameter must round-trip unmodified.
  • The token response must be application/json with access_token, token_type: "Bearer", and expires_in.

6. POST /mcp with the token

The original MCP initialize request, now with Authorization: Bearer …. Your server validates the token (issuer, audience, expiry) and answers the MCP handshake. From here on it’s plain MCP over Streamable HTTP.

Quick self-test

# 1. unauthenticated call returns 401 + WWW-Authenticate
curl -si https://yourdomain.com/mcp -X POST | head -5

# 2-3. metadata endpoints return JSON
curl -s https://yourdomain.com/.well-known/oauth-protected-resource | jq .
curl -s https://yourdomain.com/.well-known/oauth-authorization-server | jq .

# 4. DCR accepts a registration
curl -s https://yourdomain.com/register -X POST \
  -H 'content-type: application/json' \
  -d '{"redirect_uris":["https://claude.ai/api/mcp/auth_callback"],"client_name":"test"}' | jq .

All four green and the connector dialog will get you to a consent screen.

Next steps

All four curls green? Then:

  1. Add the connector in claude.ai under Customize → Connectors and complete the login; you should hit your consent screen and see the tools list populate.
  2. Make the same server work for ChatGPT and Grok with one server, three connectors.
  3. If anything is red or the dialog spins, work through debugging connector failures in order.

Still choosing an authorization server? Each has a walkthrough: Auth0, Stytch, Supabase, or self-hosted Hydra.

Put it into practice

Diagnose a failing claude.ai connector

A systematic run of the seven checks that isolate where an MCP OAuth connection breaks.

Preview prompt
My remote MCP server at https://MY_DOMAIN/mcp shows "unable to connect" when I add it as a claude.ai custom connector. Run these checks in order, report the result of each, and stop at the first failure with a diagnosis and fix:

1. DNS: dig +short MY_DOMAIN @1.1.1.1, which must return a public A/AAAA/CNAME answer. (Tailscale *.ts.net names and LAN-only DNS fail claude.ai's pre-flight even if the URL works in my browser.)
2. TLS: curl -sI https://MY_DOMAIN/mcp without -k; the certificate must chain to a public CA.
3. Unauthenticated probe: curl -si -X POST https://MY_DOMAIN/mcp | head -5; expect 401 with a WWW-Authenticate header naming the resource metadata URL (or a valid MCP response if the server is intentionally no-auth). 403, 404, redirects, or HTML mean discovery dies here.
4. Protected resource metadata: curl https://MY_DOMAIN/.well-known/oauth-protected-resource; the "resource" field must exactly equal https://MY_DOMAIN/mcp or its origin https://MY_DOMAIN (a prefix of the MCP URL); anything else fails.
5. Authorization server metadata: curl https://MY_DOMAIN/.well-known/oauth-authorization-server; must include authorization_endpoint, token_endpoint, registration_endpoint, and "S256" in code_challenge_methods_supported.
6. DCR: POST to the registration_endpoint with {"redirect_uris":["https://claude.ai/api/mcp/auth_callback"],"client_name":"probe"}; expect a JSON body containing client_id.
7. Handshake: POST an MCP initialize request to /mcp with accept: application/json, text/event-stream (use a token from the flow if auth is on); expect an initialize result, JSON or SSE-framed.

For whichever step fails, explain what claude.ai's discovery expected at that step, what my server returned instead, and the minimal change to fix it. If all seven pass, run npx @modelcontextprotocol/inspector against the endpoint and compare its handshake log with the checks above.

Use this site from your agent

Add this URL as a connector in Claude, ChatGPT, or Grok. No sign-in required.

https://quickoauth.com/mcp
Explore more guides →

Search prompts & guides

24 results