[BUG] Claude Desktop code agent drops MCP per-tool `_meta` — `anthropic/maxResultSizeChars` ignored (CLI honors it)

Status Open
Reported on v2.1.209
Maintainer reply None cached
Activity 0 comments · opened Jul 17, 2026

Summary

The code agent embedded in Claude Desktop does not honor the MCP per-tool _meta["anthropic/maxResultSizeChars"] annotation. The same harness binary honors it when tools are supplied via --mcp-config. The result: a large MCP tool result that inlines fine in the CLI gets diverted to disk only in Desktop:

Error: result (80,000 characters across 1 line) exceeds maximum allowed tokens.
Output has been saved to .../tool-results/mcp-<server>-<tool>-<timestamp>.txt

The evidence points at Desktop's MCP provisioning layer dropping per-tool _meta before tool registration, so the annotation never arms.

Environment

  • Claude Desktop 1.22209.0 (macOS, Darwin 25.4.0, arm64)
  • Embedded code agent: claude-code 2.1.209 (CLAUDE_CODE_ENTRYPOINT=claude-desktop, Agent SDK 0.3.209)
  • Also reproduced by running the Desktop-embedded 2.1.209 binary directly; standalone CLI 2.1.212 behaves identically (honors the annotation)

Expected

A tool advertising _meta: {"anthropic/maxResultSizeChars": 500000} in tools/list should have results up to that size returned inline — as happens in the CLI.

Actual (Desktop code agent only)

A ~80k-char result from the annotated tool is persisted to a file with the "exceeds maximum allowed tokens" notice, deterministically, on every call.

Evidence the annotation is lost in Desktop's provisioning, not the harness

  1. The server verifiably emits _meta. A raw JSON-RPC probe of the exact stdio server registered in claude_desktop_config.json shows tools/list entries carrying _meta: {"anthropic/maxResultSizeChars": 500000}.
  2. The same binary honors it via --mcp-config. Running the Desktop-embedded claude binary directly (-p or stream-json) against the same server config inlines the same ~80k-char result — even with MAX_MCP_OUTPUT_TOKENS=1000, proving the annotation bypass fires before the cap check.
  3. Controlled A/B on the same binary, same payload. A minimal stdio MCP server returning an identical ~80k-char text:
  • tool declared with _meta["anthropic/maxResultSizeChars"] → result inline
  • tool declared without _meta → result persisted

So the annotation is the sole deciding factor, and in real Desktop sessions the behavior matches the without case.

  1. Desktop launches the code-agent child with --input-format stream-json and no --mcp-config — MCP tool definitions are provisioned through the app/SDK layer. The Desktop app bundle (app.asar) contains no references to maxResultSizeChars, and its tool-forwarding mappings include shapes that omit _meta ({name, description, inputSchema}) or rename it (meta: a._meta).

Minimal repro

server.mjs — a stdio MCP server with two tools returning the same ~80k-char payload, one annotated, one not:

const BIG = "x".repeat(80000)
let buf = ''
process.stdin.on('data', d => {
  buf += d
  let i
  while ((i = buf.indexOf('\n')) >= 0) {
    const line = buf.slice(0, i); buf = buf.slice(i + 1)
    if (!line.trim()) continue
    let m; try { m = JSON.parse(line) } catch { continue }
    const reply = o => process.stdout.write(JSON.stringify(o) + '\n')
    if (m.method === 'initialize') reply({ jsonrpc: "2.0", id: m.id, result: { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "repro", version: "0" } } })
    else if (m.method === 'tools/list') reply({ jsonrpc: "2.0", id: m.id, result: { tools: [
      { name: "big_annotated", description: "80k chars, WITH _meta", inputSchema: { type: "object", properties: {} }, _meta: { "anthropic/maxResultSizeChars": 500000 } },
      { name: "big_plain", description: "80k chars, NO _meta", inputSchema: { type: "object", properties: {} } },
    ] } })
    else if (m.method === 'tools/call') reply({ jsonrpc: "2.0", id: m.id, result: { content: [{ type: "text", text: BIG }] } })
    else if (m.id !== undefined) reply({ jsonrpc: "2.0", id: m.id, result: {} })
  }
})
  • CLI: register via --mcp-config, call big_annotated → inline (correct). Call big_plain → persisted (correct).
  • Claude Desktop: register the same server in claude_desktop_config.json, open a code-agent session, call big_annotatedpersisted (bug) — behaves exactly like big_plain.

Impact

  • Any MCP server relying on anthropic/maxResultSizeChars (e.g. skill/guide loaders that must deliver one large document into context) breaks only in Desktop.
  • Since the whole _meta object appears to be dropped, other per-tool conventions riding it (anthropic/searchHint, anthropic/requiresUserInteraction) are presumably lost in Desktop too.

Workaround (partial)

"env": {"MAX_MCP_OUTPUT_TOKENS": "60000"} in ~/.claude/settings.json — the Desktop child runs with --setting-sources=user,project,local, so it picks this up and raises the token gate. But non-annotated tools also hit the fixed 100,000-char result cap, so true annotation parity (500k chars) is unreachable without the fix — hence this report.

Suggested fix

Forward per-tool _meta verbatim wherever Claude Desktop relays MCP tool definitions to the embedded code agent.

View original on GitHub ↗