# The tool-use primitive

Web page: https://academy.memberpress.ai/courses/code-agents/l4/

Lesson 4 of 10 in [Claude Code Agents — Sub-Agents, Tools, and MCP](https://academy.memberpress.ai/courses/code-agents/), 25 min.

Video: https://www.youtube.com/watch?v=FSaDlO7xifM

The model does not call your function. Read that sentence again before writing your first tool definition, because the architecture matters for every mistake you will make later.

What actually happens: you send a list of tool definitions alongside the user message. The model reads the definitions. If it decides a tool is appropriate, it emits a structured JSON block — a `tool_use` content block — that names the tool and provides the arguments. Your code receives that block, calls the actual function, and sends the result back as a `tool_result` message. The model reads the result and continues.

The model is making a decision, not an API call. It can only reason about what you return. If you return a fabricated or cached result, the model reasons about the fabrication. This is why tool implementations need to be honest even when they are simple.

**The schema is a decision rule, not documentation**

The description field in a tool definition is not for humans reading your code. It is the signal the model uses to decide when to call this tool versus any other tool available in the session. Write it as a decision rule: what should the model observe in the conversation that makes this the right tool to call?

```json
{
  "name": "get_deploy_status",
  "description": "Return the current deploy status for a given app ID. Call this when the user asks about whether a deploy succeeded, failed, is in progress, or when you need to verify a deploy before proceeding.",
  "input_schema": {
    "type": "object",
    "properties": {
      "app_id": {
        "type": "string",
        "description": "The application identifier, e.g. 'api-prod' or 'dashboard-staging'"
      },
      "include_logs": {
        "type": "boolean",
        "description": "If true, include the last 50 lines of deploy output. Default false."
      }
    },
    "required": ["app_id"]
  }
}
```

Notice: the description tells the model both what the tool does and when to reach for it. "Return the current deploy status" is the what. "Call this when the user asks about whether a deploy succeeded" is the when. Both sentences are load-bearing. Drop either one and the model will under-use or over-use the tool.

**Schema correctness matters more than you expect**

The model will attempt to call the tool with the schema you provided. If the schema says `app_id` is required and the model cannot infer it from context, it will either ask the user or make a guess. If the schema says `include_logs` is optional with a default, the model can omit it safely. Incorrect schemas produce incorrect arguments — and incorrect arguments produce failures your handler has to handle gracefully.

Do not copy-paste a schema from a different tool and edit the name without reading every field. The description carries tool-selection logic. The field descriptions carry argument-generation logic. Every field description in the schema is a prompt to the model.

**What the handler must return**

Return exactly what the description promises, in a structure the model can interpret without ambiguity. For a status check, return a status object with fields that clearly map to states the model knows how to reason about. Do not return a raw API response if the raw response has 40 fields the model does not need — the noise degrades reasoning quality. Strip it to the signal.

On failure, return a structured error, not an exception. The model needs to read the failure and decide what to do next. An unhandled exception that crashes your handler returns nothing — the model receives a malformed response and either hallucinates a result or loops.

**If you get stuck**

The model keeps calling the wrong tool. Check your tool descriptions for semantic overlap — two tools with similar descriptions will cause the model to guess between them. Make each description specific enough that there is no ambiguous case. Add a negative example if needed: "Do not call this for questions about deploy logs — use `get_deploy_logs` for that."

The model calls the tool with valid JSON but your handler throws a type error. The schema said `string` but you expected a number, or the schema marked a field optional but your code assumed it was always present. Fix the schema to match the implementation, not the other way around — the schema is the interface, and the model is generating arguments against it.

Tool call loops: the model calls a tool, gets a result, calls the same tool again with slightly different arguments. The result format is ambiguous — the model got a result it cannot interpret and is retrying with a variation. Fix the return format. Return exactly what the description promises. If the tool is supposed to return a deploy status, return `{ "status": "succeeded", "deployed_at": "..." }` — not a blob of text that mentions the status somewhere in the middle.

**Cross-reference**

l5 wraps tool definitions into an MCP server so Claude Code can discover them automatically — the schema patterns here map directly to the Zod schemas used there. l6 answers the question this lesson probably raised: when should this be a tool versus a shell script that the model just runs with Bash?

**Deliverable**

Write a tool definition for one real operation in your stack: name, description written as a decision rule ("Call this when..."), and complete JSON Schema for the parameters including field descriptions. Then write the two-sentence implementation contract: what the handler must return when it succeeds, and what it must return when it fails. Keep the schema and the contract in the same file — they are the interface, and they need to stay in sync.

## Try these prompts

- Write a tool definition for a get_deploy_status tool. Include the name, a description written as a decision rule ("Call this when..."), and JSON Schema for two parameters: app_id (required) and include_logs (optional boolean).
- Write the two-sentence implementation contract for a tool that checks feature flag status — what it returns on success and what it returns on failure.
- Explain why the description field in a tool definition is a decision rule, not documentation. Give one example of a description that causes under-use and one that causes correct use.

Next: [Writing an MCP server for your stack](https://academy.memberpress.ai/courses/code-agents/l5/)
