Custom Tools (React)
Tools let the agent call functions in the user's browser. When the agent decides to use a tool, the SDK executes it client-side and sends the result back to continue the conversation.
Defining tools
Use createToolHelper for type-safe tool definitions with Zod schema inference:
import { createToolHelper } from '@kapaai/agent-react';
import { z } from 'zod';
type MyContext = { apiClient: ApiClient };
const tool = createToolHelper<MyContext>();
const myTools = [
tool({
name: 'search_users',
description: 'Search for users by name or email',
parameters: z.object({
query: z.string().describe('Search query'),
limit: z.number().optional().describe('Max results'),
}),
displayName: 'Search Users',
execute: async ({ query, limit }, ctx) => {
const results = await ctx.apiClient.searchUsers(query, limit);
return results;
},
}),
];
The execute function receives:
- args — parsed and typed from the Zod schema (e.g.
{ query: string, limit?: number }) - context — the
contextobject you passed toAgentProvider
Tool options
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique tool name (sent to the LLM). |
description | string | Yes | Description of what the tool does (sent to the LLM). |
parameters | ZodType | object | Yes | Zod schema or raw JSON Schema for the tool's parameters. |
execute | (args, context) => Promise<unknown> | Yes | Function to execute when the tool is called. |
displayName | string | No | Human-readable name shown in the UI. Defaults to name. |
needsApproval | boolean | No | If true, the user must approve before execution. |
icon | ComponentType | No | Icon component shown in the tool card. |
iconColor | string | No | Color for the icon. |
render | (props) => ReactNode | No | Custom rendering for the tool card. See Custom rendering. |
The icon component should accept { size?: number | string; color?: string; stroke?: number | string } props. Any icon library that follows this pattern works (e.g. Tabler Icons, Lucide, Heroicons).
Approval flow
Set needsApproval: true to require user confirmation before a tool executes:
tool({
name: 'delete_record',
description: 'Delete a database record',
parameters: z.object({ id: z.string() }),
needsApproval: true,
execute: async ({ id }, ctx) => ctx.db.delete(id),
}),
When the agent calls this tool, the SDK shows the tool card in an expanded state with Allow and Deny buttons. The tool only executes after the user clicks Allow.
Custom rendering
The render prop lets you replace the default tool card with custom JSX:
tool({
name: 'get_weather',
description: 'Get weather for a city',
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => fetchWeather(city),
render: ({ status, args, result }) => {
if (status === 'executing') return <div>Checking weather for {args.city}...</div>;
if (status === 'completed') {
const data = result as WeatherData;
return <WeatherCard data={data} />;
}
return null; // Fall back to default ToolCallCard
},
}),
Render props
| Field | Type | Description |
|---|---|---|
status | ToolCallStatus | Current tool execution status. See ToolCallStatus. |
args | Inferred from Zod schema | The arguments the agent passed to the tool. |
result | unknown | The return value of execute (only when status === 'completed'). |
error | string | Error message (only when status === 'error'). |
onApprove | () => void | Call to approve the tool (only when status === 'approval_requested'). |
onReject | () => void | Call to reject the tool (only when status === 'approval_requested'). |
Fallback behavior
If your render function returns null for a given status, the SDK falls back to the default ToolCallCard. This means you can customize only the states you care about:
render: ({ status, result }) => {
// Only customize the completed state
if (status === 'completed') return <MyCustomResult data={result} />;
// Everything else (approval, executing, error) uses the default card
return null;
},
This is especially useful with needsApproval — you can let the SDK handle the approval UI and only customize the result display.
Context
The context object is shared across all tools and can hold anything — API clients, auth tokens, user info:
const context = useMemo(
() => ({
apiClient: new ApiClient(authToken),
userId: currentUser.id,
}),
[authToken, currentUser.id],
);
<AgentProvider tools={myTools} context={context} {...otherProps}>
Tools access it as the second argument to execute:
execute: async (args, ctx) => {
return ctx.apiClient.query(args.sql);
},
Without Zod
If you don't want to use Zod, pass a raw JSON Schema object as parameters:
tool({
name: 'search',
description: 'Search for items',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
},
required: ['query'],
},
execute: async (args) => {
return searchItems(args.query);
},
}),
Without Zod, the execute and render args are typed as Record<string, unknown> — you'll need to cast or validate manually.
Built-in tools
The agent has one built-in tool: search_knowledge_base. This tool searches your project's knowledge base — the same sources you've configured in the Kapa dashboard (documentation, GitHub repos, API references, etc.). It runs entirely server-side using Kapa's retrieval pipeline, so you don't define it as a client tool and can't customize its execution.
What you can customize is how the tool appears in the chat UI. Use builtinToolMeta to set a display name and optional icon:
<AgentProvider
builtinToolMeta={{
search_knowledge_base: {
displayName: 'Search Knowledge Base',
icon: IconSearch, // optional — any component with { size?, color?, stroke? } props
iconColor: '#38bdf8', // optional
},
}}
{...otherProps}
>
Without builtinToolMeta, the tool card will show the raw name search_knowledge_base in the chat. With it, users see "Search Knowledge Base" instead.