> ## 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

> Define and expose tools from your MCP server

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

```python theme={null}
@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}"
```

## Anatomy of a Tool

Here's the JSON-RPC response that the example above produces when a client calls `tools/list`:

```json theme={null}
{
  "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   |

<Tip>
  Always use `Annotated[type, Field(description="...")]` for your tool parameters. Without it, LLMs calling your tool won't know what each argument means.
</Tip>

## Minimal Definition

At minimum, a tool only needs a function with type hints:

```python theme={null}
@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.

## Tool Options

The `@server.tool()` decorator accepts these options:

```python theme={null}
@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

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           |

## Async Tools

Tools can be async for non-blocking I/O operations:

```python theme={null}
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:

```python theme={null}
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](https://docs.pydantic.dev/) to generate [JSON Schema 2020-12](https://json-schema.org/draft/2020-12/schema) 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:

```python theme={null}
@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:

```json theme={null}
{
  "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"]
}
```

<Tip>
  `Optional[str]` and `str | None` are identical at runtime and produce the same schema.
</Tip>

### 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:

```python theme={null}
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.

<Note>
  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}`.
</Note>

## Complex Input Types

Use Pydantic models for complex inputs:

```python theme={null}
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:

```python theme={null}
# 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:

```python theme={null}
@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
```
