Tools are the actions a model can take through your server. Define them from typed functions, write descriptions a model can act on, and return errors as data.
Why: FastMCP reads your function’s type hints and docstring to build the JSON Schema and description the model sees — so good types and a clear docstring ARE the tool’s interface. When: annotate every parameter and return type; the schema is generated from them. Where: the docstring becomes the description the model uses to decide when to call the tool.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather")
@mcp.tool()
def get_forecast(city: str, days: int = 1) -> dict:
"""Get the weather forecast for a city.
Args:
city: City name, e.g. "Cairo".
days: How many days ahead (1-7).
"""
# A real tool would call a weather API here.
return {"city": city, "days": days, "high_c": 34, "sky": "clear"}Why: the model chooses tools from their descriptions alone, so two vague tools become indistinguishable and it calls the wrong one — write descriptions that say what the tool does AND when to use it. When: make side effects explicit ("creates a ticket", "sends an email") so the model treats them with care. Where: precision here is the difference between a reliable server and a frustrating one.
@mcp.tool()
def create_ticket(title: str, priority: str = "normal") -> dict:
"""Create a NEW support ticket in the tracker. This has a side effect —
it writes a record. Use only when the user explicitly asks to open a
ticket, not to look one up (use find_ticket for that)."""
return {"id": "TICK-1042", "title": title, "priority": priority}Why: if a tool raises an unhandled exception, the host sees an opaque failure — instead catch expected problems and return a clear message the model can act on. When: validate inputs and turn bad arguments or upstream failures into readable error results. Where: the model reads the returned text and can retry, ask the user, or explain the failure.
@mcp.tool()
def get_forecast(city: str, days: int = 1) -> dict:
"""Get the weather forecast for a city (days 1-7)."""
if not 1 <= days <= 7:
# Returned as a normal result the model can read and recover from,
# not a crash the host has to guess about.
return {"error": f"days must be 1-7, got {days}"}
return {"city": city, "days": days, "high_c": 34, "sky": "clear"}