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

Search: {args.query}

{result?.results.map((item) => (
{item.title}
))}
); }, }, }; ``` 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} ); } ```