Zuplo
MCP error

Couldn't reach the MCP server

One toast covers four different failures. Claude Code and `curl` reach your server from your machine; a claude.ai connector reaches it from Anthropic's infrastructure over the public internet, so the request dies at whichever hop only exists on that path — DNS, your edge, a redirect, or OAuth discovery.

MCP Specification
2026-07-28

Is this you?

Four causes, four checks, in the order Anthropic's own diagnostic checklist runs them. Run all of them from a network outside your own — a check that passes on your VPN proves nothing about the hosted path, which is the entire problem.

TerminalRun this to check — it changes nothing
# 1. Public DNS. Every address must be globally routable, and there must be
#    an A record: claude.ai connectors are IPv4-only.
dig +short A mcp.example.com

# 2. The registered URL must not redirect to another host — the
#    Authorization header does not survive that hop.
curl -sI https://mcp.example.com/mcp | grep -Ei '^(HTTP/|location:)'

# 3. The endpoint has to answer from a public network. A 401 or 405 is fine.
curl -i -sS -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":"tools/list"}'

# 4. If the server requires auth, discovery has to answer as well. Append
#    your MCP path to the protected-resource well-known.
curl -i https://mcp.example.com/.well-known/oauth-protected-resource/mcp
This error
dig returns nothing, returns only AAAA, or returns an address in 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, loopback or link-local — and a mix of routable and non-routable answers fails the same way. A 301, 302, 307 or 308 to a different host. 403 or 429 in your edge or CDN logs during the Connect attempt that your application never generated. 404 on the protected-resource document and on both authorization-server documents, which leaves Claude nothing to read.
Then the fix is the next section.
Working
One or more A records, every one of them globally routable. No 3xx whose Location points at another host. Any HTTP response at all from the POST — a 401 or a 405 is fine, a timeout or connection refused is not. 200 and valid JSON from /.well-known/oauth-protected-resource/mcp, whose resource field matches the URL exactly as it is entered in Claude, and 200 from one of /.well-known/oauth-authorization-server or /.well-known/openid-configuration on the issuer host your authorization_servers names first. A 404 on the other one is expected.
Then this is not your problem — try the error reference.
5 other strings clients print for this
  • Authorization with the MCP server failed
  • Works in Claude Code or curl but not claude.ai
  • works in MCP Inspector or Claude Code CLI but not claude.ai
  • Custom connector "Couldn't reach the MCP server" — zero inbound traffic at edge, server verified reachable
  • AADSTS9010010 (sometimes surfaced as invalid_target)

Fix it

Identify the hop before you change anything. There are four, and this is the order Anthropic's own checklist runs them in. Public DNS: Claude resolves your hostname and validates every answer before it sends a request, so it rejects a private (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), carrier-grade NAT (100.64.0.0/10), loopback or link-local address, a mix of routable and non-routable answers, and a host with no A record — connectors are IPv4-only, so an AAAA-only hostname cannot be reached at all. Your edge: find the 403 or 429 in your CDN or WAF logs that your application never generated, and allowlist Anthropic's outbound range, 160.79.104.0/21. A redirect: curl -sI the URL you registered, because a 3xx to another host drops the Authorization header and the failure then surfaces as "Authorization with the MCP server failed" instead. Discovery: confirm your /.well-known documents answer, including on a cross-host authorization server, which Anthropic reaches from that same range. The failure toast and the page URL carry a reference id starting ofid_; file it with your server URL and your access logs on anthropics/claude-ai-mcp, because it is what lets Anthropic trace the request server-side, and the ids are time-limited.

Have your agent fix this

Copies a prompt that sends your coding agent to this page, then has it find and apply the right fix for your project.

The cause depends on your platform. Pick yours.

Claude custom connectors

If your server's access log shows nothing at all during the Connect attempt

  1. 1 Claude resolves the hostname and validates the result before it makes any request, so a rejected name produces no traffic anywhere you can see. It rejects private, carrier-grade NAT, loopback and link-local addresses, any mix of public and non-public answers, and a hostname with no A record from public DNS — connectors are IPv4-only, so an AAAA-only host has nothing to dial.
  2. 2 Publish the server on a globally routable IPv4 endpoint: a host with a public IP, a public reverse proxy, or a tunnel. An Anthropic MCP tunnel does not close this gap for claude.ai — the docs state that tunnels created through the Console are not available as connectors in claude.ai, and reach Claude Managed Agents and the Messages API only.
  3. 3 Then collect the reference id. It starts ofid_ and appears in the error toast and in the page URL; file it with your server URL and your server-side access logs on the anthropics/claude-ai-mcp tracker. It is what lets Anthropic trace the exact failure server-side, and reference ids are time-limited, so report soon after the failure.
text
.../settings/connectors?step=start_error&flow_id=ofid_d32594c73257a651
Anthropic: troubleshooting connectors

Kubernetes

If the public record points into your cluster's network, or your ingress has a source-IP allowlist

  1. 1 An internal load balancer gets an internal address: networking.gke.io/load-balancer-type: "Internal" on a GKE Service creates a load balancer that, per Google's docs, only clients in the same VPC network or a network connected to it can reach. If that address is what your public zone publishes, claude.ai resolves it, sees a non-routable answer, and stops before sending anything.
  2. 2 Point the public record at an internet-facing ingress and keep the internal name for in-cluster callers. Verify with dig +short A from outside your network, not from a pod and not from a laptop on the VPN.
  3. 3 nginx.ingress.kubernetes.io/whitelist-source-range sets the allowed client IP source ranges as a comma-separated CIDR list, and ingress-nginx documents that an annotation on an Ingress rule overrides any global restriction — so 160.79.104.0/21 has to be in whichever one applies, or the MCP and OAuth paths never reach your pod. Do not treat that allowlist as authentication: the range is shared by every Anthropic customer.
ingress-nginx: annotations reference

Cloudflare Access

If Cloudflare Access or a WAF rule sits in front of the hostname

  1. 1 A WAF rule, a bot-management rule and a rate limiter all answer before your application does, and all three look identical from claude.ai. Anthropic's guidance is the only reliable tell: look for 403 or 429 responses in your edge or CDN logs that your application did not generate, especially during a Connect attempt. Cloudflare Access in front of the hostname is a different failure with the same toast — it authenticates browser sessions, and that shape has its own page in the related errors below.
  2. 2 Add a Bypass policy for Anthropic's outbound range using an IP-range selector, which takes CIDR notation: 160.79.104.0/21. Cloudflare documents Bypass as disabling Access enforcement for traffic matching the rule, and that bypassed requests are not logged — so it removes your only gate on that path, and the MCP route needs its own authentication afterwards.
  3. 3 Do not plan on a service token instead. That needs Claude to send CF-Access-Client-Id and CF-Access-Client-Secret, and claude.ai's request-header authentication is in beta and gated on contacting Anthropic, accepts at most four headers, and restricts names to a reviewed allowlist of standard authentication and routing headers such as authorization, x-api-key and x-auth-token.
Cloudflare: Access policies

AWS Load Balancer

If a WAF web ACL, security group or network ACL sits in front of your load balancer

  1. 1 A web ACL whose default action is Block, or a managed rule group that reads an unattended client as a bot, answers before your target group does. The allow path is an IP set match rule: create an IP set holding 160.79.104.0/21 and reference it from a rule with an Allow action, ordered ahead of the blocking rules. In the console rule builder the Request option to choose is Originates from an IP address in; in the API the statement is IPSetReferenceStatement. AWS WAF supports every IPv4 and IPv6 CIDR range except /0, and one IP set holds up to 10,000 addresses or ranges.
  2. 2 Check the security group and any network ACL as well. A group scoped to your corporate ranges drops the connection with no HTTP response at all, which claude.ai reports with the same string and which leaves nothing in your WAF logs to find.
  3. 3 If the listener also carries an authenticate-oidc action, that is a separate failure that produces the same toast — a browser redirect an MCP client cannot follow. It has its own page in the related errors below.
AWS WAF: IP set match rule statement

Vercel

If the URL you registered redirects — apex to www, region routing, or a vanity domain to a CDN

  1. 1 Vercel recommends the www subdomain as the primary domain with a redirect from the non-www domain to it, and its docs say that if someone visits your domain with or without the www prefix, Vercel will attempt to redirect automatically. So the apex URL a developer naturally pastes into the connector dialog is a redirect, not an endpoint.
  2. 2 The Authorization header is dropped on a cross-host redirect per standard HTTP client security behaviour. The redirect target receives an unauthenticated request, returns 401, and the connection fails with "Authorization with the MCP server failed" — which is why this cause hides behind the other error string.
  3. 3 Register the URL the server actually listens on, not one that redirects to it. curl -sI the registered URL: if the status is a 3xx and the Location names another host, that target is the URL to register. Region routing and vanity-domain-to-CDN redirects behave the same way.
Vercel: deploying and redirecting domains

Microsoft Entra ID

If your authorization server is Microsoft Entra ID and the token request fails with AADSTS9010010

  1. 1 This one lands on the other toast — Anthropic documents it under "Authorization with the MCP server failed", because the MCP endpoint was reachable and the token request is what failed. Entra is rejecting the resource value Claude sends, sometimes surfaced as invalid_target: Claude sets resource to your MCP server URL including the path, and Entra issues a token only when that value is listed as an Application ID URI on the app registration representing your protected API. The default api://{client-id} URI alone is not sufficient.
  2. 2 In the Entra admin center open the API's app registration — not the client's, if they are separate — and under Expose an API add your MCP server URL as an additional Application ID URI (identifierUris in the manifest). The value must match exactly, including the path, without a trailing slash.
  3. 3 If your server validates the audience through Azure App Service Authentication, add the API app's Application (client) ID and its api:// URI to the allowed token audiences — the Allowed token audiences setting, not Allowed client applications, which is a different list. And where the client and the API are separate registrations, confirm the client has an admin-consented API permission for the scope your API exposes.
Microsoft: expose a web API

Zuplo MCP Gateway

If the MCP server genuinely cannot be published to the public internet

  1. 1 claude.ai has no private path into your network, and Anthropic's own MCP tunnels do not provide one: they are a research preview, and the docs state that tunnels created through the Console are not available as connectors in claude.ai. For a claude.ai connector the fix has exactly one shape — a public origin in front of the private server.
  2. 2 The gateway is that origin: one public HTTPS route per upstream MCP server, with a server that only exists inside your VPC or on bare metal reached over the Zuplo secure tunnel. The tunnel service dials out from inside your network, so nothing is exposed to the internet and no inbound firewall rule is opened.
  3. 3 It also answers the discovery half of this error. For its MCP routes the gateway is both the OAuth 2.1 resource server and the authorization server, publishing an RFC 9728 protected resource metadata document per route and RFC 8414 authorization-server metadata both gateway-wide and per route — so there is no cross-host authorization server for a WAF to block.
Zuplo MCP Gateway: authorization

Something else

Whatever sits in front of your server, the branch is the hop, and there are only four. Nothing on your network saw the request: the name you registered resolves to something Anthropic will not dial, so fix the public record or publish a routable IPv4 endpoint. Something answered before your application did: find the 403 or 429 in the edge logs and allowlist 160.79.104.0/21 on that component, or exempt the MCP and OAuth paths from the rule. The request arrived without its credential: your URL redirects to another host, so register the target instead. The MCP endpoint answered but discovery did not: return a 401 carrying WWW-Authenticate: Bearer resource_metadata="…", or serve /.well-known/oauth-protected-resource/<your-mcp-path> on the origin, and remember that discovery requests to a cross-host authorization server arrive from the same egress range — so a WAF in front of your identity provider breaks the connection even when your MCP server is perfectly reachable.

Why it happens

claude.ai connectors run on Anthropic's infrastructure and reach your server over the public internet. Everything that differs between that path and your laptop shows up here and nowhere else: split-horizon DNS answering with an internal address, a hostname that only resolves on the VPN, an edge rule that has never seen Anthropic's egress range, an apex-to-www canonicalisation no browser ever complains about. Each cause empties a different log, which is what makes the toast so hard to read: the pre-flight DNS check sends nothing at all, so no log you own records anything; an edge block leaves your application log empty but puts a 403 or 429 in your CDN's; and a discovery failure is documented as surfacing under this same string even when your MCP endpoint is reachable, with the signature that your MCP server sees the first request while your authorization server sees no traffic whatsoever. The redirect case is the one the wording actively misleads on: the request does arrive, without its credential, so the redirect target answers 401 and Claude reports "Authorization with the MCP server failed" instead. Local clients fail fast on a redirect, which is why the same misconfiguration looks instantly broken in MCP Inspector and looks like an auth bug on claude.ai.

The exchange

Each step in the exchange, in order.

  • claude.ai Anthropic infrastructure
  • Your edge CDN, WAF, ingress
  • MCP server
  • Authorization server
  1. Split-horizon or internal DNS is the usual cause: the record that answers from the public internet carries an internal address. A mix of public and non-public answers is rejected too — every address must be globally routable.

    Inside claude.ai

    Resolve mcp.example.com public DNS — A records only, IPv4-only
  2. Inside claude.ai

    Check every address it got back 203.0.113.9, 10.4.2.11
  3. Nothing leaves Anthropic's network, which is why the server looks healthy and the access log is empty. Claude Code and curl connect because they resolve and dial from your machine.

    Inside claude.ai

    Not every address is globally routable rejected before any request is sent

    Flow stops here — Your edge and MCP server and Authorization server not reached

claude.ai resolves the connector hostname on Anthropic's infrastructure and checks every address it gets back. One answer is a private address, so the connection is rejected before any HTTP request leaves Anthropic's network. Your edge, your MCP server and your authorization server are never contacted, so nothing appears in any log you own.
Zuplo

What lands on you next

Reachability was the gate. Once claude.ai can connect, the route is a public entry point that a whole organization shares, and the questions change shape.

We allowlisted Anthropic's range. Isn't that our access control?

mcp-oauth-inbound

No. 160.79.104.0/21 is shared by every Anthropic customer. One MCP OAuth policy per project — the gateway rejects a second — makes identity a token the route validates rather than a CIDR.

One connector, added once by an admin. Which tools should it see?

mcp-capability-filter-inbound

A per-route allow-list of tools, prompts and resources; a hidden tool is refused with MethodNotFound before forwarding. It is static per route, so a read-only view and a full-power view are two routes.

Everyone shares that connector. Who called refund_order?

capability_invocation

Every tool call emits an event carrying the caller, the capability, the upstream, the outcome and the latency, so a call resolves to a subject rather than to an egress IP.

An agent looped on one tool for an hour. What caps it?

rate-limit-inbound

Limits counted against the authenticated identity with rateLimitBy: "user", so one agent's retry storm does not spend another team's budget.

All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out.

Stop debugging auth on every MCP server

A gateway in front of your MCP servers handles discovery, audience binding, and token validation once — for every server and every client.