anthropics / anthropics/claude-code

[BUG] HTTP MCP: "Testing basic HTTP connectivity" pre-check silently stalls ~25s even though the TCP/TLS connection completes almost instantly

Aperta
#93,279 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
area:mcp bug platform:macos
Lingua principale
Python
Stelle
145k
Fork
23.1k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code

### What's Wrong?

After OAuth completes successfully against a remote HTTP MCP server, Claude Code's connection-establishment code logs `Testing basic HTTP connectivity to ` and then stalls for ~25 seconds before logging `Successfully connected (transport: http) in Nms`. This is not a case of no response ever arriving — a packet capture taken during the same reconnect shows the underlying TCP connection completes its handshake and has all bytes ACKed within ~0.5 seconds of the connectivity test starting. The connection then sits completely idle (no packets in either direction) for ~25 seconds, until the CLI itself sends the closing FIN, immediately followed by the "successfully connected" log line and a fast, successful `POST /mcp`.

In other words: the network path is fast. The server is fast. The CLI's own pre-check logic is what waits ~25 seconds before doing anything with a connection it already has.

This has been reproduced consistently across many fresh OAuth reconnects to the same server over multiple days, with the delay landing in a tight 25.2–25.6 second band every single time — see the elimination trail below.

### Investigation trail — what we ruled out before filing

We control the target MCP server's source code, so before assuming this was our bug we tested every plausible server/infra-side explanation directly, including one real code change deployed and verified live. All of the following were eliminated:

| Candidate cause | Test performed | Result |
|---|---|---|
| Our server / gateway hanging on the request | Packet capture (TLS-SNI-attributed) during a live reconnect | Ruled out — zero packets reach the server during the ~25s gap; the connection just sits idle |
| OAuth token exchange itself slow | Same packet capture, isolated the auth-server leg | Ruled out — token exchange completes in a few seconds, well before the stall begins |
| OAuth request shape (`resource`, `scope`, `offline_access` params) | Side-by-side diff of two real authorize URLs (affected server vs. an unaffected one) | Ruled out — byte-identical in structure |
| MCP Python SDK version mismatch | Checked resolved `mcp` package version on both the affected server and an unaffected sibling server | Ruled out — identical version on both |
| `json_response` (SSE vs. plain JSON on the streamable HTTP transport) | Deployed a real code change (`json_response=True`) to a live dev environment, confirmed via direct pod inspection that it was running, then re-measured | **Ruled out empirically** — connect time before: 25244–25306ms (4 samples); after the change was live: 25339–25552ms (2 samples). No shift at all. |
| `scope=` present/absent in the `WWW-Authenticate` challenge | Compared live 401 responses; found the unaffected server appends `scope=` via a 3-line custom middleware | Not plausible as the cause — that middleware only touches the *initial unauthenticated* `POST /mcp` (already confirmed fast on both servers), not the later step where the stall occurs |
| TLS/ALPN/certificate negotiation | Direct `openssl s_client` and `curl` comparison of both hosts | Ruled out — identical TLS 1.3, identical cipher, identical ALPN (`h2`), identical certificate (same wildcard cert/issuer) |
| A cached per-hostname "this server is slow" decision in local Claude Code state | Inspected `~/.claude.json` (`mcpServers` config, both global and project-scoped) | Nothing found — no per-host protocol/negotiation state cached locally beyond `{type, url}` |

What *is* confirmed present: `~/.claude.json`'s `cachedGrowthBookFeatures` has `tengu_mcp_protocol_negotiation_http: true` on this account — the same flag named in a closely-related issue (see below) as gating a version-negotiation probe with its own internal timeout/fallback sequence. This doesn't explain why one server is affected and another isn't, but it does confirm the account is in the code path that issue describes, and that the mechanism is a named, already-partially-diagnosed piece of Claude Code's own client logic — not anything server-side.

The one place we could **not** rule out a correlate (as opposed to a cause) is hosting platform: every server we tested that's hosted on Azure Container Apps connects in under 2 seconds; every server hosted on our newer Kubernetes-based platform (behind an internal API gateway) shows the same ~25s stall. However, two of those three "slow" servers are downstream copies of the same codebase (scaffolded from a shared template), so this is one code lineage deployed three times, not three independent confirmations — and given the packet-capture evidence that the client is silent even at the TCP level (not just the HTTP level) during the gap, we don't have a mechanism by which hosting platform would explain a client-side pre-send stall. We're not asserting hosting is the cause, just noting it as the one unexplained pattern left standing after everything else was eliminated.

### What Should Happen?

The "basic HTTP connectivity" pre-check (or the version-negotiation probe behind it) should proceed as soon as the underlying connection is established, not wait ~25 seconds afterward when the target server is healthy and responds fine once actually contacted. At minimum, the log should distinguish "waiting on the pre-check/probe" from "waiting on the server," since right now both look identical to a user with no visibility into the mechanism.

### Error Messages/Logs

```shell
From `~/Library/Caches/claude-cli-nodejs//mcp-logs-/*.jsonl` (timestamps relative, hostnames genericized):

T+0.000s Testing basic HTTP connectivity to https:///mcp
T+0.000s Parsed URL: host=, port=default, protocol=https:
... ~25.3 seconds — no further log lines at all ...
T+25.306s Successfully connected (transport: http) in 25306ms
T+25.306s Connection established with capabilities: {"hasTools":true,"hasPrompts":true,"hasResources":true,"hasResourceSubscribe":true,"serverVersion":{"name":"","version":""},"protocolEra":"modern","negotiatedProtocolVersion":"2026-07-28"}

Seven separate reconnects to the same server, spanning several days and both before and after a real server-side code change (see investigation trail above), all landed in the same narrow band:

25244ms, 25239ms, 25253ms, 25306ms, 25552ms, 25339ms, 25261ms

Independent corroboration from a packet capture taken during one of these reconnects (filtered to the MCP server's and the OAuth authorization server's hosts, connections attributed via TLS SNI):

- The TCP connection later used for the successful `POST /mcp` completes its handshake and has all bytes ACKed within ~0.5 seconds of the "Testing basic HTTP connectivity" log line.
- No further packets flow on that connection, in either direction, for ~25 seconds.
- The CLI itself sends the closing FIN at the ~25s mark — immediately followed by the "Successfully connected" log line and a fresh, fast `POST /mcp` exchange (completed in under a second).
- The OAuth token exchange (`POST /connect/token` and reply) that precedes this step is confirmed fast (a few seconds at most) via the same capture — the delay is specifically after the token is already in hand and before the real `POST /mcp` request.

Server-side (Datadog APM) for the same time window shows no trace of a slow or hanging request reaching the MCP server's application code — only a fast (~1.6ms), successful `GET /.well-known/oauth-protected-resource/mcp` at the very start of the window, from a different HTTP client identifier than the CLI's own `claude-code/ (cli)` user-agent. Nothing suggests the delay is server-side.
```

### Steps to Reproduce

### Environment

- Claude Code version: 2.1.267 (cli)
- Platform: darwin (macOS)
- Node version: v26.3.0
- Transport: HTTP (streamable), OAuth (Authorization Code + PKCE + Dynamic Client Registration)
- Negotiated protocol version: `2026-07-28`
- No `HTTP_PROXY` / `HTTPS_PROXY` set
- `cachedGrowthBookFeatures.tengu_mcp_protocol_negotiation_http`: `true`

### Steps to Reproduce

1. Configure a remote HTTP MCP server that requires OAuth (Authorization Code + PKCE), e.g. via `claude mcp add --transport http `.
2. Clear stored auth for the server and reconnect (`/mcp` → clear auth → reconnect), forcing a fresh OAuth handshake.
3. Complete the browser consent screen — note that the browser itself shows "Authentication successful" essentially immediately after clicking "Allow."
4. Switch back to the terminal. Observe that the CLI does **not** report a completed connection for another ~20–25 seconds, even though the browser-side flow already finished.
5. Cross-check `~/Library/Caches/claude-cli-nodejs//mcp-logs-/*.jsonl` for the same time window (see log excerpt below). The delay reproduces on essentially every reconnect to an affected server, whether the OAuth token is freshly issued or already cached.

### Claude Model

Sonnet (default)

### Is this a regression?

I don't know

### Last Working Version

_No response_

### Claude Code Version

2.1.267

### Platform

Anthropic API

### Operating System

macOS

### Terminal/Shell

Other

### Additional Information

### Terminal/Shell
I'm using cmux, with several sessions running

### Is this a regression?

Yes, relative to this specific server's own prior behavior — auth against it reconnected essentially immediately before it moved to its current hosting platform and the 2026-07-28 protocol (both changed at the same time, so we can't isolate which one matters, and per the investigation trail above, protocol version alone doesn't explain it either — see the unaffected sibling on the same protocol version). OAuth and Dynamic Client Registration are unchanged and confirmed fast in every test here; the regression is specifically in the connection-establishment step that follows.

### Related / possibly duplicate issues

- A closely related open issue describes an HTTP transport "version-negotiation probe" that silently times out and then falls back to a "pinned legacy" reconnect path, burning the remainder of the connect-timeout budget before succeeding or failing — gated by the same `tengu_mcp_protocol_negotiation_http` GrowthBook flag confirmed present on this account (see Environment). This report's timing (~25s, ending in success rather than timeout) looks consistent with the same mechanism, just not hitting the flag/condition combination that causes an outright failure.
- A second related open issue reports the same connection hanging *indefinitely* at the identical "basic HTTP connectivity" pre-check line against a different remote MCP server, isolated to Claude Code's own connection-establishing code rather than server-side behavior.
- A third related open issue reports OAuth completing successfully followed by the `initialize` handshake hanging until connect-timeout, with no distinct error surfaced for "authenticated but the post-auth request stalled" versus a generic network failure.

This report differs from all three in that it includes millisecond-resolution packet-capture evidence showing the TCP/TLS connection itself is not slow, a confirmed live A/B test ruling out a server-side response-format explanation, and confirmation of the specific feature flag implicated in the closest related report — narrowing this from "something is slow somewhere" to "the delay is entirely inside Claude Code's own post-auth connection/negotiation logic."

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

Start with the Claude Code connection-establishment path that emits "Testing basic HTTP connectivity" and inspect the HTTP protocol-negotiation flow gated by tengu_mcp_protocol_negotiation_http. Reproduce with an OAuth-authenticated remote HTTP MCP server and correlate mcp-logs JSONL entries with the packet-capture timing. Done means the pre-check proceeds without the reproducible ~25-second idle delay, or clearly reports which probe is waiting.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
node.js
Ambito
authentication, cli, networking
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
45/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.