Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
just 1.58.0
uv 0.12.21
uv 0.12.23
python 3.14 3.13 3.12 3.11 3.10
18 changes: 12 additions & 6 deletions src/libtmux_mcp/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,12 +759,14 @@ class TailPreservingResponseLimitingMiddleware(ResponseLimitingMiddleware):
catches a missed bound within that configured high-volume set.

Error results keep their ``is_error`` flag through truncation.
The stock truncation path rebuilds the result without it, which
turns an oversized error (e.g. a validation message echoing a
huge argument) into an apparent success — MCP clients then
validate the truncated text against the tool's output schema and
fail with a transport-level error instead of delivering the tool
error. ``meta`` (``error_type`` / ``expected`` / ``suggestion``)
FastMCP 4.1.0 forwards the flag to ``_truncate_to_result`` itself
(PrefectHQ/fastmcp#5309); earlier releases rebuild the result
without it, which turns an oversized error (e.g. a validation
message echoing a huge argument) into an apparent success — MCP
clients then validate the truncated text against the tool's output
schema and fail with a transport-level error instead of delivering
the tool error. ``on_call_tool`` restores the flag for those
releases. ``meta`` (``error_type`` / ``expected`` / ``suggestion``)
already survives via the base class, and tail-preservation keeps
the suggestion line, which sits at the end of the text.
"""
Expand Down Expand Up @@ -801,6 +803,7 @@ def _truncate_to_result(
self,
text: str,
meta: dict[str, t.Any] | None = None,
is_error: bool = False,
) -> ToolResult:
"""Keep the last ``max_size`` bytes of ``text`` and prefix a header.

Expand All @@ -813,6 +816,7 @@ def _truncate_to_result(
return ToolResult(
content=[TextContent(type="text", text=text)],
meta=meta if meta is not None else {},
is_error=is_error,
)

# Reserve space for the truncation header. The header length
Expand All @@ -828,6 +832,7 @@ def _truncate_to_result(
return ToolResult(
content=[TextContent(type="text", text=header.rstrip("\n"))],
meta=meta if meta is not None else {},
is_error=is_error,
)

# Take the LAST ``target_size`` bytes. Decode with errors=ignore
Expand All @@ -840,4 +845,5 @@ def _truncate_to_result(
return ToolResult(
content=[TextContent(type="text", text=truncated)],
meta=meta if meta is not None else {},
is_error=is_error,
)
Loading
Loading