Repository navigation
MCP tool results are pretty-printed, and every consumer of them is a model #2350
Description
Every MCP tool result both SKUs return is serialized with WriteIndented = true, and the only consumer of an MCP tool result is a language model. We are paying to indent JSON for a reader that does not care.
It is one line in one shared file: PerformanceMonitor.Common/Mcp/McpHelpers.cs:32
public static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };Used by 30 of the 74 Darling MCP files and 24 of the 31 Lite ones. It lives in Common, so one change moves both SKUs together — no parity gap to manage. Two readers (DarlingAgReader, DarlingFleetReader) declare their own options with the same flag and would need the same treatment.
Size
Measured on a 15-field record-array result shaped like get_blocking:
| rows | indented | compact | indentation's share |
|---|---|---|---|
| 10 | 2,977 | 2,297 | 22.8% |
| 30 | 8,757 | 6,757 | 22.8% |
| 100 | 29,082 | 22,462 | 22.8% |
These are bytes, not tokens, and the token saving is smaller than the byte saving — BPE tokenizers pack runs of spaces efficiently, so indentation is cheaper in tokens than it looks in bytes. Anyone quoting this should measure tokens before claiming a number. The direction is not in doubt, though, and the change costs nothing.
Why bother if it is only whitespace
Because it compounds with the thing we actually care about. Tool results are the bulk of what fills an agent's context when it works a real incident, and the fleet-wide reads are the widest results we return. A result that no longer fits is the difference between an agent reasoning over 100 blocking events and over 40.
Provenance
This came out of reviewing #2286, which proposed an opt-in third-party wire format and benchmarked it against our indented output. Separating the two matters: some part of the win reported there is a serializer flag rather than the format, and the honest baseline for evaluating any compact format is compact JSON. Credit to @blackwell-systems for the measurement work that surfaced it — this half of it carries no new dependency and no new failure mode, so it stands on its own regardless of what happens to that PR.
Not before 3.5.0
Deliberately not folding this into the release currently staged. It changes the bytes every MCP consumer sees, and the release is already carrying the Aurora fix, the watermark bound, the install-location diagnostic and the peer disclosure. This goes on dev after the tag.
Check before changing
Confirm nothing parses our own tool output positionally or by line. A grep for tests asserting on serialized MCP output with embedded newlines should come first — CrossAppMcpToolInventoryPinTests parses the instructions census, not tool results, but it is worth proving rather than assuming.
Activity
Stop pretty-printing MCP tool results
Stop pretty-printing MCP tool results (#2350) (#2355)
erikdarlingdata commentedon Aug 19, 2026
Fixed in #2355. One property on the shared
McpHelpers.JsonOptions, so both SKUs moved together, plus the two readers carrying their own options for the/api/*twins.Saving is payload-shaped — 23% of the bytes on a 15-field record array, 36% on a narrow one — and the token saving is smaller than either, because BPE tokenizers pack runs of spaces efficiently. Config files people hand-edit still indent, and a test pins that boundary in both directions.
CI caught a gap in my own verification worth recording: I checked for tests asserting on newlines and missed eighteen that depended on the space after the colon. Rather than retype them compact, they now go through
JsonAssert, which normalizes whitespace between tokens while preserving it inside strings — so they assert content, which is what they always looked like they were doing.