By default the assistant answers from your help articles and the page a visitor is on. Tools let it go further: it can call functions you write, running in the visitor's browser, to answer from data that lives in your own systems.
The important part is where the code runs. Your handler executes in your page, with the visitor's own session. Kookee never sees your database, never needs an integration, and never holds a stale copy of your catalog. It only learns that a tool exists and what to pass it.
Registering a tool
window.KookeeChat.init({
apiKey: 'YOUR_API_KEY',
tools: [
{
name: 'search_products',
description: 'Search the shop catalog. Use for questions about what is for sale, prices and stock.',
parameters: {
type: 'object',
properties: { query: { type: 'string' }, maxPrice: { type: 'number' } },
required: ['query'],
},
label: 'Searching the catalog',
handler: async ({ query, maxPrice }) => {
const res = await fetch(`/api/products?q=${encodeURIComponent(query)}`);
return res.json();
},
},
],
});
In React it is the same array, passed as a prop:
<KookeeChatWidget clientOptions={{ apiKey }} tools={tools} />
Each tool takes:
name— starts with a lowercase letter, then lowercase letters, digits and underscores, 2–41 characters in all. Must not reuse one of the assistant's own tool names, such assearch_entriesorget_entry.description— how the assistant decides whether to use it. This is the field that matters most; see below.parameters— a JSON Schema object describing the arguments.handler— your function. Receives the arguments, returns anything JSON-serializable.label— optional, shown to the visitor while it runs ("Searching the catalog…").
Write the description for a reader, not a parser
The assistant decides whether to call your tool by reading its description. That is the whole mechanism, so vague descriptions produce a tool that is never used, or used at the wrong moment.
Say what the tool is for and when to reach for it:
Weak:
"Product search"Better:
"Search the shop catalog. Use for questions about what is for sale, prices, and stock. Returns live prices."
If a tool keeps firing when it should not, tighten the description rather than the schema.
What happens when it is called
The visitor asks something your tool can answer.
Kookee tells the widget which tool to run and with which arguments — the answer pauses here.
Your handler runs in the page.
The widget sends the result back and the answer resumes.
The visitor sees an activity line while it happens. Everything after step 2 is your code and your network; Kookee only receives what your handler returns.
Limits worth knowing
Ten tools per widget.
15 seconds per handler, then it times out.
4000 characters per result — return the fields that matter, not whole records. A handler that returns a hundred products will be truncated; return ten with a title, price and availability.
A handler that throws is not fatal: the error message is passed back so the assistant can explain the failure rather than the whole answer collapsing.
Things to be careful about
Side effects have no built-in confirmation. A tool that adds to a cart, changes an order or sends an email will simply run when the assistant decides to call it. If it is not read-only, ask the visitor inside your own handler:
handler: ({ orderId }) => {
if (!window.confirm(`Cancel order ${orderId}?`)) return 'The visitor declined.';
return cancelOrder(orderId);
}
Our own advice: register read tools freely, and gate every write tool behind a confirmation.
Do not throw internal errors. A thrown error's message is sent to Kookee and shown to the assistant. Throw something you would be happy for a visitor to read.
Do not pass secrets as arguments. Tool names and arguments are stored with the conversation for 90 days and are visible in the dashboard. An order id is fine; an API token is not.
Treat the result as untrusted on your side too. The assistant is told that tool results are data, never instructions, and citation markers are stripped from them so a result cannot fabricate a source. But your handler is the thing deciding what to fetch — validate its arguments as you would any user input, because ultimately a visitor's question is what shapes them.
Built-in tools
Two ship with the widget:
search_products— registered automatically on Shopify and WooCommerce, backed by the store's own public API. Disable withbuiltInTools: { catalog: false }.navigate— opens a page on your site after asking the visitor. Off by default; enable withbuiltInTools: { navigate: true }and passonNavigateon a single-page app so it routes client-side.
A tool you register with the same name as a built-in wins, so you can replace search_products with your own implementation.
Seeing what ran
Every tool call is recorded on the conversation. Open Site widget → Conversations in the dashboard and the transcript shows which tools ran and with what arguments, alongside the answer — see Read what visitors asked your assistant.
When not to use tools
If the answer lives in your documentation, write an article instead. Tools are for data that changes faster than you can publish it — prices, stock, order status, account state. Anything stable is cheaper, faster and more reliable as a help article.