Skip to content

Tool definitions

Your host-app definition carries a tool catalog: the operations inside your product that an agent may perform. Each tool is a declarative record. Tanzanite turns it into a function the model can call, invokes your HTTP endpoint when the model calls it, and maps the response back into something the agent can read.

You write no code for this. A tool is data, which is what makes it reviewable — a reviewer can see exactly what an app can do before any user is asked to consent to it.

{
"name": "place_object",
"description": "Place an object in the current scene at the given coordinates.",
"category": "write",
"dangerLevel": "moderate",
"requiresApproval": false,
"resultRenderer": "image",
"parameters": {
"type": "object",
"properties": {
"sceneId": { "type": "string", "description": "Scene to place into." },
"assetId": { "type": "string", "description": "Asset to place." },
"x": { "type": "number" },
"y": { "type": "number" },
"rotation": { "type": "number", "description": "Degrees, 0–360." }
},
"required": ["sceneId", "assetId", "x", "y"]
},
"endpoint": {
"method": "POST",
"path": "/scenes/{{args.sceneId}}/objects",
"headers": { "X-Acme-Client": "tanzanite" },
"queryParams": { "dryRun": "{{args.dryRun}}" },
"bodyTemplate": {
"asset": "{{args.assetId}}",
"position": { "x": "{{args.x}}", "y": "{{args.y}}" },
"rotation": "{{args.rotation}}"
}
},
"responseMapping": {
"objectId": "id",
"previewUrl": "preview.url"
}
}

The tool’s identifier. The agent sees it as integration_<name> — so place_object above is offered to the model as integration_place_object. The prefix is not decoration: it is what keeps a host app’s tool from colliding with, or being mistaken for, a platform tool.

Names are also how you refer to the tool in context pins, approvals and logs. Pick stable ones; renaming a tool is, to every agent that learned it, a deletion plus a creation.

What the model reads to decide whether to call this tool. It is prefixed with [<App name>] when presented, so the agent always knows whose tool it is.

This field does more work than any other. Write what the tool does, when to use it, when not to use it, and what the arguments mean in your domain. A tool with a vague description is called at the wrong moments, and no amount of prompt context elsewhere fixes it.

read | write | action

  • read — fetches information and changes nothing.
  • write — creates or modifies data in your product.
  • action — triggers something with an effect beyond stored data: a build, a send, a deploy.

The category is part of what your app is approved for, and it is re-checked on every catalog refresh. See the ceiling.

safe | moderate | destructive

Independent of category, because they answer different questions. category says what kind of operation it is; dangerLevel says how bad it is if the agent is wrong. A write that appends a comment is safe; a write that overwrites a scene is destructive.

Be honest here. Understating danger to avoid an approval prompt is the change most likely to be caught in review, and the one most likely to burn a user’s trust in your integration if it is not.

When true, the agent must get a human approval before the call executes. The session moves to waiting_human and emits a human_input_needed event carrying the tool name and the arguments it intends to use, which you answer with POST /v1/sessions/:id/human-input/:inputId.

Set it on anything destructive, anything that spends the user’s money outside Tanzanite, and anything a user would be startled to discover after the fact.

image | diff | link | table | json

A hint for how the first-party transcript should present this tool’s result. Pick the one that matches your responseMapping output: image for a URL to a picture, diff for a before/after, link for a deep link back into your app, table for rows, json for anything else.

This is a fixed enum, and it is the entire vocabulary. A host app may never hand the platform markup — not HTML, not a template, not a component. A render hint cannot become a script injected into another user’s transcript; a markup field always eventually can.

A JSON Schema object describing the tool’s arguments. type must be "object", with properties and a required array. Per-property you can use type, description, enum, items, nested properties and nested required.

Constrain aggressively. An enum of the four asset kinds you actually support is worth more than a string and a sentence asking the model nicely. Describe every property — the description is read at call time, and an undescribed parameter is a guessed parameter.

Where the call goes. path is appended to your definition’s baseUrl.

Field Purpose
method GET, POST, PUT, DELETE or PATCH.
path Appended to baseUrl. May interpolate arguments.
headers Extra headers for this tool. Auth is added by the platform.
queryParams Query string, values may interpolate arguments.
bodyTemplate Request body for POST, PUT and PATCH.

Argument interpolation uses {{args.<name>}} in path, queryParams and bodyTemplate. Anything pinned by the session’s context is merged into args and wins over what the model supplied, which is how you keep an id the model must not choose out of the model’s hands — see context is a pin.

Authentication is added by the platform, in the shape your definition declares, carrying the per-user credential from Connect. Do not put credentials in headers.

Maps fields out of your HTTP response into the result the agent sees. Keys are the names the agent gets; values are dotted paths into your response body.

{ "objectId": "id", "previewUrl": "preview.url", "warnings": "meta.warnings" }

Map only what the agent needs. Returning your whole response object costs tokens on every call and buries the two fields that matter — and a field you did not mean to expose to a model is a field you have exposed to a model.

For an endpoint that returns a job rather than a result. When present, Tanzanite calls the tool, then polls until the job reports done.

Field Purpose
statusUrl Where to poll.
statusField Dotted path to the status value in the poll response.
completeValue The value of statusField that means success.
failedValue The value that means failure.
resultMapping Like responseMapping, but applied to the final poll response.
pollIntervalMs Delay between polls.
maxPolls Give up after this many.

Pick pollIntervalMs and maxPolls so their product comfortably exceeds your job’s realistic worst case. A tool that gives up while the job is still running reports a failure for work that then completes, and the agent acts on the wrong belief.

A definition can carry a static catalog, a toolsUrl, or both. When toolsUrl is set, the platform re-fetches your catalog at the start of every session, so you can ship a new tool without touching your definition.

Three rules govern it:

It must be on the same origin as baseUrl. A catalog endpoint elsewhere would let the record that says what your app can do live somewhere other than the app itself.

A refreshed catalog is filtered against the ceiling you were approved for. See below — this is the important one.

A stale snapshot is used when the catalog is unreachable. Your endpoint being down degrades your integration to the last known-good catalog rather than breaking every session. This is a safety net, not a strategy: a catalog that stays unreachable stays frozen at whatever it was.

Your definition is approved for a maximum category and a maximum dangerLevel. When a refreshed catalog arrives, every tool in it is checked against that ceiling, and a tool that exceeds it is dropped, not accepted.

This is why a catalog refresh cannot be used to widen reach. An app reviewed and approved for read tools cannot become an app with destructive action tools by editing the JSON its own server returns — the new tools simply do not appear. Raising the ceiling is a change to the definition, which bumps its version and sends existing users through re-consent.

Plan for it: if a dropped tool is missing from your sessions, the ceiling is the first thing to check, and the fix is a definition change rather than a catalog change.

Your definition also carries a block of prompt context describing your product to the agent: what it is, what the tools operate on, the conventions the agent should follow. It is capped in length and is presented to the model as clearly-marked third-party content, so instructions in it cannot override the platform’s own rules or the user’s.

Write it as domain briefing, not as commands. “Scenes are addressed by id; assets are immutable once placed” is useful. “Always call place_object first” is not, and will not be obeyed the way you expect.

  • Every tool has a description that says when not to use it.
  • Every parameter has a description, and every closed set is an enum.
  • dangerLevel is honest, and requiresApproval is set on everything destructive.
  • responseMapping returns the few fields the agent needs and nothing else.
  • polling budgets exceed your realistic worst case.
  • Your toolsUrl is on the same origin as baseUrl and answers quickly.
  • Nothing in the catalog exceeds the ceiling you are asking to be approved for.