A tool represents one useful business operation. Start from a real request and response contract, then describe the intent that should reach it. Do not begin by copying every API endpoint into a large undifferentiated tool catalog.
The quick start connects your own API. This chapter develops its input schema. The example below names its search input q; you may keep query from the quick start instead, provided both the schema and request mapping use that chosen input name.
Separate interpretation from execution
Each tool has a public model-facing contract—ID, description, input schema—and a private execution definition—URL, auth, mapping, dependencies, behavior, response policy.
The compiler derives the model-facing schema. Top-level fields produced by dependencies or consumed references are removed from it, while remaining part of the execution schema. Inspect runtime.listTools() to see what the model actually receives; do not assume it sees the entire YAML.
Define inputs from concrete examples
This fragment belongs under an entry in tools, alongside its id and request:
tool:
title: Search services
description: >
Search the service catalog by name.
Use this to discover services, not to create a booking.
input_schema:
type: object
additionalProperties: false
properties:
q:
type: string
minLength: 1
description: The service name or words supplied by the user.
limit:
type: integer
minimum: 1
maximum: 20
default: 5
required: [q]
For “show hair coloring services,” q contains the user's search phrase. limit is optional; validation fills 5 if it is absent. The API request will not contain either field until you map them.
| Schema decision | Why it exists | Observable effect |
|---|---|---|
type: object | Tool arguments must be named fields | Other root shapes fail compilation. |
required: [q] | Search cannot run without a query | Missing q becomes needs_input. |
minLength: 1 | An empty query is not useful | Present-but-empty q fails validation. |
| Integer limit with bounds | Protect the API from unbounded requests | Floats, strings, zero, and values over 20 fail. |
default: 5 | Give omitted optional input a defined meaning | Omission supplies 5; it does not repair an invalid provided value. |
additionalProperties: false | Reject undeclared input | A fabricated extra field cannot quietly enter execution. |
The compiler defaults root additionalProperties to false if unspecified. Declare it explicitly on nested objects too when that is your intent. Use draft-07-compatible JSON Schema; AJV strict validation does not fetch remote schema references or coerce types.
Required does not mean the model must invent it
For booking, the execution schema can require date and slot_token. The user supplies the date, while a dependency produces the token. Declare both properties, then assign token ownership through depends_on.map.
The runtime asks for missing user-owned fields before running dependencies. After lookups, it validates the completed input again. Partial model output can therefore be legitimate; a missing date is a question to ask, not an excuse to hallucinate one.
Host context, constants, and generated values can be mapped directly without inviting the model to supply them. Authentication never belongs in an input schema.
Write descriptions that separate neighboring intents
Poor: “Manage appointments.”
Better: “List available appointment times for a date. Use for availability questions, not for creating a booking.”
A description should explain the action, relevant inputs, and the boundary with related tools. title is optional, up to 200 characters. Description is required, non-empty, and limited to 8,000 characters. Long prose is not inherently better routing context.
Tool IDs must be unique, start with a letter or underscore, contain letters, digits, underscores, or hyphens, and be no longer than 64 characters. Choose stable names such as find-slots and book; changing an ID also changes its references in dependencies and navigation.
Add retrieval hints after the contract works
# Tool-level fragment:
trigger_hints:
- available appointments
- free slots
- مواعيد متاحة
keywords:
availability: 3
appointment: 1.5
موعد: 2
Hints and positive keyword weights improve lexical retrieval. They do not authorize execution and do not force a match. A very broad word with a high weight can crowd out a better tool.
Test actual messages and inspect candidate selection before adding dozens of synonyms. Routing explains how lexical confidence affects model recall.
Keep secrets outside the model contract
A property such as access_token is rejected as credential-like. Pass a user token through the host request's auth, or configure an environment-backed API key under request.auth.
Similarly, declaring tenant_id as an unconstrained model input does not establish tenancy. Use trusted server context, and let the downstream API enforce permission.
Verify the intended boundary
Test these inputs independently: valid values, missing required input, unknown properties, invalid types, enum violations, and out-of-range numbers. Then inspect listTools() for hidden dependency fields. Finally test routing with positive examples, near-neighbor intents, and unrelated conversation.
A schema proves shape and constraints. It cannot prove that a customer owns an ID or that a slot is still available. That is why dependencies and API authorization remain necessary.