# libtmux_mcp.middleware.ToolErrorResultMiddleware

- **Module:** libtmux_mcp.middleware
- **Package:** libtmux-mcp
- **Language:** Python
- **Kind:** class
- **Source:** https://github.com/tmux-python/libtmux-mcp/blob/4daddc0dfca96c43bf521d818bf91564e1434760/src/libtmux_mcp/middleware.py#L398
- **Page:** https://libtmux.org/en/py/latest/mcp/reference/libtmux_mcp-middleware-toolerrorresultmiddleware/

Convert tool-call failures into rich ``ToolResult`` errors.

Replaces the stock :class:`ErrorHandlingMiddleware` behavior for
``tools/call`` only. The stock ``transform_errors=True`` path
funnels every non-MCP exception through a ``-32603`` catch-all, so
agents received ``"Internal error: Pane not found: %5"`` — the
transform mangled every expected failure message. This subclass
intercepts tool-call exceptions first (``on_call_tool`` is the
innermost hook of a middleware's chain) and returns
:func:`_error_tool_result` instead; non-tool messages fall through
to the inherited ``on_message``.

That inherited transform matches the not-found exception types by
exact class -- ``error_type in (FileNotFoundError, KeyError,
NotFoundError)`` -- so only an unknown resource URI, which fastmcp
raises as ``NotFoundError`` itself, reaches a client as ``-32002``.
The ``tmux://`` handlers raise :exc:`~fastmcp.exceptions.ResourceError`
for a missing session, and path screening raises
``ResourceSecurityError``, a ``NotFoundError`` *subclass*; neither is
an exact match, so both arrive as ``-32603`` "Internal error" -- the
code that tells a client the server is broken, for what is really an
absent or disallowed target.

Logging honors ``FastMCPError.log_level`` (fastmcp >= 3.3): the
expected failures demoted to WARNING by
:class:`~libtmux_mcp._utils.ExpectedToolError` no longer get
re-shouted at ERROR by the stock ``_log_error``. Argument-schema
validation failures — raised by fastmcp before tool code can
classify them — are treated as expected too (see
:func:`_is_schema_validation_error`), and when the rejected
arguments include unexpected names the error result carries a
synthesized suggestion telling the agent which names to drop or
fix (see :func:`_error_tool_result`).

Ordering invariant: must sit **outside** ``AuditMiddleware`` and
``ToolsetMiddleware``. Both depend on exception semantics: audit detects
failures by catching them, and toolset denials must propagate as exceptions
for audit to record them. Converting the exception to a result any deeper in
the stack would silently break both.

## Members

- `on_call_tool` (method): Convert tool-call exceptions into ``is_error`` results.
