In the last lesson a missing order came back as {"error": "Order not found"}, and for the client that was a normal answer: nothing said the call had failed. A tool says "this call failed" by raising a ToolError with a message. The client then gets the result marked as an error, with your message in it. Any other exception also comes back as an error, but without its text, so details about your database never leave the server.
A server can hold as many tools as you like, each a function under its own @mcp.tool(). It can also offer resources: things to read, like a document. A resource has an address called a URI, and its function sits under @mcp.resource with that address. Here are both ideas on a calculator server:
@mcp.tool()
def divide(a: int, b: int) -> float:
"Divide a by b."
if b == 0:
raise ToolError("Cannot divide by zero")
return a / b
@mcp.resource("help://units")
def units() -> str:
"The units this calculator uses."
return "All amounts are in euros."
The difference is who asks. A model asks for a tool to be run. A resource is fetched by the app, which can put its text in front of the model. In both cases the docstring becomes the description.
The check connects to your server, lists its tools, calls them, then lists the resources and reads the policy. It must print:
Tools: 2
- lookup_order: Look up one order of the Corner Shop by its id: its date and status.
input: order_id (integer, required)
- find_orders: Find a customer's orders by their email address, newest first.
input: email (string, required)
call lookup_order {"order_id": 4} -> ok: {"id": 4, "ordered": "2026-04-18", "status": "shipped"}
call lookup_order {"order_id": 40} -> error: Error executing tool lookup_order: Order not found
call find_orders {"email": "chloe@example.com"} -> ok: {"id": 7, "ordered": "2026-05-30", "status": "paid"}, {"id": 4, "ordered": "2026-04-18", "status": "shipped"}
call find_orders {"email": "eva@example.com"} -> ok: (empty)
Resources: 1
- policy://returns: The shop's returns policy.
read policy://returns -> Items can be returned within 14 days of delivery, unused and in the original packaging.
Your task: in get_order, raise a ToolError with the message "Order not found" when no row is found. Then add the tool find_orders, which takes an email (text) and returns what get_orders finds, and the resource policy://returns, whose function returns_policy returns RETURNS_POLICY.
Assignment
Follow the Coding Tutorial to make a missing order a real error, add a second tool and offer the returns policy as a resource.
Hint
Each new function needs its own decorator on the line above it. @mcp.tool() has empty parentheses; @mcp.resource(...) takes the URI as a string. Inside each function the docstring comes first, then the return.
Free plan: 30 tutor messages. PRO is unlimited.
