The agent never sees your code
In Level 1 you followed one tool call through the agent loop. This level covers the step just before that call: choosing which tool to use. You know your API from the inside. The model doesn't. All it sees of each tool is three things:
- the name
- the description
- the parameter schema (property names, types, enums, which fields are required, and any per-parameter descriptions)
Nothing else gets to the model: not your README, not your implementation, not the intent behind the endpoint. On the Claude API, tool definitions are written into a special system prompt that comes before the conversation. In MCP, the client calls tools/list and hands those same fields to the model. Picking a tool is reading comprehension, not code analysis.
Think of a new hire on their first day, given a list of internal scripts and one line about each. Whatever that line leaves out, they will guess. Anthropic's engineering guidance (11 September 2025) puts it the same way: describe the tool as you would to a new hire.
Walkthrough: predict the pick
The running example for this course is Ledgerly, an invoicing API. Here is a first attempt at exposing it as tools:
[
{ "name": "search",
"description": "Search Ledgerly.",
"inputSchema": { "type": "object",
"properties": { "q": { "type": "string" } } } },
{ "name": "get_invoice",
"description": "Gets an invoice.",
"inputSchema": { "type": "object",
"properties": { "id": { "type": "string" } },
"required": ["id"] } },
{ "name": "lookup_customer",
"description": "Look up customer info and invoices.",
"inputSchema": { "type": "object",
"properties": { "customer": { "type": "string" } } } }
]
The user asks: "Has Acme paid INV-2041 yet?"
Now put yourself in the model's place:
get_invoicelooks right, but isidthe human numberINV-2041or an internal UUID? The schema doesn't say. And does the result include payment status? The description doesn't say.lookup_customeralso mentions invoices, so it competes. And shouldcustomerbe a name or an ID?searchclaims everything, so it's the safe fallback whenever the model is unsure.
The usual result: the agent calls search with q: "Acme INV-2041 paid" and gets back a fuzzy list. Or it passes INV-2041 to get_invoice as id, which expected a UUID, and gets a 404. Neither failure is the model being stupid. The descriptions overlap, so the model had to guess.
Gotcha: Give a model an overlapping set of tools and it tends to fall back on the most general one. A tool called
searchwith a one-line description pulls in requests that belong elsewhere.
Rewriting a description: what, when, when not
Anthropic's tool-use docs (checked October 2026) say the description is "by far the most important factor in tool performance". They recommend at least 3–4 sentences, covering what the tool does, when to use it and when not to, what each parameter means, and what the tool does not return. Here is get_invoice rewritten:
{
"name": "ledgerly_invoice_get",
"description": "Fetch one invoice by its invoice number, including amount, due date and payment status (paid, partially_paid, unpaid, void). Use this when the user names a specific invoice, e.g. 'INV-2041'. Do not use it to find invoices by customer or date; use ledgerly_invoice_search for that. Does not return the customer's contact details.",
"inputSchema": {
"type": "object",
"properties": {
"invoice_number": { // was "id": now says which ID
"type": "string",
"pattern": "^INV-[0-9]+$", // the format is visible to the model
"description": "Human-facing invoice number, e.g. INV-2041"
}
},
"required": ["invoice_number"]
}
}
What changed, and why it matters:
- What it does lists the fields that answer "has it been paid?", so the model knows this tool is enough.
- When to use connects the tool to how users phrase requests ("names a specific invoice").
- When not to sends the model to the sibling tool. This one sentence does most of the work of separating overlapping tools.
- The parameter name
invoice_numberreplacesid. The guidance is to prefer unambiguous names (user_id, notuser). The schema is part of the description.
Trade-off: Longer descriptions cost tokens on every request. Level 3 measures that cost. For now: a clear sentence costs much less than a wrong tool call.
Collisions: when two tools share a name
An agent seldom runs your server alone. Picture a user who connects Ledgerly and a CRM, and both expose search. The MCP spec (version 2026-07-28) addresses this directly. Tool names only have to be unique within one server. Clients that combine several servers SHOULD disambiguate, for example by adding a server identifier as a prefix. And serverInfo.name is not guaranteed to be unique, so clients shouldn't rely on it to tell servers apart.
Clients handle this differently. Claude Code shows MCP tools as mcp__<server>__<tool>, where <server> is whatever name the user gave when adding the server. So two search tools become mcp__ledgerly__search and mcp__crm__search. The prefix stops the names clashing, but the model still has to choose between two tools described as "Search." Don't rely on the client. Put your own namespace in the name and the scope in the description:
ledgerly_invoice_search # resource + action, namespaced by product
ledgerly_invoice_get
ledgerly_customer_get
Anthropic reports that prefix-based and suffix-based naming score measurably differently in their evaluations, and that the effect varies by model. Choose one scheme, use it everywhere, and test it (Level 30-something will cover how).
Gotcha: name rules differ by layer. The MCP spec allows letters, digits,
_,-and., up to 128 characters. The Claude API's tool-name regex is^[a-zA-Z0-9_-]{1,128}$, which has no dot. Claude Code replaces any character outside that set with_. Soledgerly.invoice.getmay reach the model under a different name than the one you wrote. Stick to letters, digits and underscores and the name stays the same everywhere.
A quick collision audit
Before you ship, put all your tools side by side and ask:
1. Could two descriptions both honestly claim the same request? -> add "when not" lines
2. Is any tool a catch-all (search, query, run, do)? -> narrow or remove it
3. Do two tools share a verb but differ only in an unstated way? -> name the difference
4. Would your names clash with a popular server's (search, get)? -> namespace them
5. Are parameter names ambiguous (id, user, name, q)? -> say which thing they are
Key takeaways
- The agent picks a tool from its name, description and parameter schema alone. Anything you leave out, it guesses.
- A good description covers what the tool does, when to use it, when not to (naming the alternative), and what it does not return.
- Overlapping scope and catch-all tools cause wrong picks more often than the model does.
- MCP names only have to be unique within one server. Namespace your own tools, because clients disambiguate in different ways.
- Name rules differ between the MCP spec and model APIs. Plain
snake_casewith letters, digits and underscores survives every layer.