---
title: Tools
description: Define and use tools in model and agent workflows.
type: guide
summary: Define function tools, design schemas, handle tool errors, use schema-only tools, provider tools, and MCP tools.
---

# Tools



AI SDK for Python supports multiple types of tools, including function tools
defined in code using `@ai.tool`, as well as built-in provider-side tools.

## Declare function tools

Decorate an async function with `@ai.tool`:

```python
import ai


@ai.tool
async def contact_mothership(query: str) -> str:
    """Contact the mothership for important decisions."""
    return "Soon."
```

The tool name comes from the function name. The model receives the function
parameters as a JSON schema and the docstring as the tool description.

Function tools will only be automatically executed by the SDK when used
in the context of the agent. Provider-side tools will always get executed
by the provider, even when passed to `ai.stream`.

## Handle tool errors

Tool exceptions become `ToolCallResult` events with `is_error=True`. The model
sees the error text on the next turn:

```python
async with agent.run(model, messages) as stream:
    async for event in stream:
        if isinstance(event, ai.events.ToolCallResult):
            for result in event.results:
                if result.is_error:
                    print(f"{result.tool_name} failed: {result.result}")
```

The original exception is available on `event.exception` for logging.

## Declare tools that stream output

AI SDK for Python supports tools that return async iterables. Use a streaming tool
when it needs to return partial output, such as when wrapping a subagent.

Streaming tools use aggregators. An aggregator solves two problems: the tool can
yield many values over time, but the agent still needs one final tool result;
and your app may want a rich stored result while the model needs a simpler value
on the next turn.

The core interface is:

```python
class Aggregator[Item, Result, ModelInput]:
    def feed(self, item: Item) -> None: ...
    def snapshot(self) -> Result: ...

    @classmethod
    def to_model_input(cls, snapshot: Result) -> ModelInput: ...
```

Use `ai.StreamingTextTool` when yielded strings should concatenate into the
tool result:

```python
@ai.tool
async def draft_reply(topic: str) -> ai.StreamingTextTool:
    """Draft a reply."""
    yield "Checking "
    yield "records for "
    yield topic
```

Use `ai.StreamingStatusTool[T]` when intermediate yields are progress updates
and the last yielded value is the final result.

Use `ai.SubAgentTool` when a tool streams events from a nested agent. The stored
result is a message bundle, and the model sees the final assistant text.

The agent emits `PartialToolCallResult` events for those values, then sends
the aggregated result back to the model on the next turn.

Every tool must return an awaitable or an async iterable. Every async iterable
tool needs an aggregator, either through its return annotation or the
`aggregator=` argument.

## Understand tool anatomy

`ai.Tool` represents tools of all kinds in the SDK.

Each tool carries an optional `spec`, which is a description consumed by the
model, as well as a `tool_config` that contains tool-specific execution config
(e.g. retry policy).

`AgentTool` wraps `ai.Tool` together with its corresponding Python function,
which allows `agent.run` to find and call that function when the model requests
to do so.

Pass `ai.Tool` objects directly to `ai.stream` when you want the model to emit
tool calls but you do not want the SDK to execute them:

```python
tool = ai.Tool(
    kind="function",
    name="contact_mothership",
    spec=ai.tools.ToolSpec(
        description="Contact the mothership.",
        params={
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    ),
)

async with ai.stream(model, messages, tools=[tool]) as stream:
    async for event in stream:
        if isinstance(event, ai.events.ToolEnd):
            print(event.tool_call.tool_args)
```

## Use provider-executed tools

Provider-executed tools run on the provider side. Pass them to `ai.stream` in
the `tools` list:

```python
messages = [
    ai.user_message("Check the latest mothership telemetry reports."),
]

async with ai.stream(
    model,
    messages,
    tools=[ai.providers.anthropic.tools.web_search(max_uses=3)],
) as stream:
    async for event in stream:
        if isinstance(event, ai.events.TextDelta):
            print(event.chunk, end="", flush=True)
```

When you route through AI Gateway, you can use provider-specific tool factories
and AI Gateway tool factories:

```python
tools = [
    ai.providers.anthropic.tools.web_search(max_uses=3),
    ai.providers.ai_gateway.tools.perplexity_search(max_results=5),
]
```

## Use MCP tools

The Model Context Protocol (MCP) adapter converts server tools into agent tools:

```bash
uv add "ai[mcp]"
```

```python
tools = await ai.mcp.get_http_tools(
    "http://localhost:3000/mcp",
    headers={"Authorization": "Bearer your_access_token_here"},
)

agent = ai.Agent(tools=tools)
```

Use `ai.mcp.get_stdio_tools` for subprocess-based MCP servers.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)