Return results, errors and behavior hints
Choose between protocol errors and tool errors, and describe tool behavior with annotations.
- Decide between a JSON-RPC protocol error and a tool execution error
- Return content the model can act on
- Set tool annotations - and know why clients can’t fully trust them
Things go wrong in two different ways, and MCP reports them differently:
| Protocol error | Tool execution error | |
|---|---|---|
| Examples | unknown tool, malformed request, server crash | API failure, invalid date, business-rule violation |
| Sent as | JSON-RPC error (e.g. -32602) | a normal result with isError: true |
| Given to the model? | may be | should be - so it can fix its call and retry |
1{
2 "jsonrpc": "2.0",
3 "id": 4,
4 "result": {
5 "resultType": "complete",
6 "content": [
7 {
8 "type": "text",
9 "text": "Invalid departure date: must be in the future. Current date is 2026-10-01."
10 }
11 ],
12 "isError": true
13 }
14}Try it
Which kind of error?
For each failure, decide how the server should report it.
“The client called a tool named “fly_to_moon” that doesn’t exist”
“The date argument is in the past”
“The upstream weather API timed out”
“The request body isn’t valid JSON”
“The customer has no order with that number”
Behavior hints: annotations
Tools can carry annotations that help a client decide what to confirm with the user. The four hints, with their defaults (MCP blog):
readOnlyHint(defaultfalse) - the tool doesn’t modify anything,destructiveHint(defaulttrue) - it may delete, overwrite, send or charge,idempotentHint(defaultfalse) - calling it twice with the same arguments has the same effect as once,openWorldHint(defaulttrue) - it reaches systems beyond the server (the web, email, other people).
The defaults are deliberately cautious: a tool with no annotations is treated as potentially destructive. The destructive and idempotent hints only matter for tools that aren’t read-only.
Try it
Annotate the tools
Set the four hints for each tool, then check. Think about what actually happens when the tool runs - and what happens if it runs twice.
search_docs
Search the company wiki and return matching page titles.
readOnlyHint (default false)
Only reads - changes nothing?
destructiveHint (default true)
May delete, overwrite or send?
idempotentHint (default false)
Calling twice = calling once?
openWorldHint (default true)
Reaches outside systems?
Key takeaways
Unknown tools and malformed requests → JSON-RPC protocol errors; failures while running → results with
isError: true.Tool execution errors go back to the model so it can self-correct.
Annotations (
readOnlyHint,destructiveHint,idempotentHint,openWorldHint) guide confirmations, but are untrusted claims.
Lesson quiz
6 questions · pass with 5 correct · up to 50 XP
Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.
Practice: write Python
Write Python in the editor and run it against sample inputs. Python runs locally in your browser using a WebAssembly runtime.
Report a failure the right way
Read a failure kind and a message on one line, separated by |. Kinds unknown_tool and malformed are protocol errors; invalid_input, api_failure and business_rule are tool execution errors.
For a protocol error print error CODE MESSAGE with code -32602 for unknown_tool and -32600 for malformed. For a tool execution error print result isError=true MESSAGE.
- Unknown tool
- Bad date
- Malformed
Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.
Questions about this lesson
Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.
Loading posts…