Guides
Tools
Give agents typed functions with defineAgentTool, and hold risky calls for approval.
A model can only describe what to do; a tool lets it act. Tools are typed functions that an agent calls with validated arguments, running on your server with your database and secrets.
Writing a Tool
Each file in server/tools default-exports one tool. defineAgentTool is auto-imported in Nuxt;
in Void, import it from @nuxtagentic-pro/server and pass the tools to defineVoidAgentic. This
tool from the starter calculates monthly recurring revenue:
import { and, count, eq } from 'drizzle-orm'
import { z } from 'zod'
import { customers } from '../models/customers'
const monthlyPrice = { free: 0, pro: 49 }
export default defineAgentTool({
name: 'calculate_mrr',
description: 'Calculate monthly recurring revenue from customer plans in the current workspace',
input: z.object({}),
async handler(_input, { actor, database }) {
const rows = await database
.select({ plan: customers.plan, customers: count() })
.from(customers)
.where(and(eq(customers.workspace, actor.workspace), eq(customers.ownerId, actor.user.id)))
.groupBy(customers.plan)
const mrr = rows.reduce((total, row) => total + monthlyPrice[row.plan] * row.customers, 0)
return { currency: 'EUR', mrr, plans: rows }
},
})
| Option | Description |
|---|---|
name | What the model calls: letters, digits, _, and - |
description | Tells the model when to use the tool |
input | A Zod object; arguments are validated before the handler runs |
approval | true holds every call for a person's approval |
readOnly | Marks tools without side effects for MCP clients; defaults to !approval |
handler | (input, { actor, database, env, signal }) |
The handler context:
actor: the signed-in user, workspace, and role the agent acts for. Always filter by it, as the example does, because thedatabaseis not scoped for you.database: the Drizzle D1 database.env: the Worker bindings and secrets.signal: aborts when the run is cancelled.
A thrown error goes back to the model, which can retry or explain.
Pausing Risky Calls for Approval
Some calls should never run without a person's say, such as refunds or emails to customers. Set
approval: true:
import { z } from 'zod'
export default defineAgentTool({
name: 'issue_refund',
description: 'Refund an order. A person approves every refund before it runs.',
input: z.object({
orderNumber: z.string().min(1),
amount: z.number().positive(),
reason: z.string().min(1),
}),
approval: true,
handler: ({ orderNumber, amount }) => ({
refundId: `rf_${orderNumber}_${Math.round(amount * 100)}`,
status: 'refunded',
}),
})
When the agent calls issue_refund, the run pauses in waiting and the call appears in the
approvals inbox with its exact arguments. Approving runs the handler once and resumes the run;
rejecting ends the call without running it. A replayed call returns the stored result and never
runs the write twice.
Tools You Get without Writing Code
web_fetch: fetches a public web page as text. Private and local addresses are refused.- Model tools such as
customer_listandcustomer_update, from Agentic Models. - Integration actions such as
resend_send_email, once a workspace connects and grants them.
Reusing Tools from the Free Module
Tools written with the free module's defineAgentTool keep working under Pro. Their handlers
also receive actor, database, and env next to signal.