[BUG] Claude Desktop code agent drops MCP per-tool `_meta` — `anthropic/maxResultSizeChars` ignored (CLI honors it)
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
- The server verifiably emits
_meta. A raw JSON-RPC probe of the exact stdio server registered inclaude_desktop_config.jsonshowstools/listentries carrying_meta: {"anthropic/maxResultSizeChars": 500000}. - The same binary honors it via
--mcp-config. Running the Desktop-embeddedclaudebinary directly (-por stream-json) against the same server config inlines the same ~80k-char result — even withMAX_MCP_OUTPUT_TOKENS=1000, proving the annotation bypass fires before the cap check. - 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.
- Desktop launches the code-agent child with
--input-format stream-jsonand no--mcp-config— MCP tool definitions are provisioned through the app/SDK layer. The Desktop app bundle (app.asar) contains no references tomaxResultSizeChars, 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, callbig_annotated→ inline (correct). Callbig_plain→ persisted (correct). - Claude Desktop: register the same server in
claude_desktop_config.json, open a code-agent session, callbig_annotated→ persisted (bug) — behaves exactly likebig_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
_metaobject 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.