anthropics / anthropics/claude-code

Cowork desktop: MCP elicitation (2026-07-28 MRTR) dropped via print-mode branch, tool call hangs 180s

Open
#94,806 2 comments 0 reactions 0 assignees View on GitHub
area:cowork area:mcp bug has repro
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

# Elicitation (MCP 2026-07-28 MRTR) is dropped in print mode — capability is declared, `InputRequiredResult` is never serviced, tool call hangs for 180s

**Component:** MCP client used by the Claude Cowork desktop app
**Version:** `claude-code/2.1.273 (remote_cowork)`
**Severity:** High — makes any MCP server that gates writes behind elicitation unusable in Cowork

> **The core of it:** the client takes a *print-mode* (non-interactive) branch on receiving an elicitation, even though the surface is the **Claude Cowork desktop app** with a live user sitting in front of it. A human is present and the app already renders tool-approval prompts — there is somewhere obvious to show the form. The session merely *executes* remotely; that is not a reason to treat it as headless.

---

## Summary

The Claude Cowork desktop app declares `"elicitation": {}` in `io.modelcontextprotocol/clientCapabilities` and negotiates protocol revision `2026-07-28`. When a server responds to `tools/call` with a spec-compliant `InputRequiredResult` carrying an `elicitation/create` (form mode) request, the client **receives and parses it**, logs

```
Elicitation request received in print mode: {"method":"elicitation/create", …}
```

…and then does nothing. No UI is rendered, no retry is issued, no `decline`/`cancel` is returned, and no error is surfaced. The originating `tools/call` stays pending until the transport timeout fires at **180 seconds**, at which point the user sees a generic timeout that gives no hint that a confirmation was requested.

The server's `InputRequiredResult` arrives **~2 seconds** after dispatch. The remaining ~178 seconds are entirely client-side stall.

Reproduced 3/3 in a single session.

---

## Expected behaviour

Per the [MRTR pattern spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr), Client Requirements 1 and 2:

> 1. If a client receives an `InputRequiredResult` that contains the `inputRequests` field, the client **MUST** construct the requested inputs before retrying the original request.
> 2. If an `InputRequiredResult` contains the `requestState` field, the client **MUST** echo back the exact value of that field when retrying the original request.

So one of:

1. **Preferred** — bridge the elicitation to the Cowork desktop UI. This is not a headless context: a user is sitting in the Cowork desktop app, and the app already renders tool-approval prompts, so there is an obvious place to show the form. Then retry the call with `inputResponses` + the echoed `requestState` under a new JSON-RPC id.
2. **Acceptable** — if the surface genuinely cannot service elicitation, respond immediately with `{"action": "cancel"}` on the retry leg so the server can fall back, and fail the call fast with a diagnostic naming elicitation as the cause.
3. **Minimum** — stop declaring the `elicitation` capability in modes that cannot honour it. Servers are required by MRTR server-requirement 7 to *only* send elicitation requests the client has declared support for; declaring support that is then silently dropped makes that guarantee meaningless and pushes the failure onto conformant servers.

## Actual behaviour

Capability is declared → server correctly sends form-mode elicitation → client logs it under a print-mode branch → request is dropped → 180s hang → generic timeout.

---

## Environment

| | |
|---|---|
| Surface | **Claude Cowork desktop app** (Windows), user interactively present |
| Client id (internal) | `claude-code/2.1.273 (remote_cowork)` — the Cowork desktop app's MCP client self-identifies as `claude-code`; the product surface is Cowork |
| Runtime | Node v26.3.0, Linux — session executes in an Anthropic cloud container |
| Transport | HTTP → `https://api.anthropic.com/v2/ccr-sessions//mcp?mcp_server_id=&mcp_url=&toolbox_mcp_server_id=` |
| Transport timeout | `timeoutMs: 180000` |
| Negotiated protocol | `2026-07-28` |
| Server | Remote MCP server built on the MCP C# SDK (`InputRequiredException` / MRTR) |
| Tool | `add_agenda_item` — a write gated behind a boolean confirmation |

---

## Reproduction

1. Connect an MCP server that negotiates `2026-07-28` and returns an `InputRequiredResult` with a form-mode `elicitation/create` from a `tools/call` handler.
2. Call that tool from the Claude Cowork desktop app.
3. Observe: no form, no retry, 180s hang, timeout error.

Minimal server behaviour needed to reproduce — return this from `tools/call` instead of a `CallToolResult`:

```json
{
"jsonrpc": "2.0",
"id": 11,
"result": {
"resultType": "input_required",
"inputRequests": {
"confirmation": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Confirm this write?",
"requestedSchema": {
"type": "object",
"properties": {
"confirm": { "type": "boolean", "title": "Confirm", "default": false }
},
"required": ["confirm"]
}
}
}
},
"requestState": ""
}
}
```

---

## Evidence

### 1. Client request — capability *is* declared

```json
{
"method": "tools/call",
"params": {
"name": "add_agenda_item",
"arguments": {
"title": "Impact of MCP servers",
"description": "

Discuss the impact of MCP servers …

",
"eventId": ""
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "claude-code",
"title": "Claude Code",
"version": "2.1.273"
},
"io.modelcontextprotocol/clientCapabilities": {
"roots": { "listChanged": true },
"elicitation": {}
},
"claudecode/toolUseId": "",
"progressToken": 11
}
},
"jsonrpc": "2.0",
"id": 11
}
```

Note `"elicitation": {}` — per the [elicitation spec](https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation), an empty object is equivalent to declaring `form` mode support.

### 2. Server response — spec-compliant `InputRequiredResult`

```json
{
"jsonrpc": "2.0",
"id": 11,
"result": {
"resultType": "input_required",
"inputRequests": {
"confirmation": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Add this item to the meeting's agenda?\nMeeting: \nWhen: Sat 19 Sep 2026, 05:30 (UTC)\nItem: Impact of MCP servers\nDuration: 10 min\nAgenda: Existing draft agenda, not yet visible to attendees.",
"requestedSchema": {
"type": "object",
"properties": {
"confirm": { "type": "boolean", "title": "Add agenda item", "default": false }
},
"required": ["confirm"]
}
}
}
},
"requestState": ""
}
}
```

Conformance check against MRTR server requirements: `resultType` set ✅ · `inputRequests` keyed uniquely, value is a valid `ElicitRequest` ✅ · `requestState` present and integrity-protected (req. 4) ✅ · at least one of `inputRequests`/`requestState` (req. 6) ✅ · sent on `tools/call`, a supported request ✅ · mode matches declared client capability (req. 7) ✅.

`InputRequiredResult` has no `content` field in the schema, so its absence is correct and is not the cause.

### 3. Client log — the drop

From `~/.cache/claude-cli-nodejs//mcp-logs-/.jsonl`:

```
15:29:11.854 Calling MCP tool: add_agenda_item
15:29:13.862 Elicitation request received in print mode: {"method":"elicitation/create","params":{"mode":"form", …}}
15:29:41.877 Tool 'add_agenda_item' still running (30s elapsed)
15:30:11.877 Tool 'add_agenda_item' still running (60s elapsed)
15:30:41.879 Tool 'add_agenda_item' still running (90s elapsed)
15:31:11.941 Tool 'add_agenda_item' still running (120s elapsed)
15:31:41.939 Tool 'add_agenda_item' still running (150s elapsed)
15:32:11.855 Tool 'add_agenda_item' failed after 180s: MCP server "…" tool "add_agenda_item" timed out after 180s
```

The `Elicitation request received in print mode` line is the whole bug: the client got it, understood it, and had no branch to act on it.

### 4. All three occurrences

| # | Dispatched | Elicitation received | Outcome |
|---|---|---|---|
| 1 | 15:29:11.854 | 15:29:13.862 (+2.0s) | timed out 15:32:11.855 (180s) |
| 2 | 15:32:43.228 | 15:32:45.190 (+2.0s) | timed out 15:35:43.231 (180s) |
| 3 | 15:42:14.041 | 15:42:15.966 (+1.9s) | user-interrupted, still pending at 15:43:44 |

All times UTC, 2026-09-16.

### 5. Server-side trace context

W3C `traceparent` captured on the server for one of the hanging calls above:

```
00-b2acd673fcb084d36e29cc0d266aac7d-c20cbf725df2e5a5-01
```

| Field | Value |
|---|---|
| version | `00` |
| trace-id | `b2acd673fcb084d36e29cc0d266aac7d` |
| parent-id (span) | `c20cbf725df2e5a5` |
| trace-flags | `01` (sampled) |

The server span shows the handler completing promptly — it emits the `InputRequiredResult` and returns. There is no corresponding retry request in the trace, confirming the client never issued the second leg. Traces for the remaining two calls can be supplied on request.

Note that this trace context does **not** appear anywhere in the Cowork MCP logs: the client neither logs nor propagates a server `traceparent`, so client↔server correlation currently has to be done by wall-clock timestamp. Surfacing the server's trace context in the client log alongside `Calling MCP tool: …` would make this class of issue substantially cheaper to diagnose, and is worth considering independently of the fix.

---

## Impact

- Any MCP server using 2026-07-28 elicitation to confirm a write is **unusable** in the Cowork desktop app. Every confirming write hangs three minutes and then fails.
- The print-mode branch is taken on an **interactive surface with a user present**. This is not a headless run where dropping the request would be defensible — the person is in the Cowork desktop app, watching the tool call hang, and would have answered a one-checkbox form instantly.
- The failure is **silent and misattributed**. The surfaced error is a transport timeout naming the server, so users reasonably conclude the server is broken. Diagnosing this took an extended session and only resolved on reading the client's own MCP log; the log line is the sole indication that a confirmation was ever requested.
- It **penalises conformant servers**. A server that correctly honours MRTR server-requirement 7 — send elicitation only where the client declared support — is led into the hang by the client's own declaration. A non-conformant server that ignores capabilities and writes unconditionally works fine here.
- Server authors have **no signal to route around it**. The C# SDK's `IsMrtrSupported` returns `true`, because `2026-07-28` genuinely is negotiated. Nothing in capability negotiation distinguishes this surface; only sniffing `clientInfo.name` would, which is not something server authors should be encouraged to build on.

## Suggested fix

Priority order: (1) bridge elicitation to the Cowork approval UI; (2) failing that, return `{"action":"cancel"}` and surface a diagnostic that names elicitation; (3) at minimum, do not advertise the `elicitation` capability from a mode that drops it, and shorten the failure path so it does not consume the full 180s transport timeout.

## References

- [Elicitation — MCP 2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation)
- [Multi Round-Trip Requests (MRTR) — MCP 2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr)
- [SEP-2322: Multi Round-Trip Requests](https://modelcontextprotocol.io/seps/2322-MRTR)
- [MRTR in the MCP C# SDK](https://csharp.sdk.modelcontextprotocol.io/v2/concepts/mrtr/mrtr.html)
- Prior art — same gap in another client: [github/copilot-cli#4834](https://github.com/github/copilot-cli/issues/4834)

---

### Redaction note

Tenant GUID, Microsoft Graph event ID, team/channel IDs, session UUID, `toolUseId`, the `requestState` blob, and user name/email have been removed.

The `traceparent` in §5 is retained deliberately: trace and span IDs are random per-request identifiers that carry no tenant, user, or resource information, and they are what makes the server-side timeline verifiable. They are only resolvable against the Decisions backend, so they are included for correlation on request rather than as something Anthropic can look up directly.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at the MCP client's print-mode handling for elicitation/create and the tools/call retry path. Reproduce with a server returning the provided InputRequiredResult, then verify that Cowork presents the request or fails fast with a diagnostic and that the original call no longer waits 180 seconds.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js
Domain
api, desktop
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.