anthropics / anthropics/claude-ai-mcp

claude.ai web: capability refresh fails ("Client server capabilities not available") and MCP Apps tool results render as "Unable to reach " — while the origin server returns 200 to every request and never receives the GET stream

Offen
#636 9 Kommentare 6 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
bug
Vorherrschende Sprache
Keine Sprachdaten
Sterne
471
Forks
76
PR-Merge-Kennzahlen
Keine gemergten PRs in 30 T.

Beschreibung

### What happened?

# Body

## Summary

On claude.ai web, a custom streamable-HTTP connector intermittently gets the **"Client server capabilities not available"** toast, and from that moment every **MCP Apps (SEP-1865) interactive tool result renders as an "Unable to reach " error chip** — even though the origin server's request log shows every one of those calls **arrived and returned HTTP 200**, and the async jobs they triggered completed successfully.

The failure correlates with **conversation open/resume** (capability refresh), not with any tool code path. This looks like the claude.ai-web sibling of anthropics/claude-code#78193: the client's optional GET SSE stream request appears to be rejected **inside Anthropic's proxy** — our origin has **never received a single GET** from Claude — and the client treats that failure as fatal to the connector's capability state. The MCP spec explicitly allows the standalone SSE stream to be unavailable (405) with graceful fallback to POST-only operation.

- Request ID (from an error toast in the same failure family, 2026-07-17): `req_011Cd7HQ1duYJuKRm9rHJcqV`
- Client: claude.ai web (custom connector, OAuth); also reproduced via Claude Desktop
- Date of captures below: 2026-07-17 (UTC)
- Server URL available privately on request (happy to share with the team; omitting here)

## What the origin server saw (request-level evidence)

The server keeps a ring buffer of every request that reaches its `/mcp` endpoint (method, tool, status, duration, session id, UA). During a failing exchange:

```
08:41:03 initialize 200 Claude-User
08:41:04 notifications/initialized 202 Claude-User
08:41:04 tools/list 200 Claude-User
08:41:04 resources/list 200 Claude-User
09:32:04 tools/call 200 843ms Claude-User <- UI rendered this as "Unable to reach "
```

Key facts:

1. **Every POST from claude.ai returned 200**, including the exact `tools/call` invocations the UI displayed as "Unable to reach ". The assistant's own chat text confirms it received the results (it accurately described the submitted job).
2. **Zero GET requests from Claude have ever reached the origin** — across thousands of requests and multiple days. The only GETs in the log are our own curl probes. The origin serves `GET /mcp` with a valid 200 `text/event-stream` (keepalive) — it is simply never asked.
3. The failures cluster at **conversation open/resume**. A fresh conversation immediately after connecting works perfectly (interactive cards render; the card's bridge `tools/call` polls run for minutes over one session). Resuming the same conversation ~25 minutes later reproduced the toast and a failed card on the very next tool call — whose POST returned 200 in 843 ms.

## What we ruled out server-side

We progressively eliminated every server-side cause; the failure persisted identically until the client-side pattern above became clear:

- **Reachability / TLS / DNS** — origin healthy, publicly routable; all POSTs logged 200.
- **CORS** — full preflight support, `WWW-Authenticate` exposed.
- **405 on the optional GET stream** — origin now answers `GET /mcp` with 200 + `text/event-stream` keepalive. Irrelevant in practice: the GET never arrives at origin.
- **SSE vs JSON response framing** — reproduced with both.
- **Stateless session churn** — server implements full `Mcp-Session-Id` sessions (initialize once, long-lived transport). Bridge-side polling works for minutes on one session. Failure still occurs at refresh time.
- **Server restarts** — unknown session ids are served gracefully (no 404s).
- **MCP Apps wiring** — `capabilities.extensions["io.modelcontextprotocol/ui"]` declared, resource MIME `text/html;profile=mcp-app`, tool `_meta.ui` limited to `{resourceUri, visibility}`, `ui/initialize` / `ui/notifications/initialized` handshake implemented. Cards render correctly whenever the capability state is healthy — including the first calls of a fresh conversation.
- **SDK currency** — TypeScript SDK 1.29.0 (latest), protocol version 2025-11-25 negotiated.

## Ask

1. Treat GET-stream unavailability as non-fatal (spec-compliant fallback to POST-only), or stop gating Apps rendering on that refresh.
2. Don't render HTTP-200 tool results as "Unable to reach" — if the interactive render path is unavailable, fall back to the plain content render.
3. If the proxy (`/v1/toolbox/shttp/...`) is rejecting the GET before it egresses (our origin never sees it), forward it — the origin serves a valid stream.

Happy to provide the server URL, full origin logs, timestamps, or a test account privately on request.

### What did you expect to happen?

## Expected behavior (per spec)

The standalone GET SSE stream is optional. A client whose GET attempt fails (405 or otherwise) should fall back to POST-only operation without invalidating the connector's negotiated capabilities, and should not render successfully-returned (HTTP 200) tool results as transport failures.

## Actual behavior

- "Client server capabilities not available" toast at capability-refresh moments (conversation open/resume).
- All subsequent MCP Apps tool results in that window render as "Unable to reach " chips (tooltip: "Client server capabilities not available"), despite 200 responses.
- In that state the card iframe's bridge `tools/call` requests are refused; before we added sessions, each refusal also surfaced as a visible "Unable to reach" chip in chat.
- Plain (non-Apps) results continue to render — the damage is specific to interactive results, which makes Apps-enabled connectors look completely down while functioning perfectly.

### Steps to reproduce

## Repro

1. Add a custom streamable-HTTP connector with MCP Apps tools (OAuth or bearer).
2. Start a conversation, invoke an interactive tool → card renders (healthy window).
3. Leave the conversation idle ~20–30 min, return, send a follow-up that triggers another interactive tool call.
4. Intermittently: toast + "Unable to reach " chip, while the origin logs the call with HTTP 200.

### Area

MCP Connector (adding/managing servers)

### MCP Server (if applicable)

custom server

### Error messages or logs

```shell
"Client server capabilities not available"
```

### Additional context

_No response_

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.