Wire assistant-ui toolkits into your server with the AI SDK — generativeTools, frontendTools, mixing client and server tools, and multi-modal results.
A tool is only callable if the model knows about it, and the model is configured on your server. This page covers wiring a toolkit into an AI SDK route handler: exposing your generative toolkit, receiving client-defined tools, mixing the two, and round-tripping multi-modal results.
For authoring tools, see Defining Tools. For MCP servers, see MCP.
The request body
@assistant-ui/react-ai-sdk posts { messages, system, tools } to your route. tools is the map of frontend tools the client serialized for this request (the model needs their schemas to call them, even though they run in the browser):
const {
messages,
system,
tools,
}: {
messages: UIMessage[];
system?: string;
tools?: Record<string, { description?: string; parameters: JSONSchema7 }>;
} = await req.json();Generative toolkits: generativeTools
When you author tools in a "use generative" file, the same import resolves to the server build inside a route handler — schema plus any backend execute, with renderers stripped. Pass it to generativeTools together with the uploaded tools:
import { generativeTools } from "@assistant-ui/react-ai-sdk";
import { streamText, convertToModelMessages, type UIMessage } from "ai";
import { openai } from "@ai-sdk/openai";
import toolkit from "../../toolkit";
export async function POST(req: Request) {
const { messages, system, tools } = await req.json();
const result = streamText({
model: openai("gpt-5.4-nano"),
system,
messages: await convertToModelMessages(messages),
tools: generativeTools({ toolkit, frontendTools: tools }),
});
return result.toUIMessageStreamResponse();
}generativeTools registers every toolkit tool with the model using its schema, wires the backend execute where the server build carries one, and merges in the uploaded frontendTools. A server execute wins over an uploaded entry of the same name. Frontend and human tools (no server execute) are exposed schema-only and left for the client and the user to fulfill.
If your toolkit spreads in MCP server tools (defineMcpToolkit), use
new AISDKToolkit({ toolkit }).tools({ frontend }) instead of
generativeTools — it also opens the MCP connections. See MCP.
Client-defined tools: frontendTools
If a toolkit cannot go through the generative compiler, the AI SDK adapter still
serializes browser-executed tools into the request tools. Convert them to the
AI SDK shape with frontendTools and spread your own server tools alongside:
import { frontendTools } from "@assistant-ui/react-ai-sdk";
import { streamText, convertToModelMessages, tool, zodSchema } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
export async function POST(req: Request) {
const { messages, tools } = await req.json();
const result = streamText({
model: openai("gpt-5.4-nano"),
messages: await convertToModelMessages(messages),
tools: {
...frontendTools(tools ?? {}),
query_database: tool({
description: "Query the application database.",
inputSchema: zodSchema(z.object({ query: z.string() })),
execute: async ({ query }) => db.query(query),
}),
},
});
return result.toUIMessageStreamResponse();
}generativeTools calls frontendTools for you under the hood; reach for
frontendTools directly when you're not on the generative build.
toToolsJSONSchema emits the uploaded tools in alphabetical order, so two
renders that register the same set produce byte-identical request bodies —
which keeps provider prompt caches stable across renders.
Multi-modal results
When a tool declares toModelOutput, frontend tool results round-trip through the AI SDK chat protocol back to your route on the next turn. For toModelOutput to fire on those round-tripped results, pass the tool registry to convertToModelMessages as well — the same pattern the AI SDK documents for any multi-modal tool response:
const aiSDKTools = { ...frontendTools(tools ?? {}) };
const result = streamText({
model,
// Pass tools to BOTH calls so prior results are projected via toModelOutput.
messages: await convertToModelMessages(messages, { tools: aiSDKTools }),
tools: aiSDKTools,
});Skip the { tools } argument and prior results are sent as a plain JSON blob — toModelOutput is silently ignored. Tools without toModelOutput are unaffected either way.
Read/write compatibility for persisted threads. When toModelOutput is
set, the runtime persists the AI SDK output as
{ __aui_modelContent, value }. Upgrade every reader before any writer starts
producing toModelOutput; older readers treat the whole envelope as the
result and break that tool's render. Don't return objects whose top-level
key is literally __aui_modelContent from any execute.
Multi-step tool calls
To let the model see a frontend tool's result and continue, configure the runtime to send back automatically when the last assistant message is complete with tool calls:
import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
const runtime = useChatRuntime({
api: "/api/chat",
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
});For the full AI SDK v6 backend setup — history persistence, reasoning, server-side approvals — see the AI SDK v6 guide.