Home · Guides

A tool that fails should return a result, not an error

MCP has two error channels and they are not interchangeable. Which one you use decides whether the model gets to try again.

Protocol errorTool execution error
ShapeJSON-RPC error objectJSON-RPC result with isError: true
MeansThe request was wrongThe request was fine, the work failed
Examples from the specUnknown tool, malformed request, server errorAPI failures, input validation errors, business logic errors
What the client doesMAY pass it to the modelSHOULD pass it to the model

The specification's wording, revision 2026-07-28: protocol errors "indicate issues with the request structure itself that models are less likely to be able to fix", while tool execution errors "contain actionable feedback that language models can use to self-correct and retry with adjusted parameters".

The shapes

A protocol error:

{
  "jsonrpc": "2.0",
  "id": 3,
  "error": { "code": -32602, "message": "Unknown tool: invalid_tool_name" }
}

A tool execution error:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "resultType": "complete",
    "content": [{ "type": "text", "text": "Invalid departure date: must be in the future. Current date is 08/08/2025." }],
    "isError": true
  }
}

Note what is inside the second one. The message names the field, states the rule and gives the value needed to satisfy it. That is what makes a retry possible on the next turn instead of a dead end.

The mistake, and why it is expensive

Server code that validates arguments and throws produces a protocol error, because most SDK wrappers turn a thrown exception at the transport layer into -32602. So a bad date, a value out of range or an expired identifier arrives at the client as "the request was malformed". Clients are only told they MAY show that to the model. When they do not, the model sees a failed call with no reason and either repeats it or gives up.

The same content returned as isError: true is something clients SHOULD show. The fix is usually to catch inside the tool handler and return a result rather than to let the throw escape.

Where the line falls in practice

What to put in the message

An execution error is read by a model deciding what to do next, so write it for that reader. Name the parameter. State the constraint. Give the current value if it helps. Say whether retrying unchanged could work. "Invalid input" fails all four and is the most common thing servers return.

Source: the tools section of the MCP specification, revision 2026-07-28, at modelcontextprotocol.io, fetched 2026-09-09. Both example payloads above are the specification's own. The resultType field shown in the result is part of the same revision.

Questions

Does isError work with structured content?

Yes. isError sits on the result alongside content and structuredContent, so an error result can still carry a machine-readable payload. If your outputSchema describes a success shape, be careful that an error result does not claim to conform to it.

What JSON-RPC error codes should I use?

The standard ones. The specification's example uses -32602, invalid params, for an unknown tool. Inventing codes in the reserved range is not useful because clients key on the presence of an error object, not on its code.

Should I log the failure as well?

Yes, and on stdio the place for it is stderr, never stdout. Stdout carries MCP messages only, and anything else written there corrupts the stream. Clients typically capture stderr to a log file.

How do I test which one my server returns?

Call the tool with a deliberately bad argument and look at the top-level keys of the response. If you see "error", it is a protocol error. If you see "result" with isError true, it is an execution error. Anything that throws before your handler runs will be the former.

Does the client see my exception message?

Only if you put it there. A thrown exception usually becomes a generic protocol error, and stack traces should not be sent to a model in any case. Catch it, decide what the model needs to know, and return that.

Related

All MCP servers and prices · All guides · Buy the bundle $39