MCP gives tools two ways to report failure. A **protocol error** is a standard JSON-RPC error for problems with the request itself: an unknown tool, a request that fails the `CallToolRequest` schema, or a server error. A **tool execution error** is a normal tool result with `isError: true`, used when the request was valid but the tool failed: an API failure, an input validation error, or a business logic error. The tools spec's Error Handling section says tool execution errors "contain actionable feedback that language models can use to self-correct and retry with adjusted parameters," while protocol errors are issues "that models are less likely to be able to fix" (spec 2026-07-28).
Which mechanism to use
All rows below follow the Error Handling section of the tools spec, 2026-07-28.
| Failure | Mechanism | How it's returned |
|---|---|---|
| Unknown tool name | Protocol error | JSON-RPC error (spec example: code `-32602`, "Unknown tool: invalid_tool_name") |
| Malformed request (fails `CallToolRequest` schema) | Protocol error | JSON-RPC error |
| Server error | Protocol error | JSON-RPC error |
| Upstream API failure | Tool execution error | Result with `isError: true` |
| Input validation (wrong date format, value out of range) | Tool execution error | Result with `isError: true` |
| Business logic error | Tool execution error | Result with `isError: true` |
| Expired or unknown handle in a stateful tool | Tool execution error | Result that says so, "so the model can recover by creating a new one" |
Why the split matters
The spec ties the two mechanisms to what the client does with them: "Clients MAY provide protocol errors to language models, though these are less likely to result in successful recovery. Clients SHOULD provide tool execution errors to language models to enable self-correction" (spec 2026-07-28). If you report a fixable input problem as a JSON-RPC error, you are using the mechanism the spec treats as less likely to reach the model.
A tool execution error looks like an ordinary result:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{ "type": "text", "text": "Invalid date '10/14/2026'. Expected format YYYY-MM-DD." }
],
"isError": true
}
}Error codes for protocol errors
The base protocol page marks `-32000` to `-32019` as legacy: new codes MUST NOT be allocated there and new implementations SHOULD NOT use them. `-32020` to `-32099` is reserved for the MCP spec (spec 2026-07-28).
Pitfalls
- Throwing a JSON-RPC error for bad input. A wrong date format or out-of-range value is a tool execution error per the spec; return it with `isError: true`.
- Vague error text. "Something went wrong" gives the model nothing to act on. Say what failed and what valid input looks like.
- Returning success for a stale handle. For stateful tools, the spec says to return a tool execution error that says the handle is expired or unknown.
- Picking codes in the legacy range. Avoid `-32000` to `-32019` for new protocol errors.
How to check it
Call your server with an unknown tool name and confirm you get a JSON-RPC error. Then call a real tool with a deliberately invalid argument and confirm you get a `result` with `isError: true` and text that says how to fix the input.
Where the kit fits
The MCP Starter Kit ($27) is built for teams putting OAuth-protected remote MCP servers in front of Cursor and Claude Connect. Soft path: start at /free; Cheat Sheet for the skim checklist mindset; kit when you want the auth-ready layout.
Soft next step
Go through each tool's failure paths and sort them into request problems (protocol errors) and fixable tool failures (`isError: true`), with error text the model can act on. Soft ladder: /free, then the MCP Starter Kit ($27). Related: OAuth pillar, MCP OAuth failure card, MCP tool annotations.