anthropics / anthropics/claude-code

sdk-cli mode: Streamable HTTP MCP GET-stream aborts ~200-500ms after connect, tools vanish before first call

Đang mở
#87,661 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
area:mcp bug has repro platform:linux stale
Ngôn ngữ chính
Python
Star
145k
Fork
23.1k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

## Summary

In "sdk-cli" mode (`--input-format stream-json --output-format stream-json`, the mode used by SDK-driven/headless Claude Code automation), the Streamable HTTP client transport for a remote MCP server aborts its GET notification stream ~200-500ms after a successful connection, before any tool call can complete — even though the connection, authentication, and `initialize` handshake all succeed cleanly. The same server, same tools, and same OAuth tokens work perfectly in plain interactive `cli` mode. This makes the affected MCP server's tools permanently unusable from any headless/automated Claude Code session.

## Environment

- Claude Code version: 2.1.203 (confirmed via `User-Agent: claude-code/2.1.203 (sdk-cli)` in client logs)
- Platform: Linux, Node v26.3.0 (per client's own debug log)
- MCP server: a self-hosted Streamable HTTP MCP server (`@modelcontextprotocol/sdk`'s `StreamableHTTPServerTransport`), OAuth 2.0 + PKCE, reachable over the public internet via HTTPS

## Reproduction

Run Claude Code exactly as an SDK/automation harness would:

```bash
echo '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Call and return the raw result."}]}}' \
| claude --input-format stream-json --output-format stream-json --verbose --permission-mode dontAsk \
--allowedTools "mcp____" -p
```

Result: the `system/init` event reports the MCP server as `"status":"connected"`, but the very next `ToolSearch` call for that tool returns `"No matching deferred tools found"` — the tool has already vanished from the index by the time the model tries to use it, seconds into the very same turn.

The corresponding MCP client debug log (`~/.cache/claude-cli-nodejs//mcp-logs-plugin-/*.jsonl`) shows:

```
14:03:25.969 | HTTP transport options: {"url":"https:///mcp","headers":{"User-Agent":"claude-code/2.1.203 (sdk-cli)","Accept-Encoding":"identity"},"hasAuthProvider":true,"timeoutMs":60000}
14:03:25.984 | Has refresh token: true
14:03:26.196 | Successfully connected (transport: http) in 228ms
14:03:26.196 | Connection established with capabilities: {"hasTools":true, ...}
14:03:26.435 | HTTP connection dropped after 0s uptime
14:03:26.435 | Connection error: SSE stream disconnected: AbortError: The operation was aborted.
14:03:26.435 | Terminal connection error 1/3
```

~239ms after a clean, fully-authenticated connection, the GET/SSE notification stream gets an `AbortError` and the client gives up (only 1 of 3 configured reconnect attempts is logged before the tool disappears from ToolSearch for the rest of the turn).

## What we ruled out (server-side and infra)

We control the affected MCP server's source and its reverse proxy, so we were able to rule out several server-side and network hypotheses over an extended investigation:

1. **Not an auth/token bug.** Token/refresh-token handling is correct and verified (`Has refresh token: true`, `Successfully connected`, valid `hasTools:true` capabilities). A separate, now-fixed bug where invalid tokens returned bare HTTP 500 instead of 401/400 was found and fixed independently — this abort happens on a *freshly and successfully authenticated* connection, unrelated to that fix.
2. **Not HTTP/2-specific.** The server negotiates HTTP/2 via its reverse proxy (Traefik, ALPN). We forced the reverse proxy to offer only HTTP/1.1 (`tls.options.alpnProtocols: ["http/1.1"]`) and confirmed via `curl` that the negotiated protocol was indeed HTTP/1.1 for this endpoint. The abort reproduced identically:
```
Successfully connected (transport: http) in 228ms
HTTP connection dropped after 0s uptime
Connection error: SSE stream disconnected: AbortError: The operation was aborted.
```
(Same ~200-500ms timing, same error, under both HTTP/1.1 and HTTP/2.)
3. **Not a missing "Connection: keep-alive" / h2 header violation.** We already ship a server-side fix stripping the SDK's default `Connection: keep-alive` header from h2 responses (which RFC 7540 §8.1.2.2 forbids) — no change in behavior with or without it.
4. **Not "no bytes sent" / idle-stream heuristic.** The server sends an SSE comment-line keep-alive (`: ping\n\n`) immediately on stream open and every 50ms for the first 3 seconds specifically to beat any such heuristic. The abort still happens ~200-500ms in, after at least one ping should have been delivered.
5. **Server does hold the connection open correctly.** Correlating the reverse proxy's own access logs with the client's abort timestamp shows the proxy received a clean `200` and the connection was closed **from the client side**, not reset by the server or an intermediary.
6. **Not a general "sdk-cli mode can't hold a Streamable HTTP GET stream" issue.** The exact same Claude Code binary, same `-p`/`--input-format stream-json` invocation, same OS/network, connecting instead to a trivial local reference MCP server (plain HTTP/1.1, no TLS, no reverse proxy, using the same `@modelcontextprotocol/sdk`'s `StreamableHTTPServerTransport` with no custom modifications) — ToolSearch finds the tool and the call succeeds normally:
```
TOOL_USE: mcp__oauth-hypothesis-test-v2__ping_tool {}
TOOL_RESULT: [{'type': 'text', 'text': 'pong'}]
```
7. **Plain interactive `cli` mode against the SAME real server works.** A long-running interactive session (`User-Agent: ... (cli)`) held the identical connection open for 6975+ seconds with normal use before an unrelated, much-later disconnect.

## What this narrows the bug down to

The abort is specific to the **combination** of: (a) `sdk-cli` mode's Streamable HTTP client transport, and (b) something about this particular server's response — but *not* its HTTP version, its `Connection` header, or the presence/timing of keep-alive bytes, all of which we controlled for directly. It is not a general "can't hold a slow/silent HTTP stream" limitation of the sdk-cli client, since the same client mode is fine against a trivial reference server implemented with the same open-source MCP SDK transport class. We were not able to identify what specific difference in the two servers' responses triggers the client-side abort — that requires visibility into the sdk-cli transport's internal abort/watchdog logic, which is closed-source from our side.

## Impact

Any MCP server accessed exclusively through headless/SDK-driven Claude Code sessions (CI pipelines, background agents, `claude -p` automation) that happens to trigger this condition is effectively unusable from those sessions — tools connect, report `hasTools:true`, and then disappear before any call can be made — while working completely normally in interactive sessions. This is easy to misdiagnose as a server auth/config bug (which is what led to an extended, ultimately inconclusive investigation on our end) rather than a client-side transport issue.

## Ask

- Any insight into what could cause the sdk-cli transport's Streamable-HTTP GET-stream `AbortError` at ~200-500ms post-connect, independent of HTTP version, `Connection` header, and keep-alive traffic, given it does not occur against a trivial reference server using the same SDK transport class.
- Happy to provide further logs/traces or a minimal public reproduction server if useful.

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Hướng nghiên cứu

Start with the provided sdk-cli reproduction command and compare its MCP debug log with the successful local reference-server run. Trace the Streamable HTTP GET/SSE connection around the 200–500ms AbortError, especially the transport's abort or watchdog behavior described in the report. Done means the affected server's tools remain indexed and callable in sdk-cli mode without breaking the working interactive and local-server cases.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
node.js
Lĩnh vực
api, cli, networking
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
42/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.