Tool error: successful response with `isError: true` plus content the model reads. Protocol error: JSON-RPC error object with numeric code; the host catches it and the model never sees it.
Imagine ordering food at a restaurant. Two ways things can go wrong. First: the kitchen tried to cook your dish but ran out of an ingredient. The waiter brings you a polite note explaining and asks if you want something else. That is a tool error. The tool ran, but the result was a failure. You (the model) read the note and decide what to do next. Second: the waiter does not understand what you said because you spoke a language they do not know. The conversation broke down before anyone could try cooking. That is a protocol error. The system failed before the operation even started. MCP uses different channels for each: a flag inside a normal response for the first kind, a numbered error code for the second.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
MCP has two error channels because two distinct audiences need to react to failures: the LLM (which can change strategy based on information) and the host or its operator (which needs to detect and fix protocol bugs). Sending every failure down one channel collapses the distinction and makes both jobs harder.
This explanation walks each channel in detail, names the standard JSON-RPC codes, gives the decision test for choosing between them, and closes with the most common bug pattern and a useful host-side mitigation for a specific edge case.
Channel one: tool errors via isError in a successful response
When a tool runs but produces a failure outcome, the server returns a successful JSON-RPC response. There is no error object on the response; the result is present as normal. Two fields inside the result signal the failure.
isError: true is a boolean flag marking the result as an error result. The host uses it to decide how to surface the result (e.g., red text in a UI), and the model uses the content to understand what happened.
content is the standard ContentBlock array describing the failure. Typically this is one text block with a clear, actionable error message. 'File not found: /path/to/missing.ts' is actionable; 'tool failed' is not. The message should be specific enough that the model can decide its next move.
The host wraps the result as a tool_result message on the next inference turn. From the model's perspective, the tool returned content that happens to describe an error. The model reads it and decides: retry with corrected arguments, try a different tool, ask the user for clarification, or explain the problem.
Examples that belong on this channel: file not found, API rate limit, SQL syntax error, permission denied, validation rejected the input, downstream service unavailable, timeout. In every case, the model has options for what to do next.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- A filesystem server asked to read a missing file returns `{isError: true, content: [{type: 'text', text: 'File not found: /path/to/missing.ts'}]}`. The model on the next turn retries with a corrected path or asks the user.
- A database server asked to run invalid SQL returns `{isError: true, content: [{type: 'text', text: 'SQL syntax error at line 3: unexpected FROM'}]}`. The model corrects the SQL and retries.
What an interviewer would ask next. Try answering before peeking at the approach.
QWhat is the right path for a tool that hit an unexpected exception during execution?
Catch the exception inside the tool handler. Format the failure as a text content block (surface a clean message, not a stack trace). Return a successful response with isError true. The model reads it and reacts. Only fall through to JSON-RPC -32603 if the failure is in the protocol handler itself, before tool execution started.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Returning a JSON-RPC error for tool business-logic failures. The model never sees JSON-RPC errors. Use `isError: true` in the result so the model can read the failure and decide how to recover.
60 second bullets to scan on the way to the call.
Name both error channels: in-result isError true, and JSON-RPC error object.
Explain which one the model sees (isError) and why the host hides the other.
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.