# Tools
URL: /docs/guides/tools
Give your assistant actions like API calls, database queries, and more.
Tools enable LLMs to take actions and interact with external systems. assistant-ui provides a comprehensive toolkit for creating, managing, and visualizing tool interactions in real-time.
Overview \[#overview]
Tools in assistant-ui are functions that the LLM can call to perform specific tasks. They bridge the gap between the LLM's reasoning capabilities and real-world actions like:
* Fetching data from APIs
* Performing calculations
* Interacting with databases
* Controlling UI elements
* Executing workflows
When tools are executed, you can display custom generative UI components that provide rich, interactive visualizations of the tool's execution and results. Learn more in the [Generative UI guide](/docs/guides/tool-ui).
If you haven't provided a custom UI for a tool, assistant-ui offers a
[`ToolFallback`](/docs/ui/tool-fallback) component that you can add to your
codebase to render a default UI for tool executions. You can customize this by
creating your own Tool UI component for the tool's name.
Recommended: Tools() API \[#recommended-tools-api]
The `Tools()` API is the recommended way to register tools in assistant-ui. It provides centralized tool registration that prevents duplicate registrations and works seamlessly with all runtimes.
Quick Start \[#quick-start]
Create a toolkit object containing all your tools, then register it using `useAui()`:
```tsx
import { useAui, Tools, type Toolkit } from "@assistant-ui/react";
import { z } from "zod";
// Define your toolkit
const myToolkit: Toolkit = {
getWeather: {
description: "Get current weather for a location",
parameters: z.object({
location: z.string().describe("City name or zip code"),
unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
execute: async ({ location, unit }) => {
const weather = await fetchWeatherAPI(location, unit);
return weather;
},
render: ({ args, result }) => {
if (!result) return
Fetching weather for {args.location}...
;
return (
{args.location}
{result.temperature}° {args.unit}
{result.conditions}
);
},
},
// Add more tools here
};
// Register tools in your runtime provider
function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
const runtime = useChatRuntime();
// Register all tools
const aui = useAui({
tools: Tools({ toolkit: myToolkit }),
});
return (
{children}
);
}
```
Benefits \[#benefits]
* **No Duplicate Registrations**: Tools are registered once, preventing the "tool already exists" error
* **Centralized Definition**: All your tools in one place, easier to manage and test
* **Type-Safe**: Full TypeScript support with proper type inference
* **Flexible**: Works with all runtimes (AI SDK, LangGraph, custom, etc.)
* **Composable**: Easily split toolkits across files and merge them
Tool Definition \[#tool-definition]
Each tool in the toolkit is a `ToolDefinition` object with these properties:
```tsx
type ToolDefinition = {
description: string;
parameters: z.ZodType; // Zod schema for parameters
execute: (args, context) => Promise;
render?: (props) => React.ReactNode; // Optional UI component
};
```
Organizing Large Toolkits \[#organizing-large-toolkits]
For larger applications, split tools across multiple files:
```tsx
// lib/tools/weather.tsx
export const weatherTools: Toolkit = {
getWeather: { /* ... */ },
getWeatherForecast: { /* ... */ },
};
// lib/tools/database.tsx
export const databaseTools: Toolkit = {
queryData: { /* ... */ },
insertData: { /* ... */ },
};
// lib/toolkit.tsx
import { weatherTools } from "./tools/weather";
import { databaseTools } from "./tools/database";
export const appToolkit: Toolkit = {
...weatherTools,
...databaseTools,
};
// App.tsx
import { appToolkit } from "./lib/toolkit";
function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
const runtime = useChatRuntime();
const aui = useAui({
tools: Tools({ toolkit: appToolkit }),
});
return (
{children}
);
}
```
UI-Only Tools \[#ui-only-tools]
For tools where execution happens elsewhere (e.g., backend MCP tools), omit the `execute` function:
```tsx
const uiOnlyToolkit: Toolkit = {
webSearch: {
description: "Search the web",
parameters: z.object({
query: z.string(),
}),
// No execute - handled by backend
render: ({ args, result }) => {
return (
);
},
},
};
```
Tool Execution Context \[#tool-execution-context]
Tools receive additional context during execution:
```tsx
execute: async (args, context) => {
// context.abortSignal - AbortSignal for cancellation
// context.toolCallId - Unique identifier for this invocation
// context.human - Function to request human input
// Example: Respect cancellation
const response = await fetch(url, { signal: context.abortSignal });
// Example: Request user confirmation
const userResponse = await context.human({
message: "Are you sure?",
});
};
```
Human-in-the-Loop \[#human-in-the-loop]
Tools can pause execution to request user input or approval:
```tsx
const confirmationToolkit: Toolkit = {
sendEmail: {
description: "Send an email with confirmation",
parameters: z.object({
to: z.string(),
subject: z.string(),
body: z.string(),
}),
execute: async ({ to, subject, body }, { human }) => {
// Request user confirmation before sending
const confirmed = await human({
type: "confirmation",
action: "send-email",
details: { to, subject },
});
if (!confirmed) {
return { status: "cancelled" };
}
await sendEmail({ to, subject, body });
return { status: "sent" };
},
render: ({ args, result, interrupt, resume }) => {
// Show confirmation dialog when waiting for user input
if (interrupt) {
return (
Confirm Email
Send to: {interrupt.payload.details.to}
Subject: {interrupt.payload.details.subject}
);
}
// Show result
if (result) {
return
Status: {result.status}
;
}
return
Preparing email...
;
},
},
};
```
Alternative Methods (Legacy) \[#alternative-methods-legacy]
The following methods are supported for backwards compatibility but are not
recommended for new code. They can cause duplicate registration errors and are
harder to maintain. Use the `Tools()` API instead.
Using makeAssistantTool (Deprecated) \[#using-makeassistanttool-deprecated]
Register tools with the assistant context. Returns a React component that registers the tool when rendered:
```tsx
import { makeAssistantTool, tool } from "@assistant-ui/react";
import { z } from "zod";
const weatherTool = tool({
description: "Get current weather for a location",
parameters: z.object({
location: z.string(),
}),
execute: async ({ location }) => {
const weather = await fetchWeatherAPI(location);
return weather;
},
});
const WeatherTool = makeAssistantTool({
...weatherTool,
toolName: "getWeather",
});
// Place inside AssistantRuntimeProvider
function App() {
return (
);
}
```
**Why this is deprecated**: Component-based registration can lead to duplicate registrations if components are remounted or if the same tool is defined in multiple places.
Using useAssistantTool Hook (Deprecated) \[#using-useassistanttool-hook-deprecated]
Register tools dynamically using React hooks:
```tsx
import { useAssistantTool } from "@assistant-ui/react";
import { z } from "zod";
function DynamicTools() {
useAssistantTool({
toolName: "searchData",
description: "Search through the data",
parameters: z.object({
query: z.string(),
}),
execute: async ({ query }) => {
return await searchDatabase(query);
},
});
return null;
}
```
**Why this is deprecated**: Hook-based registration ties tool definitions to component lifecycle, making them harder to test and potentially causing duplicate registrations.
Using makeAssistantToolUI (Deprecated) \[#using-makeassistanttoolui-deprecated]
Create UI-only components for tools defined elsewhere:
```tsx
import { makeAssistantToolUI } from "@assistant-ui/react";
const SearchResultsUI = makeAssistantToolUI<
{ query: string },
{ results: Array }
>({
toolName: "webSearch",
render: ({ args, result }) => {
return (
Search: {args.query}
{result.results.map((item) => (
{item.title}
))}
);
},
});
function App() {
return (
);
}
```
**Why this is deprecated**: Component-based UI registration can cause issues with tool UI not appearing or appearing multiple times.
Tool Paradigms \[#tool-paradigms]
Frontend Tools \[#frontend-tools]
Tools that execute in the browser:
```tsx
const frontendToolkit: Toolkit = {
screenshot: {
description: "Capture a screenshot of the current page",
parameters: z.object({
selector: z.string().optional(),
}),
execute: async ({ selector }) => {
const element = selector ? document.querySelector(selector) : document.body;
const screenshot = await captureElement(element);
return { dataUrl: screenshot };
},
},
};
```
Backend Tools \[#backend-tools]
Tools executed server-side:
```tsx
// Backend route (AI SDK)
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(messages),
tools: {
queryDatabase: {
description: "Query the application database",
inputSchema: zodSchema(
z.object({
query: z.string(),
table: z.string(),
}),
),
execute: async ({ query, table }) => {
const results = await db.query(query, { table });
return results;
},
},
},
});
return result.toUIMessageStreamResponse();
}
```
Client-Defined Tools with frontendTools \[#client-defined-tools-with-frontendtools]
The Vercel AI SDK adapter implements automatic serialization of client-defined tools. Tools registered via the `Tools()` API are automatically included in API requests:
```tsx
// Frontend: Define tools with Tools() API
const clientToolkit: Toolkit = {
calculate: {
description: "Perform calculations",
parameters: z.object({
expression: z.string(),
}),
execute: async ({ expression }) => {
return eval(expression); // Use proper parser in production
},
},
};
function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
const runtime = useChatRuntime();
const aui = useAui({
tools: Tools({ toolkit: clientToolkit }),
});
return (
{children}
);
}
// Backend: Use frontendTools to receive client tools
import { frontendTools } from "@assistant-ui/react-ai-sdk";
export async function POST(req: Request) {
const { messages, tools } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(messages),
tools: {
...frontendTools(tools), // Client-defined tools
// Additional server-side tools
queryDatabase: {
description: "Query the database",
inputSchema: zodSchema(z.object({ query: z.string() })),
execute: async ({ query }) => {
return await db.query(query);
},
},
},
});
return result.toUIMessageStreamResponse();
}
```
MCP (Model Context Protocol) Tools \[#mcp-model-context-protocol-tools]
Integration with MCP servers using AI SDK's experimental MCP support:
```tsx
import { experimental_createMCPClient, streamText } from "ai";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
export async function POST(req: Request) {
const client = await experimental_createMCPClient({
transport: new StdioClientTransport({
command: "npx",
args: ["@modelcontextprotocol/server-github"],
}),
});
try {
const tools = await client.tools();
const result = streamText({
model: openai("gpt-4o"),
tools,
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
} finally {
await client.close();
}
}
```
Best Practices \[#best-practices]
1. **Use Tools() API**: Always prefer the `Tools()` API over legacy component/hook-based registration
2. **Centralize Definitions**: Keep all tools in a toolkit file for easy management
3. **Clear Descriptions**: Write descriptive tool descriptions that help the LLM understand when to use each tool
4. **Parameter Validation**: Use Zod schemas to ensure type safety
5. **Error Handling**: Handle errors gracefully with user-friendly messages
6. **Loading States**: Provide visual feedback during tool execution
7. **Security**: Validate permissions and sanitize inputs
8. **Performance**: Use abort signals for cancellable operations
9. **Testing**: Test tools in isolation and with the full assistant flow
Migration from Legacy APIs \[#migration-from-legacy-apis]
To migrate from legacy APIs to the `Tools()` API:
1. **Create a toolkit object** with all your tools
2. **Move tool definitions** from `makeAssistantTool`/`useAssistantTool` calls into the toolkit
3. **Register once** using `useAui({ tools: Tools({ toolkit }) })` in your runtime provider
4. **Remove component registrations** (``, etc.)
5. **Test** to ensure all tools work as expected
Example migration:
```tsx
// Before (Legacy)
const WeatherTool = makeAssistantTool({
toolName: "getWeather",
description: "Get weather",
parameters: z.object({ location: z.string() }),
execute: async ({ location }) => { /* ... */ },
});
function App() {
return (
);
}
// After (Recommended)
const toolkit: Toolkit = {
getWeather: {
description: "Get weather",
parameters: z.object({ location: z.string() }),
execute: async ({ location }) => { /* ... */ },
},
};
function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
const runtime = useChatRuntime();
const aui = useAui({
tools: Tools({ toolkit }),
});
return (
{children}
);
}
```