On this page
- Start with one clear responsibility
- Tool names should be boring and explicit
- Descriptions should explain when to use the tool
- Prefer structured fields over overloaded text
- Use required fields deliberately
- Enums are better than “be reasonable” strings
- Avoid giant enums when IDs can be retrieved first
- Prefer stable IDs over names for mutation
- Do not ask the model to repeat trusted identity
- Keep side effects obvious
- Two-phase tools can improve safety
- Schema validation is necessary but not sufficient
- Use additionalProperties: false where supported and appropriate
- Keep schemas as small as the operation allows
- Separate read tools from write tools
- Descriptions should not contain secrets
- Defaults belong in the executor when possible
- Use bounded strings and arrays
- Avoid polymorphic schemas unless they add real value
- Tool result shape matters too
- Include explicit error results
- Make approval previews from parsed arguments
- Tool descriptions are part of the prompt-injection surface
- Version tool schemas
- Test model behavior, not only JSON validation
- A schema review checklist
- Where BYOKchat fits
- Further reading
A tool schema is a miniature API designed for a probabilistic caller.
That makes it different from an ordinary internal function signature.
A model needs enough information to answer:
Should I use this tool?
Which tool should I choose?
What arguments are required?
What values are valid?
What should I not invent?
Your application then needs a schema strict enough to validate the model’s proposal before execution.
Start with one clear responsibility
Bad tool:
manage_workspace
Possible meanings:
create project
delete project
rename project
invite user
list files
archive project
That is too broad.
Prefer narrow actions:
create_project
rename_project
archive_project
list_project_files
Narrow tools improve model selection and make permission policy easier.
Tool names should be boring and explicit
Good names describe the operation:
get_weather
search_documents
create_calendar_event
send_email
Avoid vague names:
do_it
helper
process
manage
smart_action
The model sees the name repeatedly in its tool context. Clarity beats branding.
Descriptions should explain when to use the tool
A weak description:
Searches documents.
A stronger description:
Search the user's indexed project documents for passages relevant to a factual question. Use this before answering questions that depend on project files. Do not use it for public web search.
Useful descriptions include:
- purpose;
- scope;
- when it is appropriate;
- important exclusions.
Do not bury authorization policy in the description. The application must enforce that separately.
Prefer structured fields over overloaded text
Bad:
{
"query": "email alex@example.com subject hello body hi"
}
Better:
{
"to": ["alex@example.com"],
"subject": "Hello",
"body": "Hi"
}
Structured fields improve validation, previews, logging, and future migration.
Use required fields deliberately
If a field is necessary for execution, mark it required.
Do not rely on the model to infer that an omitted field is mandatory.
Example:
{
"type": "object",
"properties": {
"city": { "type": "string" },
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"],
"additionalProperties": false
}
If unit has a safe application default, it can remain optional.
Enums are better than “be reasonable” strings
If the application supports exactly three modes:
fast
balanced
accurate
encode them as an enum.
Do not ask the model for an arbitrary string and then maintain a fuzzy parser for:
quick
speedy
high quality
normal
Enums turn an ambiguous language problem into a deterministic validation problem.
Avoid giant enums when IDs can be retrieved first
A tool with 2,000 customer IDs embedded in its schema is inefficient and brittle.
Use a retrieval step:
search_customers(query)
→ customer IDs
get_customer(customer_id)
This keeps schemas small and lets the model ground later calls in real identifiers.
Prefer stable IDs over names for mutation
Names can be ambiguous:
Project Alpha
Alex
Budget
For destructive or state-changing tools, use stable IDs when possible:
{
"project_id": "proj_123"
}
The model can discover the ID using a read-only search/list tool first.
This reduces accidental operations on the wrong object.
Do not ask the model to repeat trusted identity
If the user is already authenticated to tenant tenant_42, avoid a tool schema like:
{
"tenant_id": "...",
"query": "..."
}
when the model is not supposed to choose the tenant.
Bind trusted context in application code:
authenticated tenant from session
+ model-provided query
This prevents the model from crossing authorization boundaries by inventing another ID.
Keep side effects obvious
A tool named:
preview_email
should not send email.
A tool named:
send_email
should have a permission policy appropriate for a real side effect.
Avoid tools whose effect depends on a hidden flag such as:
{
"dry_run": false
}
when a model could accidentally switch it.
Separate preview and commit actions if the risk justifies it.
Two-phase tools can improve safety
For high-impact actions:
prepare_transfer
→ returns preview + operation token
confirm_transfer
→ requires explicit confirmation token
The application can require human approval between phases.
This makes the irreversible boundary visible.
Schema validation is necessary but not sufficient
A valid schema can still request something unauthorized:
{
"account_id": "someone-else",
"amount": 1000
}
Validation layers are:
JSON parse
→ schema validation
→ semantic validation
→ authorization
→ side-effect policy
Do all of them.
Use additionalProperties: false where supported and appropriate
Unexpected arguments can signal hallucination or prompt injection.
Rejecting them makes behavior more predictable.
But JSON Schema support differs by provider/tool protocol, so verify the supported subset.
The application should still ignore/reject unknown fields locally even if the provider does not enforce strict schema generation.
Keep schemas as small as the operation allows
Every tool definition consumes model context.
Large schemas can:
- increase token usage;
- reduce prompt-cache stability;
- make tool selection harder;
- expose irrelevant implementation details.
If the model never needs a field, do not include it.
Separate read tools from write tools
A useful permission taxonomy:
read-only
reversible write
irreversible/high-impact write
Schema design can reinforce this by making operations separate tools.
This is easier to audit than one database_action tool with an operation enum containing select, update, and delete.
Descriptions should not contain secrets
Tool definitions can be sent to the model provider.
Do not put:
- API keys;
- private credentials;
- hidden database passwords;
- sensitive internal tokens
inside tool descriptions or example values.
Secrets should be resolved inside the executor.
Defaults belong in the executor when possible
Suppose limit defaults to 20.
You can document it:
limit: optional integer, defaults to 20, max 100
but the executor should enforce:
let limit = min(args.limit ?? 20, 100)
Do not trust the model to honor prose-only limits.
Use bounded strings and arrays
A schema/executor should constrain:
maximum query length
maximum recipient count
maximum result count
maximum file count
This protects downstream systems from oversized model-generated requests.
Even if schema keywords for limits are unsupported by a provider, enforce them locally.
Avoid polymorphic schemas unless they add real value
Complex oneOf/nested variants can be harder for models and providers to handle consistently.
Instead of:
one tool with 12 operation variants
multiple simpler tools are often easier to reason about.
Use polymorphism when the domain genuinely requires it, not to reduce the number of tool names.
Tool result shape matters too
The model also consumes the result.
Return structured, concise data where possible:
{
"customer_id": "cus_123",
"name": "Alex",
"status": "active"
}
Avoid dumping a 5 MB internal object containing irrelevant fields.
Tool-result minimization improves context efficiency and reduces accidental sensitive-data exposure.
Include explicit error results
Do not turn every tool failure into an unstructured exception that kills the model loop.
A recoverable tool result can be:
{
"ok": false,
"error": {
"code": "not_found",
"message": "No project matched that ID."
}
}
The model can then decide whether to ask the user or try a different read-only lookup.
Keep internal stack traces out of model-visible error messages.
Make approval previews from parsed arguments
A schema with explicit fields enables a useful human approval card:
Send email
To: alex@example.com
Subject: Release schedule
Body: 824 characters
That is much safer than displaying raw JSON or only the tool name.
See How to Build Human Approval Into AI Tool Calls.
Tool descriptions are part of the prompt-injection surface
Untrusted external content should never be concatenated into tool descriptions dynamically.
For example, do not build:
Tool description = "Use this API. Current page says: <untrusted webpage>"
Tool definitions should come from trusted application/server configuration.
Version tool schemas
If a durable conversation stores tool calls, schema evolution matters.
A tool can keep a stable external name and add a version internally, or expose versioned names when changes are incompatible.
Persist enough metadata to interpret old calls during backup/restore/debugging.
Test model behavior, not only JSON validation
A technically valid schema may still be confusing to models.
Evaluate:
Does the model choose the right tool?
Does it avoid the tool when unnecessary?
Does it fill required arguments correctly?
Does it ask the user when information is missing?
Does it confuse similar tools?
Use representative prompts and multiple supported models.
A schema review checklist
Before shipping a tool:
- one clear responsibility;
- explicit name;
- description explains when/when not to use;
- required fields truly required;
- enums for closed domains;
- stable IDs for mutations;
- no trusted identity delegated to model;
- bounded inputs;
- unknown fields rejected;
- side effect obvious;
- result minimized;
- error contract defined;
- approval preview possible;
- authorization implemented outside schema.
Where BYOKchat fits
A provider-neutral tool layer can keep one portable schema intent while adapters translate it to provider/MCP formats. The application then validates the returned arguments against the same trusted schema and applies per-tool permission policy before execution.