Documentation Index
Fetch the complete documentation index at: https://docs.mcp-use.com/llms.txt
Use this file to discover all available pages before exploring further.
Tools are the primary way your MCP server exposes functionality to clients. They represent actions that an AI agent can invoke to perform tasks.
Example
@server.tool(
name="get_weather",
title="Weather Information Provider",
description="Get current weather information for a location",
annotations=ToolAnnotations(
readOnlyHint=True,
openWorldHint=True,
),
)
async def get_weather(
location: Annotated[str, Field(description="City name or zip code")],
units: Annotated[str, Field(description="Temperature units", default="celsius")],
context: Context,
) -> str:
"""Get current weather information for a location."""
await context.info(f"Fetching weather for {location}")
return f"Weather in {location}: 22 {units}"
Here’s the JSON-RPC response that the example above produces when a client calls tools/list:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "get_weather",
"title": "Weather Information Provider",
"description": "Get current weather information for a location",
"annotations": {
"readOnlyHint": true,
"openWorldHint": true
},
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or zip code"
},
"units": {
"type": "string",
"description": "Temperature units",
"default": "celsius"
}
},
"required": ["location"]
}
}
]
}
}
Here’s what maps to what:
| Python | MCP Schema | Notes |
|---|
name="get_weather" | "name" | Falls back to function name if omitted |
title="Weather Information Provider" | "title" | Human-readable display name |
description="Get current weather..." | "description" | Falls back to docstring if omitted |
annotations=ToolAnnotations(...) | "annotations" | Behavioral hints for clients |
location: str | "type": "string" | Python type hint → JSON Schema type |
Field(description="City name...") | property "description" | Argument-level description |
default="celsius" | "default": "celsius" | Makes the parameter optional |
No default on location | "required": ["location"] | Parameters without defaults are required |
context: Context | (not in schema) | Automatically excluded from the schema |
Always use Annotated[type, Field(description="...")] for your tool parameters. Without it, LLMs calling your tool won’t know what each argument means.
Minimal Definition
At minimum, a tool only needs a function with type hints:
@server.tool()
def greet(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
The function name becomes the tool name, the docstring becomes the description, and type hints define the input schema.
The @server.tool() decorator accepts these options:
@server.tool(
name="custom_name", # Override the function name
title="Custom Title", # Human-readable title
description="Custom desc", # Override the docstring
annotations=ToolAnnotations( # Behavioral hints for clients
destructiveHint=True,
readOnlyHint=False,
),
structured_output=True, # Return structured JSON output
)
def my_tool(param: str) -> str:
return param
Tool annotations provide hints to clients about the tool’s behavior:
| Annotation | Description |
|---|
destructiveHint | Tool may modify or delete data |
readOnlyHint | Tool only reads data, no side effects |
idempotentHint | Calling multiple times has same effect as once |
openWorldHint | Tool interacts with external systems |
Tools can be async for non-blocking I/O operations:
import httpx
@server.tool()
async def fetch_url(
url: Annotated[str, Field(description="The URL to fetch content from")],
) -> str:
"""Fetch content from a URL."""
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.text
Using Context
Access the MCP context for logging and progress reporting. Add a Context parameter — it’s automatically excluded from the tool’s input schema:
from mcp_use.server import Context
@server.tool()
async def long_task(
items: Annotated[list[str], Field(description="Items to process")],
context: Context,
) -> str:
"""Process items with progress reporting."""
results = []
for i, item in enumerate(items):
await context.report_progress(i, len(items))
results.append(f"Processed: {item}")
return "\n".join(results)
Parameter Types and JSON Schema
Every tool parameter becomes a property in the tool’s inputSchema. mcp-use uses Pydantic to generate JSON Schema 2020-12 from your Python type hints.
Type mapping
| Python type | JSON Schema | Notes |
|---|
str | {"type": "string"} | |
int | {"type": "integer"} | |
float | {"type": "number"} | |
bool | {"type": "boolean"} | |
list[str] | {"type": "array", "items": {"type": "string"}} | Works with any item type |
dict[str, Any] | {"type": "object"} | |
Literal["a", "b"] | {"type": "string", "enum": ["a", "b"]} | Renders as a dropdown in the Inspector |
Required, optional, and nullable
How you declare a parameter determines whether it appears in the required array:
@server.tool()
def example(
# Required — no default, goes into "required"
name: Annotated[str, Field(description="User name")],
# Optional with default — NOT in "required", default shown in schema
limit: Annotated[int, Field(description="Max results")] = 10,
# Nullable — NOT in "required", type stays "string" (not anyOf)
company: Annotated[str | None, Field(description="Company filter")] = None,
) -> str: ...
This produces:
{
"properties": {
"name": { "type": "string", "description": "User name" },
"limit": { "type": "integer", "description": "Max results", "default": 10 },
"company": { "type": "string", "description": "Company filter", "default": null }
},
"required": ["name"]
}
Optional[str] and str | None are identical at runtime and produce the same schema.
Enum parameters with Literal
Use Literal to restrict a parameter to a fixed set of values. MCP clients like the Inspector render these as dropdown selects:
from typing import Literal
@server.tool()
def create_event(
title: Annotated[str, Field(description="Event title")],
visibility: Annotated[
Literal["public", "private", "team"],
Field(description="Who can see this event"),
],
recurrence: Annotated[
Literal["none", "daily", "weekly", "monthly"],
Field(description="Recurrence pattern"),
] = "none",
) -> dict: ...
Produces {"type": "string", "enum": ["public", "private", "team"]} in the schema.
mcp-use automatically simplifies Pydantic’s nullable anyOf schemas. Without this,
str | None would produce {"anyOf": [{"type": "string"}, {"type": "null"}]} which
breaks description rendering in the MCP Inspector and fails validation in some clients
like Google’s Gemini API. mcp-use collapses this into {"type": "string", "default": null}.
Use Pydantic models for complex inputs:
from pydantic import BaseModel
class SearchQuery(BaseModel):
query: str = Field(description="The search query string")
max_results: int = Field(default=10, description="Maximum number of results to return")
include_metadata: bool = Field(default=False, description="Whether to include metadata")
@server.tool()
def search(params: SearchQuery) -> list[str]:
"""Search with complex parameters."""
return ["result1", "result2"]
Return Types
Tools can return various types:
# String
@server.tool()
def text_tool() -> str:
return "Hello"
# Dict (serialized to JSON)
@server.tool()
def json_tool() -> dict:
return {"key": "value", "count": 42}
# List
@server.tool()
def list_tool() -> list[str]:
return ["a", "b", "c"]
Error Handling
Raise exceptions to indicate errors. The error message will be returned to the client:
@server.tool()
def divide(
a: Annotated[float, Field(description="Dividend")],
b: Annotated[float, Field(description="Divisor")],
) -> float:
"""Divide two numbers."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b