[BUG] MCP error results (isError) silently truncated to first/last 5,000 characters, even when the tool sets anthropic/maxResultSizeChars

Status Open
Reported on v2.1.283
Maintainer reply None cached
Activity 1 comment · opened Sep 27, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When an MCP tool returns an error (isError: true) whose text is longer than about 11,000 characters, Claude Code keeps the first and last 5,000 characters and replaces the middle with … [N characters truncated] …. Nothing is saved to a file, so the middle is unrecoverable. This is not documented, and anthropic/maxResultSizeChars on the tool does not change it.

What Should Happen?

Any of:

  • document the error-result limit next to the other MCP output limits;
  • honor anthropic/maxResultSizeChars for error results as well;
  • or persist the full error text to a file, as is done for successful results, instead of dropping the middle.

Error Messages/Logs

<first 5,000 characters of the error> … [1500 characters truncated] … <last 5,000 characters of the error>

Steps to Reproduce

  1. Save the minimal server below as synth_server.py (MCP Python SDK / FastMCP 1.28.1).
  2. mcp.json: {"mcpServers":{"synth":{"type":"stdio","command":"python","args":["synth_server.py"]}}}
  3. Run one tool call per case: claude -p "<call the tool with n=…>" --strict-mcp-config --mcp-config mcp.json --output-format stream-json --verbose
  4. Inspect tool_result in the stream-json log.

raise_error(n) raises an exception so that the client-visible text Error executing tool raise_error: <message> is exactly n characters.

| Case | n (chars) | Result in the model's context |
|---|---|---|
| latin | 9,900 / 10,000 / 10,100 / 10,500 / 11,000 | full error text |
| latin | 11,500 | first 5,000 + … [1500 characters truncated] … + last 5,000 |
| latin | 12,000 / 15,000 / 17,500 / 20,000 | same pattern; truncated 2,000 / 5,000 / 7,500 / 10,000 |
| greek | 11,000 / 11,500 | full / truncated 1,500 (same boundary as Latin) |
| latin, tool with anthropic/maxResultSizeChars: 250000 (raise_error_meta) | 20,000 | still truncated 10,000 |

"""Minimal MCP server to reproduce Claude Code tool-result size limits.

echo_text(n, alphabet)   -> success result of exactly n characters
raise_error(n)           -> error result whose text is exactly n characters
*_meta variants          -> same, with _meta["anthropic/maxResultSizeChars"] = 250000
"""
import random

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("synth")

ALPHABETS = {
    "latin": "abcdefghijklmnopqrstuvwxyz",
    "greek": "αβγδεζηθικλμνξοπρστυφχψω",
}
META = {"anthropic/maxResultSizeChars": 250000}


def make_text(n: int, alphabet: str, line_len: int = 0) -> str:
    """Deterministic random words, exactly n characters, ending with a marker.
    line_len > 0: a newline replaces the space roughly every line_len characters."""
    letters = ALPHABETS[alphabet]
    rnd = random.Random(n)
    marker = " END-OF-TEXT"
    parts, size = [], 0
    while size < n - len(marker):
        w = "".join(rnd.choice(letters) for _ in range(rnd.randint(2, 9))) + " "
        parts.append(w)
        size += len(w)
    body = "".join(parts)[: n - len(marker)]
    if line_len > 0:
        chars = list(body)
        for i in range(line_len, len(chars), line_len):
            chars[i] = "\n"
        body = "".join(chars)
    text = body + marker
    assert len(text) == n
    return text


def error_text(n: int, tool: str, alphabet: str = "latin") -> str:
    """Message such that the client-visible text 'Error executing tool <tool>: <msg>' is exactly n chars."""
    prefix = f"Error executing tool {tool}: "
    return make_text(n - len(prefix), alphabet)


@mcp.tool(structured_output=False)
def echo_text(n: int, alphabet: str = "latin", line_len: int = 0) -> str:
    """Return a text of exactly n characters (alphabet: latin | greek; line_len > 0 - multi-line)."""
    return make_text(n, alphabet, line_len)


@mcp.tool(structured_output=False, meta=META)
def echo_text_meta(n: int, alphabet: str = "latin", line_len: int = 0) -> str:
    """Same as echo_text, tool carries anthropic/maxResultSizeChars=250000."""
    return make_text(n, alphabet, line_len)


@mcp.tool(structured_output=False)
def raise_error(n: int, alphabet: str = "latin") -> str:
    """Fail with an error whose client-visible text is exactly n characters."""
    raise ValueError(error_text(n, "raise_error", alphabet))


@mcp.tool(structured_output=False, meta=META)
def raise_error_meta(n: int, alphabet: str = "latin") -> str:
    """Same as raise_error, tool carries anthropic/maxResultSizeChars=250000."""
    raise ValueError(error_text(n, "raise_error_meta", alphabet))


if __name__ == "__main__":
    mcp.run()

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.283 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Non-interactive/CI environment

Additional Information

Impact: errors that carry actionable lists (e.g. "ambiguous — candidates: …") lose the middle of the list with no indication from the server; the model sees a plausible but incomplete message.

A related undocumented limit on successful results is filed separately: #97622

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗