49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
2.2 KiB
2.2 KiB
error-handling
Tool execution failures travel as a successful CallToolResult with
is_error=True so the LLM can read the message and self-correct.
Protocol failures travel as a JSON-RPC error that the client catches as
MCPError. This story shows how to produce each from a tool body — raise ToolError(...) vs raise MCPError(...) on MCPServer; an explicit
is_error=True return vs raise MCPError on lowlevel.Server — and how a
client tells them apart.
Run it
# stdio (default — the client spawns the server as a subprocess)
uv run python -m stories.error_handling.client
# HTTP — the client self-hosts the server on a free port, runs, then tears it down
uv run python -m stories.error_handling.client --http
# same, against the lowlevel-API server variant
uv run python -m stories.error_handling.client --http --server server_lowlevel
What to look at
client.pymain— opens withasync with Client(target, mode=mode) as client:. Inside it,awaitreturns foris_errorresults andexcept MCPErrorcatches protocol errors; the client never auto-raises onis_error.server.py—raise ToolError(...)vsraise MCPError(...): sameraisekeyword, opposite wire channel. The tool wrapper re-raisesMCPErrorverbatim and wraps everything else as anis_errorresult.server_lowlevel.py— no wrapper: you buildCallToolResult(is_error=True)yourself, andMCPErroris the only way to pick a JSON-RPC error code.
Caveats
- The "any other exception →
is_errorresult" contract onMCPServerand the "uncaught exception →code=0" behaviour onlowlevel.Serverare not shown — the contract is under design and the legacy code is a known spec divergence. This story will grow those cases once the contract lands. MCPServerprefixes the execution-error message with"Error executing tool {name}: "; build aCallToolResultdirectly from a lowlevel handler if you need verbatim control.
Spec
See also
tools/ (the happy path), streaming/ (cancellation as a third error-adjacent
surface).