Tools
A tool is a named action the AI can call, with typed inputs — e.g.send_email(to, subject, body). Tools are declared in a skill’s tools.ts and, for tools your app serves, backed by an HTTP route in one of your components. This page is the source of truth for the tool contract; Skills covers where tools.ts lives.
Declaring a tool
Each entry intools.ts has a provider-safe name, a friendly displayName, a description, a Zod input schema, and a target that says what Kazzle invokes.
Targets
target is the reusable address for a tool or pending-input button. A direct AI tool call and a button click can use the same target object, so both run the same code.
HTTP targets use the same request fields:
body: '${input}'.
References
Kazzle resolves references in HTTP targetquery, headers, body, and url fields before dispatch:
${input}— the whole tool input, or the whole button input.${input.path}— a nested value from the input.${env.NAME}— a named environment variable from the owning app component’s declaredenv.collection+env.environment.
$${input.name} when you need literal ${input.name} text.
Handler request
For anapp target, add the matching route in the target component. With body: '${input}', Kazzle sends the typed input as the JSON body:
app targets also receive an Authorization: Bearer <identity token> header identifying the user + install. App targets cannot set Authorization through headers; Kazzle owns that header.
A tool declared with no matching route does nothing useful — add both together. tools.json is not supported; the app compiler fails if it finds one.
Handler response
Return plain text, or JSON with up to three channels:content— the plain result the AI reads (fed to the model). Required.markdown— optional. A short rich-text summary rendered in the tool card (react-markdown; raw HTML is escaped). Good for a sentence, a small list, an inline link. A bare relative path renders as dead preformatted text — links must be absolute.embedUrl— optional. An absolute URL your app serves; the card renders it in a sandboxed iframe. This is how you show a real, full-width UI — a connect screen, a dashboard, a chart. Your app hosts and owns the page, so it can be fully interactive against your own backend, cookies, and OAuth. Nothing is written to the drive.
embedUrl wins over markdown when both are set. Always keep a meaningful content — that’s what the AI reads.
Pending input
An app tool can pause the thread when it needs the user to act. Returntype: 'pending_input' with a card title and buttons:
/chat/resume after the user clicks a button. A button without target submits its input as the tool result. A button with a target runs that target first, then uses the target result as the tool result.
Rich UI — embed an app-hosted page
When a tool’s result is visual or interactive (a connect screen, a chart, a summary dashboard), serve the page from one of your components and return its absolute URL asembedUrl. Build the URL from the injected component URL — never a relative path.
embedUrl over emitting HTML strings: your app already serves pages, the UI stays interactive and versioned with your code, and no per-call artifacts are written anywhere.