1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
24 small wins to finish your pathNext lesson →
Tool annotations: telling hosts what a tool does
Looking an order up is harmless; refunding one moves money. Annotations let a server say which is which, so a host can ask a person first.
@mcp.tool(
title="Refund an order",
annotations=ToolAnnotations(read_only_hint=False, destructive_hint=False, idempotent_hint=False),
)
def refund_order(order_id: str, reason: str) -> str:
"""Refund the full total of an order to the customer's card."""
if order_id not in ORDERS:
raise ToolError(f"No order with id {order_id!r}.")
return f"Refunded {ORDERS[order_id]['total']:.2f} for order {order_id}: {reason}."The imports gain ToolAnnotations from mcp.types. title is a name for people, shown in place of refund_order. The annotations are hints about behaviour:
read_only_hint: the tool changes nothing.lookup_ordernow sets it toTrue.destructive_hint: an update may delete or overwrite something. A refund adds a transaction rather than destroying data, soFalse.idempotent_hint: calling twice with the same arguments has no more effect than once. Two refund calls would be two refunds, soFalse.open_world_hint, not set here: the tool reaches outside systems, like the web.
import asyncio
from mcp import Client
from shop import mcp
async def main():
async with Client(mcp) as client:
tools = await client.list_tools()
for tool in tools.tools:
hints = tool.annotations
read_only = hints.read_only_hint if hints else None
print(f"{tool.name:14} title={tool.title!r:20} read_only={read_only}")
asyncio.run(main())search_help has no annotations at all, so a careful host has to assume the worst about it. Claude Code and other hosts ask for approval before calling tools according to their own settings, and hints like these are what they can base that on.
Hints, not security
An annotation is the server describing itself. A host may ignore it, and a malicious server can lie in it. Anything that must not happen without approval has to be enforced by your own code, which lesson 14 does for refunds.
Try it yourself
- Mark
search_helpas read-only and list the tools again. - Add
open_world_hint=Falsetolookup_order. - Print
tool.annotationsforrefund_order.
You understood something today that you didn't yesterday.