The response budget
Five stacked mechanisms so a fleet read fits a client's tool-output limit - and a cap that errors rather than truncates.
This section exists because of a measured failure: fleet_show with no arguments, on a
real 68-stream record, exceeded the MCP client's tool-output limit — the response was
dropped to a file instead of reaching the model. The projection was correct; the budget did
not exist. MCP offers no mechanism for a client to declare its budget to a server, so the
server must choose defaults and choose well. Five mechanisms now stack, each with
production precedent behind it:
Default live horizon. fleet_show and wave_show default to horizon: "live" —
terminal streams (merged, closed-without-merge) are excluded. On the measured record that
was 83% of the payload: dead streams carrying expired liveness blocks and week-old gate
prose. The view is named fleet_show; the fleet is what flies. History is one explicit
argument away (horizon: "all" — byte-identical to a pre-narrowing read), and a defaulted
response names the default that fired rather than narrowing silently:
Honest summary. detail: "summary" (the default) carries the stream-identity and state
fields, a four-field last_gate (no justification prose — that lives in stream_detail
and full), and a trimmed liveness block. detail: "full" remains complete; the budget
moves data behind an explicit request, it never removes data. The descriptors enumerate
both levels exhaustively.
Cursor pagination as tool arguments. limit defaults to 25, maximum 100 — a value
above the maximum is rejected with limit must be an integer between 1 and 100, never
silently clamped. A truncated response is a successful bounded response
(isError: false): it carries next_after, which names the exact next call
(after: <cursor>), and total_count still answers how many streams exist unfiltered — a
truncated list can never be mistaken for the complete set. (MCP protocol pagination
deliberately does not cover tools/call; these are ordinary arguments read by the model
from the descriptor, which is why the descriptor discipline above is load-bearing.)
Tabular formats. format: "csv" / "tsv" return rows only, a fixed narrow column set
— measured at roughly 29% fewer tokens than JSON for this row shape. This changes the read
contract (a table, not the envelope) and is therefore opt-in.
A hard byte cap as an error, never a silence. After all narrowing has run, a response
at or above 200,000 bytes is refused, class result-too-large, with the exact next move:
The reasoning is recorded with its precedents: clients demonstrably truncate oversized tool
results silently, at boundaries the server did not choose, and the model then answers with
full confidence from a fragment. If the server does not cut explicitly, something below
will cut implicitly. Additionally, fleet_show / wave_show declare
_meta["anthropic/maxResultSizeChars"] = 200000 — a transitional client-side allowance
above the cap, documented in the code as "never a substitute for the narrowing".
The regression test for this class is deliberate: an artificial record of ≥200 streams must fit the default query's budget — the test closes the class, not the day's instance.