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
stateparameter must round-trip unmodified. - The token response must be
application/jsonwithaccess_token,token_type: "Bearer", andexpires_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:
- 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.
- Make the same server work for ChatGPT and Grok with one server, three connectors.
- 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