Skip to content

43 critical output size unboundedness

Part VII: Ecosystem, Runtime & Agent-Specific | Challenge §43

43. Tool Output Result Size Unboundedness

Severity: Critical | Frequency: Common | Detectability: Hard | Token Spend: Critical | Time: High | Context: Critical

The Problem

Challenge #5 (Pagination & Large Output) addresses paginated list commands that return many items. This challenge addresses a different problem: a single tool invocation or command that returns one item but that item itself is arbitrarily large — for example, a file's contents, a log excerpt, a search result body, or a database record with large text fields.

Unlike list pagination (where the fix is clear: add --limit and cursor), single-result unboundedness has no standard solution. The agent cannot know in advance how large the result will be, and many commands have no way to truncate a single result meaningfully (you cannot return half a file, for example).

In MCP specifically, a single tools/call response can contain a text body of arbitrary size. There is no max_output_bytes field in the call, no Content-Length, and no Content-Range equivalent. A command that reads a 10MB log file returns all 10MB in a single JSON response, filling (or overflowing) the agent's context window entirely.

# Agent invokes a file-reading tool, not knowing the file size
result = await mcp_client.call_tool("read_file", {"path": "/var/log/app.log"})
# result.content[0].text might be 50,000 lines = 400,000 tokens
# Entire context window consumed; agent cannot reason about anything else
# CLI equivalent: no output limit
$ my-tool get-record --id 12345 --format json
# Returns a record with a "description" field containing 200KB of text
# Agent receives all 200KB as stdout; must include it all in context for parsing

Pydantic's weakness note identifies this as "no standard paginated response model" — but the problem is deeper: there is no standard way to express output size constraints at the schema level, so agents cannot even request a truncated form before invocation.

Impact

  • Context window overflow: a single large result can consume the entire remaining context window, preventing further reasoning
  • Forced truncation: agents that truncate large outputs mid-stream may corrupt structured data (e.g., cutting JSON in the middle)
  • Token spend: even if the context window is large, reading 400K tokens of log output at token prices is expensive
  • No recovery path: unlike list pagination (where the agent can request the next page), there is no "next chunk" for a single large result
  • Agents cannot pre-flight the size before calling the tool

Solutions

For CLI/tool authors:

# Provide a --max-length or --truncate flag
my-tool get-record --id 12345 --max-length 10000 --truncate-mode head

# Output envelope should signal truncation
{
  "ok": true,
  "data": {"id": "12345", "description": "First 10000 chars..."},
  "meta": {"truncated": true, "total_bytes": 204800, "returned_bytes": 10000,
           "truncation_hint": "Use --offset and --max-length for subsequent chunks"}
}

For framework design: - Implement a default output size limit per command (e.g., 50KB of text content) with the excess truncated and meta.truncated: true set - Provide a --max-output flag (injected automatically on all commands) that the agent can set to control output size - For large string fields in responses, automatically truncate at a configurable max_field_length (default: 10,000 chars) and add a "_truncated": true marker on the field - In MCP tool definitions, expose maxOutputBytes as a tool annotation so clients can pre-negotiate output size - Schema should declare "max_output_bytes": 51200 as a tool property, allowing agents to assess expected output size before calling

Evaluation

Score Condition
0 Single-result commands return unbounded output; no meta.truncated; no --max-output flag; agent has no pre-flight size check
1 --max-length flag exists on some commands; no meta.truncated signal when truncation occurs
2 meta.truncated: true and meta.total_bytes present when truncation occurs; --max-output or --max-length accepted
3 Default output limit enforced per command; max_output_bytes declared in schema; large string fields auto-truncated with _truncated: true marker

Check: Pass a known large resource (e.g., a log file >50KB) to any read command — verify the response includes meta.truncated: true and meta.total_bytes rather than returning all content.


Agent Workaround

Estimate output size before processing; use --max-output to bound large results; always check meta.truncated:

import subprocess, json, os

MAX_OUTPUT_TOKENS = 8000   # conservative context budget
MAX_OUTPUT_BYTES = MAX_OUTPUT_TOKENS * 4  # ~4 bytes/token

result = subprocess.run(
    ["tool", "get-record", "--id", record_id,
     "--max-output", str(MAX_OUTPUT_BYTES),
     "--output", "json"],
    capture_output=True, text=True,
)

output_bytes = len(result.stdout.encode())
approx_tokens = output_bytes // 4
if approx_tokens > MAX_OUTPUT_TOKENS:
    raise RuntimeError(
        f"Output too large (~{approx_tokens} tokens). "
        "Use --fields to select specific fields or --max-output to truncate."
    )

parsed = json.loads(result.stdout)
if parsed.get("meta", {}).get("truncated"):
    total = parsed["meta"].get("total_bytes", "unknown")
    print(
        f"WARNING: Output was truncated ({total} total bytes). "
        "Use --offset and --max-output for subsequent chunks if needed."
    )

Request only needed fields to reduce output size:

result = subprocess.run(
    ["tool", "get-record", "--id", record_id,
     "--fields", "id,name,status",   # only what the agent needs
     "--output", "json"],
    capture_output=True, text=True,
)

Limitation: If the tool has no --max-output or --fields flag and returns unbounded single-result output, the only option is to post-process the raw output — extract just the needed fields using jq or Python dict access and discard the rest before storing in context