MCP tool response text-content block dropped when structuredContent is also present (runtime_get_value)
Description
When calling an MCP tool whose response includes both a structuredContent block (JSON metadata) and a content text block (the actual document body), Claude Code appears to surface only the structured block and silently drops the text block.
Repro
- MCP server: custom internal
runtime-mcpserver (HTTP transport) - Tool:
runtime_get_value(name="consumer-service", filepath="xx/xx.json") - Called identically across: fresh session, re-authenticated session, and after fully removing/re-adding the MCP server — same result every time.
Expected: tool returns the full document body (JSON text) plus metadata, per the tool's own description ("Returns the document body as text ... plus publication metadata ... as separate structured content").
Actual (in Claude Code):
{"namespace":"consumer-service","file":"xx/xx.json","cell":"cell-001","doc_type":"json","document_id":" etc}
Only the metadata wrapper — no document body text.
Confirmed not server-side: the identical tool call against the identical MCP server, run from Cursor, returns the full body correctly (JSON with 26 store IDs). This isolates the bug to Claude Code's handling of the MCP response, not the server.
Environment: Claude Code CLI, macOS (Darwin 25.5.0)
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Independent Linux reproduction with converter-level cause
Confirmed the same defect on Claude Code CLI 2.1.222 with a local stdio MCP server.
Wire and transcript proof
The server returns a valid mixed result:
The recorded Claude Code
tool_result.contentis instead the 91-character serialization ofstructuredContent; the answer text block is absent. A direct MCP stdio handshake against the same server returns the answer, so the loss occurs after the client receives the validCallToolResult.Converter cause
Read-only extraction from the installed 2.1.222 binary at byte offset
266806674recovered functionIhd(mapToolResultToToolResultBlockParam). Its effective order is:structuredContentexists, serialize it;contentcontains non-text blocks, preserve only those blocks and append serialized metadata;contentarray only whenstructuredContentis absent.Therefore an all-text
contentarray is deterministically dropped whenever the result also containsstructuredContent.Calibrated synthetic probe
A minimal in-memory reproduction of that exact branch produced:
The control direction matters: the same converter preserves the answer when
structuredContentis omitted. This rules out the probe/parser itself as the source of the disappearance.Suggested regression gate
contentplusstructuredContent: preserve the text and retain metadata without replacement;structuredContentcannot remove pre-existing text content.This affects every MCP tool returning text plus structured metadata, not only the server used for this reproduction.
Adding one more data point in case it's helpful. I'm not sure whether this is intended behavior, so I'm just sharing what I observed.
On Claude Code 2.1.280 (macOS, stdio transport,
claude -p --strict-mcp-config --output-format stream-json --verbose), I used a small MCP server whose tools put different marker strings incontentandstructuredContent. Then I read thetool_resultblocks Claude Code passed to the model:| tool | server sent |
tool_resultpassed to the model ||---|---|---|
|
both_with_schema(declaresoutputSchema) |content:CONTENT-MARKER-A (markdown rendering)<br>structuredContent:{"marker":"STRUCTURED-MARKER-A"}|"{\"marker\":\"STRUCTURED-MARKER-A\"}"||
both_no_schema(nooutputSchema) |content:CONTENT-MARKER-B (markdown rendering)<br>structuredContent:{"marker":"STRUCTURED-MARKER-B"}|"{\"marker\":\"STRUCTURED-MARKER-B\"}"||
content_only|content:CONTENT-MARKER-C (markdown rendering)|[{"type":"text","text":"CONTENT-MARKER-C (markdown rendering)"}]|What I noticed:
structuredContentis present, the model getsJSON.stringify(structuredContent), and thecontenttext doesn't appear anywhere in the stream (including thetool_use_resultrecord). That happens whether or not the tool declares anoutputSchema.structuredContentis the only way I found to get thecontenttext to the model.For context, and without claiming this is a bug: the spec describes
contentas present alongsidestructuredContent, and its example keeps the two semantically identical. Servers that put different, model-oriented text incontentare going past that guidance, which the MCP maintainers discussed and deferred in SEP-2200, with SEP-3279 as the proposed follow-up. Other clients currently handle this differently, for example Cursor and MCP Apps hosts readingcontent. So this may be a deliberate choice. Mostly it would help server authors to know which field Claude Code treats as model-visible, maybe through a note in the MCP docs.<details>
<summary>Probe server (Node, no dependencies)</summary>
mcp.json:{ "mcpServers": { "probe": { "command": "node", "args": ["/path/to/server.mjs"] } } }</details>
Thanks for all the work on Claude Code's MCP support.
Still reproducible on Claude Code 2.1.283 (macOS, stdio,
claude -p --strict-mcp-config --output-format stream-json --verbose).A test server's tool returned two
contenttext blocks plusstructuredContent:| server sent |
tool_resultpassed to the model ||---|---|
|
content: [JSON body, trailing notice text] +structuredContent| only thestructuredContentJSON; both text blocks dropped, including the first one || same two
contentblocks, nostructuredContent| both text blocks |So any extra text a server appends after the serialized result (warnings, pagination notes, disclosures) silently never reaches the model once
structuredContentis present.Workaround we shipped server-side: we also copy those trailing text blocks into
structuredContent(an optionalnotices: string[]field, declared inoutputSchema), and keep them incontentfor content-only clients.If dropping
contentis intended, documenting it would help. It is easy to miss because the spec only says structured results SHOULD also be serialized into a text block, and says nothing about clients ignoring the rest ofcontent.