[BUG] MCP HTTP transport hangs 30s and fails to connect on any dual-stack server when the host's IPv6 route is dead (no IPv4 fallback)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
Any HTTP-transport MCP server whose hostname resolves to both A (IPv4) and AAAA (IPv6) records fails to connect — claude mcp list / claude mcp get report "✘ Failed to connect" — when the host's IPv6 route is present in DNS but not actually reachable (common on WSL2, and on other misconfigured dual-stack networks). The client picks an IPv6 address, gets no response, and hangs for the full 30s connection timeout instead of falling back to IPv4. There's no Happy Eyeballs (RFC 8305) style dual-stack fallback. This is a bug in the MCP HTTP transport layer itself — reproduces with any dual-stack MCP server, plugin-provided or manually configured via claude mcp add. Not specific to any one MCP server.
What Should Happen?
The MCP HTTP transport should fall back to IPv4 quickly (Happy Eyeballs, or at least a short per-address-family timeout) when the preferred address family is unreachable, instead of hanging for the full 30s connection timeout on a dead route.
Error Messages/Logs
From claude --debug-file:
MCP server "context7": Testing basic HTTP connectivity to https://mcp.context7.com/mcp
MCP server "context7": Parsed URL: host=mcp.context7.com, port=default, protocol=https:
MCP server "context7": No token data found
... (30s pass) ...
MCP server "context7": Connection timeout triggered after 30008ms (limit: 30000ms)
MCP server "context7": HTTP Connection failed after 30010ms: MCP server "context7" connection timed out after 30000ms
Manual isolation of the root cause:
getent ahosts mcp.context7.comreturns both an IPv4 address (e.g.98.91.39.95) and multiple IPv6 addresses (2600:9000:...).curl -6 https://mcp.context7.com/mcpfails immediately ("Couldn't connect to server") — IPv6 is unreachable on this host.curl -4 https://mcp.context7.com/mcpsucceeds in ~0.15s.NODE_OPTIONS=--dns-result-order=ipv4firsthas no effect on theclaudebinary — it's a bundled/SEA Node binary that doesn't appear to readNODE_OPTIONSfrom the environment, or the custom HTTPS/mTLS agent it builds (log line:mTLS: Creating HTTPS agent with custom certificates) doesn't honor Node's DNS result ordering.- Disabling IPv6 system-wide (
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1) makesclaude mcp listconnect successfully and quickly — confirms IPv6-first resolution with no fallback is the cause.
Steps to Reproduce
- On a host where IPv6 is technically routable per DNS/
/etc/resolv.confbut not actually reachable (e.g. default WSL2 networking), add any HTTP-transport MCP server whose host has both A and AAAA records. Example used to find this bug (Upstash's Context7 docs server):
````
claude mcp add --transport http context7 https://mcp.context7.com/mcp --header "Context7-API-Key: <key>" -s user
- Run
claude mcp list. - Observe
✘ Failed to connectafter a ~30s pause.
Note: this also reproduces via a plugin-provided MCP server (Upstash's official Context7 plugin) with no manual config involved, and is unrelated to that plugin's specific header/config — see "Ruled out" below.
Claude Model
Not applicable — this is a CLI-level MCP transport issue, unrelated to model choice.
Is this a regression?
I don't know
Claude Code Version
2.1.219 (also reproduced on 2.1.218)
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
Suggested fix: Implement Happy Eyeballs (RFC 8305), or at minimum a fast IPv4 fallback, for the MCP HTTP transport's connectivity check and actual connection — instead of a single ~30s attempt on whichever address family resolves/sorts first. Alternatively/additionally, honor --dns-result-order / NODE_OPTIONS for DNS resolution order in the bundled binary, or expose an equivalent Claude Code setting. The 30s hang is especially painful for what should be a fast, low-risk operation (claude mcp list health check) — a much shorter per-address-family timeout before falling back would help even without full Happy Eyeballs.
Workaround: Disabling IPv6 system-wide (sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1) fixes it, but is a blunt, machine-wide workaround for what should be transport-level fallback behavior.
Ruled out: Initially suspected the Context7 plugin's .mcp.json header name (CONTEXT7_API_KEY, underscore) might be rejected by the server, since its CORS Access-Control-Allow-Headers only lists X-Context7-API-Key/Context7-API-Key/X-API-Key/Authorization. Confirmed this was a dead end: once IPv6 was disabled, the plugin's original underscore header connected and authenticated correctly (verified via a live resolve-library-id call showing up in the Context7 dashboard's "last used" within a minute). IPv6 was the sole cause — no plugin-side bug, no MCP-server-side bug.