MCP tool results · field-visibility survey

What MCP clients actually do with tool results

A · content only B · structuredContent first C · forward both D · fallback

The companion to the server survey. That one asks what servers put on the wire; this one asks what clients forward to the model. The MCP spec gives a tool result two places for data — unstructured content and a structuredContent object. For tools with an outputSchema, servers MUST populate structuredContent — and SHOULD also duplicate it into a content text block for backwards compatibility. What a client then forwards to the model is left open — hence this survey.

10 content only structuredContent never reaches the model
4 structuredContent first content text dropped when structured is present
4 forward both whole envelope serialized — payload lands in context twice
1 Fallback content, falling back to structuredContent

The same tool result, handled four ways

Take one tool that returns a dataset. Because it declares an outputSchema, it must populate structuredContent — and it also serializes the rows into content, so the payload sits in both fields. Here is the one response — then what each variant forwards to the model.

One server response

a dataset-returning tool · ~250 rows �— 9 fields

{
  "content": [
    { "type": "text",
      "text": "[{\"id\":\"row-0001\",\"name\":\"Acme Corp\",\"category\":\"Enterprise\",
        \"revenue\":48200000,\"headcount\":1200,\"ageMonths\":31,\"score\":78,
        \"openTickets\":4,\"nps\":62}, … 249 more ]" }
  ],
  "structuredContent": { "rows": [ { … same ~250 × 9 … } ] }   // identical payload
}
Client Variant Acontent only

Model receives the content text — the full ~250-row string. structuredContent is never sent.

strandsagent-framework·pyclinecrewAI +toolsdeepagentsgemini-cligoose*LibreChatRoo-Codezed
Client Variant BstructuredContent first

Model receives JSON.stringify(structuredContent). The content text is dropped.

codex*fast-agentmastraVS Code*
Client Variant Cforward both

Model receives both content and structuredContent — the dataset lands twice.

adk-pythonagent-framework·netkilocodeopencode
Client Variant Dfallback

Both present → both forwarded; structuredContent substitutes only when content is empty. For this response, still a double dump.

hermes-agent
The shape that survives every variant

Keep content a concise, model-useful summary and put the bulk dataset in structuredContent (large or binary blobs behind a resource). The outputSchema contract is still satisfied, Variant A still gets something readable, and no variant dumps the full dataset into the model.


Nineteen clients, four variants

Each row was verified by reading the client's tool-result handling at a pinned SHA — each client name links to its repo at that commit. Open-source clients only: the conversion is checked against source, so closed-source agents are out of scope.

Last updated · commit history

19 rows across 18 projects — agent-framework is counted twice (Python → Variant A, .NET → Variant C).

ClientWhat reaches the modelClient VariantRelated links / PRs
strands content only structuredContent kept as a separate field on the result for hooks/programmatic access; not forwarded to the model (providers serialize only content) A #528
agent-framework Python content only at top level structuredContent read only for embedded blocks; code mode reuses the same parser A #3313 · #4763
cline content text/images only empty → "(No response)"; no structuredContent read A —
crewAI + crewAI-tools content[0].text only crewAI-tools delegates the same content-only path via mcpadapt A —
deepagents content only structuredContent → LangChain artifact (tooling, not the model) A —
gemini-cli content only reverse shim backfills content from structuredContent; never sends sC to the model A #27045
goose conversational: content only code-mode TS sandbox prefers structuredContent — two paths, one client A B —
LibreChat content only empty → "(No response)"; PTC code paths decided in external @librechat/agents A #8447
Roo-Code content text/images only empty → "(No response)" A —
zed content only structuredContent deserialized then discarded on the model path A —
codex structuredContent as whole body if present, else content code-mode runtime/TS sandbox hands the whole result (both) — a second path B #10334 · #2594
fast-agent compact JSON of structuredContent replaces text, else content B #703
mastra structuredContent alone if present, else whole envelope execute_typescript code mode inherits the same wrapper B #10430
VS Code + Copilot Chat JSON.stringify(structuredContent), dropping content text; else content a separate agentHost/Claude path forwards both; copilot-chat inherits, no own conversion B #290063
adk-python entire CallToolResult dict (model_dump) — both fields generic dump, no field-specific logic C #3893
agent-framework .NET whole result serialized — both fields delegated to the .NET MCP SDK; same project as the Variant-A Python binding C —
kilocode raw CallToolResult → AI SDK — both fields delegated C —
opencode whole CallToolResult JSON.stringify'd — both fields incidental, no field-specific logic C #27263 · #28567
hermes-agent both when both present; structuredContent if content empty; else content closest to SEP-2200's fallback — yet still double-dumps this response D #7043 · #7118
Split-path clients
  • goose — conversational replies use content; the code-mode TS sandbox prefers structuredContent.
  • codex — conversational prefers structuredContent alone; the code-mode sandbox gets the whole result (both fields).
  • VS Code — the LanguageModelTool path serializes structuredContent and drops content text; a separate agentHost/Claude path forwards both.
  • agent-framework — same project, two bindings, two variants: Python is content-only (A), .NET forwards both (C).

The finding

The duplication isn't author sloppiness — it's structurally forced. Per the spec, a tool that declares an outputSchema must return structuredContent conforming to it. Yet the Variant A majority (10 of 19) forwards only content to the model — so anything the model needs to read has to live in content too. Declare an output schema and write for the dominant client, and the same payload lands in both fields by construction — the dump in §1. The flooding is a rational response to client fragmentation, not a mistake.