MCP tool results · server-side field-emission survey

What MCP servers actually put on the wire

A · content only, JSON text B · content only, rendered text C · dual-write D · schema declared, dropped

The companion to the client survey. That one asked what clients forward to the model; this one asks what servers actually emit. 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 mirror it into a content text block for backwards compatibility. We read 22 popular servers' tool-result code at a pinned SHA to see what they put on the wire.

8 content only, JSON text structure survives only as reparseable text — no schema, no structuredContent
10 content only, rendered text structure discarded into prose/markdown/YAML
3 dual-write structuredContent + a content mirror reach the client
1 schema declared, dropped outputSchema authored, then stripped on the wire

The same data, four wire shapes

Take one tool that returns a dataset — ~250 rows × 9 fields. Here is the same payload as the four variants put it on the wire.

Server Variant Acontent only, JSON text

The rows are JSON.stringify'd into one text block. A client can reparse it, but there is no schema and no structuredContent.

{ "content": [ { "type": "text", "text": "[{\"id\":\"row-0001\", … 249 more}]" } ] }
Server Variant Bcontent only, rendered text

The rows are flattened to prose/markdown/YAML. The structure is gone — only a rendering survives.

{ "content": [ { "type": "text", "text": "Acme Corp — Enterprise — $48.2M — 1200 staff\n… 249 more" } ] }
Server Variant Cdual-write

structuredContent carries the typed rows; content carries a mirror. Schema-aware clients get data; legacy clients still get text.

{
  "content": [
    { "type": "text", "text": "```json\n{\"rows\":[ … ]}\n```" },   // mirror (Apify fences it)
    { "type": "text", "text": "250 accounts; top by revenue: Acme Corp" }  // narrative
  ],
  "structuredContent": { "rows": [ { … same ~250 × 9 … } ] }
}
Server Variant Dschema declared, dropped

The tool authors an outputSchema, but the wrapper strips it from tools/list and ships content-only JSON — the type-safety work never reaches the wire.

// outputSchema declared in code · absent from tools/list · no structuredContent on the wire
{ "content": [ { "type": "text", "text": "{\"rows\":[ … ]}" } ] }
The shape that travels best

Variant C, done leanly: keep content a concise, model-useful summary and put the bulk payload in structuredContent (large or binary blobs behind a resource). That satisfies the outputSchema contract, still hands content-only clients something readable, and avoids shipping the full dataset twice. Apify's full duplication does dump it twice; Desktop Commander's summary-in-content + data-in-structuredContent split is the better template.


Twenty-two servers, four variants

Each row was verified by reading the server's tool-result construction at a pinned SHA — each server name links to its repo at that commit. The set is a representative slice of the June-2026 popularity leaderboard plus three widely-installed servers (context7, browser-use, tavily) — each row chosen to show a distinct way of handling the two fields. Open-source servers only.

Last updated · commit history

ServerWhat's on the wireServer VariantSDK / framework
github-mcp-server content only, JSON.stringify'd via MarshalledTextResult no tool declares OutputSchema; csv_output.go explicitly nils StructuredContent A official go-sdk v1.6.1
notion-mcp-server content only, JSON.stringify(response.data) OpenAPI returnSchema parsed but never surfaced as outputSchema A TS-SDK 1.25
blender-mcp content only, json.dumps(result, indent=2) in text every tool typed -> str, so FastMCP derives no schema A FastMCP
browser-use-mcp-server content only, json.dumps inside a legacy list[TextContent] pre-2025-06-18 lowlevel @server.call_tool shape A lowlevel mcp.server
agent-toolkit Stripe content only, upstream JSON as text 4-arg this.tool() has no outputSchema slot; proxy drops the upstream schema; throws on error A TS-SDK 1.17
firecrawl-mcp-server content only, asText = JSON.stringify(data, null, 2) the firecrawl-fastmcp fork gives authors no structuredContent channel at all A firecrawl-fastmcp
mcp-server-cloudflare content only, JSON.stringify(...) via a shared accountTool helper ~12 monorepo apps share one content-only helper A TS-SDK (custom wrapper)
unity-mcp content only, a dict auto-stringified to JSON handlers return dict; FastMCP wraps it as text A FastMCP
Figma-Context-MCP content only, YAML (default outputFormat: yaml) content treated as a model-facing text channel, not serialized data B TS-SDK 1.29
tavily-mcp content only, Title:/URL:/Content: prose the API returns rich JSON (scores, raw_content) but formatResults flattens it before the wire B raw Server
context7 content only, rendered docs (markdown/prose) appropriate here: the text is the product B TS-SDK registerTool
serena content only, mostly -> str text tools registered manually so FastMCP derives no schema; some diagnostics JSON-stringified into text B FastMCP (manual registration)
mcp-server-chart content only, a chart URL in text routes the chart spec through _meta.spec instead of structuredContent (a misuse) B raw Server
magic-mcp content only, markdown (UI component code) BaseTool.execute's return type is content-only by construction B TS-SDK registerTool
spec-workflow-mcp content only, a Toon-encoded struct as text toMCPResponse serializes the ToolResponse object into one text block B TS-SDK setRequestHandler
playwright-mcp content only, markdown accessibility snapshots thin shim; the tool code now lives in playwright-core upstream B custom defineTool
sentry-mcp content only, markdown narrative the test client has a dead hasStructuredContent check the server never satisfies B TS-SDK defineTool
exa-mcp-server split — web_search_exa → prose; web_search_advanced_exaJSON.stringify; both attach a content-block _meta.searchTime two conventions in one repo B A TS-SDK 1.12 (5-arg)
actors-mcp-server Apify structuredContent + content content[0] a fenced ```json mirror, content[1] a narrative; outputSchema on ~every tool — the full-duplication gold standard C TS-SDK 1.29 (raw Server)
DesktopCommanderMCP structuredContent + content content kept a text summary "for broad host compatibility"; structuredContent carries file/preview data for a UI widget — leaner than full duplication C TS-SDK setRequestHandler
XcodeBuildMCP structuredContent + content a render session emits a typed StructuredOutputEnvelope (schema + version) alongside text/image content C TS-SDK registerTool
supabase-mcp content only, JSON.stringify(result) every DB tool declares a Zod outputSchema (required by the wrapper's types), but @supabase/mcp-utils drops it from tools/list and never emits structuredContent; the README tells clients to "fall back to parsing JSON from content" D custom @supabase/mcp-utils
Cross-cutting notes
  • _meta-as-payload misuse — antvis/mcp-server-chart routes the chart spec through _meta.spec; exa attaches _meta.searchTime to a content block. Per spec, _meta is for protocol metadata, not tool payload.
  • Wrappers decide the wire shape — the firecrawl-fastmcp fork and @supabase/mcp-utils both prevent or drop structuredContent regardless of whether the underlying SDK supports it.
  • SDKs disagree on the mirror — the Go SDK writes a byte-identical mirror when Content is nil; Python pretty-prints (indent=2), so its text mirror is not byte-identical to structuredContent; the TypeScript SDK does not auto-mirror at all — it only validates that structuredContent is present when an outputSchema was declared.
  • The three dual-writers do it for rendering — Apify fences JSON for the model; Desktop Commander and XcodeBuild emit structuredContent to drive UI/preview widgets — none adopted it purely to satisfy SEP-2200.

The finding

The popular-server ecosystem is overwhelmingly content-only: 18 of 22 surveyed servers never put structuredContent on the wire. Only three dual-write — and a common claim that Apify is the lone dual-writer turns out to be wrong: Desktop Commander and XcodeBuild do too. But all three do it to feed UI / preview rendering, not to satisfy SEP-2200 — which only reinforces that structuredContent today is pulled by UI use-cases, not by general structured-output conformance. Supabase is the cautionary case: it authors a Zod outputSchema on every database tool, then a custom wrapper drops both the schema and the structured field on the way out.

The bottleneck is adoption and wrapper ergonomics, not SDK capability — TypeScript, Python and Go SDKs have all supported structuredContent since the 2025-06-18 revision. And this is the server-side mirror of the client survey: because most clients forward only content to the model, servers rationally pour everything into content. The two surveys describe the same degenerate equilibrium from opposite ends.