Contents
Figure 1: Writing Tool Descriptions and Schemas That Help LLMs Choose — the model reads the label, not the implementation
Your model never sees your code. It sees a name, a description and a schema, and it decides from those alone. Anthropic's tool-definition docs put the stakes plainly: detailed descriptions are "by far the most important factor in tool performance" [2]. So the text you write is the product.
This guide pulls together Anthropic's and OpenAI's published advice, then tests it by running a linter on three tool sets, including the real tools from my earlier MCP demo server. The linter does not call a model, so it flags definitions that give a model less to go on — and it cannot predict what a model will do.
Start With the Description
A description has one job: let the model decide whether this tool fits the request. Anthropic's docs ask for four things [2]:
- What the tool does.
- When to use it — "and when it shouldn't"
[2]. - What each parameter means.
- Any caveats.
They suggest at least three or four sentences per tool, more for complex ones [2]. Anthropic's engineering post adds a mental test: describe the tool "to a new hire on your team" [1]. OpenAI's guide has the same idea, which it calls the intern test: could a new person use the function with only what the model sees [3]?
Compare these two definitions:
{"name": "search", "description": "Search.", "inputSchema": {"properties": {"q": {"type": "string"}}}}
{"name": "support_search_tickets",
"description": "Search customer support tickets by keyword and status. Use when the user asks to find, list or look up tickets or complaints. Returns up to `limit` tickets with id, subject and status. Does not search the knowledge base and does not change any ticket."}
The second one says what it covers, when to reach for it, what comes back, and what it will not do. That last part matters most when you have a sibling tool, such as a knowledge-base search.
Name Things So They Cannot Be Confused
- Use specific names.
searchsays nothing about the domain.support_search_ticketsdoes. - Namespace related tools. Anthropic suggests shared prefixes like
asana_searchandjira_search[2]. Its post adds that prefix versus suffix naming had "non-trivial effects" in evaluations, and the effects vary by model, so test your scheme[1]. - Make parameter names unambiguous. The post's example: instead of
user, tryuser_id[1]. - Consolidate. "More tools don't always lead to better outcomes"
[1]. The post recommends aschedule_eventtool that finds availability and books, in place of separate list and create tools. - Keep the list short. OpenAI suggests fewer than 20 functions at the start of a turn and calls that a soft suggestion
[3].
Let the Schema Do the Policing
Description tells the model what to do. The schema stops it from doing the wrong thing. OpenAI's guide says to "use enums and object structure to prevent invalid states," with toggle_light(on: bool, off: bool) as the cautionary example, since it allows both to be true [3]. Use these:
| Keyword | Use it for |
|---|---|
enum |
Any fixed set of choices |
minimum / maximum |
Numbers with a known range |
pattern, minLength, maxLength |
Strings with a known format |
required |
What the call cannot work without |
additionalProperties: false |
Rejecting unknown keys |
OpenAI's examples set additionalProperties: false and list every property as required, but its page does not state either as a rule, so do not treat them as one without checking your provider [3].
Two more principles from the guides:
- Do not ask the model for values your code already knows. If you have the session id, pass it in code
[3]. - Combine functions that are always called together
[3].
What My Linter Found
I wrote a linter that encodes these rules. The thresholds are my choices, not standards. I ran it on three sets:
| Tool set | Tools | Warnings | Per tool | What it caught |
|---|---|---|---|---|
| Deliberately weak set | 8 | 50 | ~6.2 | One-sentence descriptions, no "when to use", no "what it will not do", 13 undocumented parameters, 7 ambiguous names like q and data, an api_key the model should not fill in, an on/off boolean pair |
| Rewritten support tools | 6 | 1 | ~0.2 | A false alarm, explained below |
| Real tools from my MCP demo server | 6 | 25 | ~4.2 | Every tool: one sentence, no "when to use", no "what it will not do", one undocumented parameter; one parameter named name |
That third result humbled me. I wrote those tools and they work, but a model would have little to go on if I added a dozen more like them. Running a linter on your own tools is the fastest way to find out how terse you have been.
Where the Linter Is Wrong
The overlap check compares word usage between descriptions. It is weak in both directions:
- On the weak set, it missed the
search_filesandsearch_docspair, because one-line descriptions share too few words. - On the rewritten set, it flagged
support_get_ticketagainstsupport_update_ticket_statusat 0.53 similarity. These are a read tool and a write tool that share words like "ticket" and "status". That is the false alarm. - When I made two near-identical search tools with detailed descriptions, it did flag them — 0.55 against my 0.4 threshold.
So treat overlap flags as "look at these two," not as verdicts. Two tools can share vocabulary and differ in function.
Schemas That Reject Bad Calls
I also tested whether constraints actually do their job, using a battery of seven invalid calls and the jsonschema library:
- A status outside the allowed list.
- An extra unknown argument.
- A missing required argument.
- A
limitof 500 against a 1–50 range. - A malformed ticket id.
- An empty reply body.
- A number where text belongs.
The loose schemas let through 6 of the 7. The strict schemas rejected all 7, and four valid examples still passed. Read this carefully: I built the loose schemas to be loose, so the numbers show what constraints do, not how common loose schemas are in the wild. The point stands, though: a schema that rejects a call returns an error the model can read and retry; a loose one lets the bad value reach your code.
Returns and Errors Count Too
Anthropic's engineering post says the rest of the tool matters as much as the definition [1]:
- Return meaningful context. Prefer readable names to raw identifiers like UUIDs. The post says resolving them "significantly improves Claude's precision in retrieval tasks by reducing hallucinations"
[1]. - Offer a
response_formatenum (concise or detailed). The post's example responses used 72 and 206 tokens[1]. - Paginate and truncate sensibly. The post notes Claude Code limits tool responses to 25,000 tokens by default
[1]. - Write errors that tell the model what to try next, not opaque codes or tracebacks.
Test With Real Tasks
Small wording changes can matter. The post says "even small refinements to tool descriptions can yield dramatic improvements," and gives an example: Claude kept appending "2025" to a search query, which biased results, and a description fix corrected it [1].
The post's method [1]:
- Write realistic, multi-step tasks with verifiable outcomes.
- Run them programmatically.
- Track accuracy, tool-call counts and errors.
- Read the transcripts.
OpenAI's guide similarly says to evaluate performance with different numbers of functions [3].
My linter cannot replace that. It only checks that the text is there. Whether the text works is an empirical question for your model and your tasks.
The Description Template I Use Now
- Sentence 1: what the tool does, in domain words.
- Sentence 2: when to reach for it, including the user phrasing that should trigger it.
- Sentence 3: what it returns, field by field.
- Sentence 4: what it does not do, naming the sibling tool that does.
Then the schema: additionalProperties: false, every parameter documented, enums instead of free text, ranges instead of trust.
Common Pitfalls
- A one-sentence description. The model has nothing to discriminate on.
- No negative case. Two sibling tools look identical, so the model picks either one.
- Generic names.
query,get_dataandsearchdescribe nothing. - Ambiguous parameters.
q,data,name,user— preferquery,payload,note_title,user_id. - Asking the model for values you already have, like a session id.
- Free-text where an enum belongs, so typos become runtime errors.
- A loose schema plus hope. Constraints prevent; validation catches.
- Trusting a linter as a quality gate. It checks presence, not performance.
- Never reading the transcripts. Aggregate accuracy hides the wrong-tool choice that caused it.
One Subscription Instead of Five AI Tools
Partner offer
GPTAstra Max: one subscription instead of five AI tools
Chat model, image model and video tool behind one interface with a lifetime licence — one bill while you write and test tool definitions against several models.
Affiliate link — we may earn a commission at no extra cost to you.
Wrapping Up
Good tool definitions say what the tool does, when to use it, when not to, and what comes back. They use specific, namespaced names and unambiguous parameters, and they use schemas to make invalid calls impossible.
Run a linter to catch the obvious gaps, then run real tasks to find the subtle ones. Pick your three busiest tools this week, rewrite each description to four sentences, add an enum or a range wherever a parameter has fixed choices, and compare your results before and after.
The schema layer has a twin on the receiving side: validating tool call outputs with Pydantic shows what to do with the arguments once they arrive, and how LLMs decide to call a tool covers the selection step in more depth. If you are writing these definitions for an MCP server, the low-level Python SDK walkthrough shows where the schema is declared.
Frequently Asked Questions
How long should a tool description be?
Anthropic asks for at least three or four sentences per tool, and more for complex ones. Cover four things: what the tool does, when to use it and when not to, what each parameter means, and any caveats. The practical test is to describe the tool to a new hire who cannot see your code. If that explanation needs a paragraph, the description needs one too, because the model sees only the name, the description and the schema.
What should a tool description include that most descriptions miss?
The negative case. Most descriptions say what a tool does and stop there, which leaves the model guessing between sibling tools. Adding when not to use it, and naming the tool that covers the other case, is what lets a search tool coexist with a knowledge-base search tool without the model picking the wrong one. Also state what comes back, so the model can judge whether the result will answer the question.
Should tool names be namespaced?
Yes, for related tools. Anthropic suggests shared prefixes such as asana_search and jira_search, and its engineering post reports that prefix versus suffix naming had non-trivial effects in evaluations, with results varying by model. That last part matters: the naming scheme is worth testing on your own model rather than assumed. Namespacing costs a few characters and removes a class of confusion when two tools share a verb.
Why is additionalProperties false important in a tool schema?
It rejects arguments you never declared, so a surprise key such as override_approval fails validation instead of reaching your code. OpenAI's examples set additionalProperties to false and mark every property as required, but its guide does not state either as a universal rule, so treat them as good practice and check your provider's requirements. The schema is the layer that makes an invalid call impossible rather than merely discouraged.
Can a schema replace validation in code?
No. A schema constrains the shape of the call, not the meaning of it. A status inside the allowed list can still be wrong for this user, a ticket id matching the pattern can still not exist, and an extra field can still arrive if the client ignores your schema. Validate in your code as well, and keep the schema tight so most bad calls never get that far. The two layers answer different questions.
How do I test whether my tool definitions work?
Write realistic, multi-step tasks with verifiable outcomes, run them programmatically, and track accuracy, tool-call counts and errors while reading the transcripts. Anthropic reports that even small refinements to descriptions can produce dramatic improvements, including one case where Claude kept appending a year to a search query and biased the results until the description was fixed. A linter only checks that the text is present; whether the text works is an empirical question for your model and your tasks.
Sources
[1]Anthropic, "Writing effective tools for agents" — the new-hire test, prefix versus suffix naming, consolidation,response_format, 72 versus 206 token responses, the 25,000-token response limit in Claude Code, and the 2025 query-biasing example: anthropic.com/engineering[2]Anthropic, "Define tools" (tool-use documentation) — detailed descriptions as "by far the most important factor in tool performance", the four things a description must cover, three or four sentences, and shared prefixes likeasana_search: docs.claude.com[3]OpenAI, "Function calling" — the intern test, enums and object structure to prevent invalid states, thetoggle_lightexample,additionalProperties: false, fewer than 20 functions, and values your code already knows: platform.openai.com[4]Model Context Protocol, "Tools" in the specification — howname,descriptionandinputSchemaare advertised to a client: modelcontextprotocol.io