The wall without tools
Ask a model what the weather is in San Francisco right now and the only honest answer it can give is that it does not know. Its knowledge ends where its training data ended, and this morning was not in it. Tool use is the structured way past that wall, and the structure is the part worth understanding, because the model still does not call anything. It asks. Your application makes the call, hands the result back, and the model writes its answer out of what it was handed.
A tool is two things kept together: a function in your own code, and a description of that function written for the model to read. The description travels to the model with every question. The function never leaves your process, and it runs with your credentials, on your network, under your logging. Adding a tool changes nothing inside the model - it changes what arrives in the model's input, and what the model is allowed to ask you for.
That is the same bargain retrieval makes, and it is worth saying plainly before the mechanics: the model is not gaining access to your systems. It is gaining a vocabulary for requesting that your systems be used.
One tool call: four steps
Every tool call is the same four steps, always in this order.
- You send the question, and the tools with it. The tool list is part of the request, not a setting on the account: names, descriptions, and the shape of each tool's input, sent again on every call.
- The model answers with a request instead of prose. When it judges that a tool fits, the reply contains a
tool_useblock naming the tool and the input it wants, and the response carriesstop_reason: "tool_use". It has stopped mid-answer and is waiting for you. - Your code runs the function. This is the only step that leaves your process: the HTTP call, the database query, the write. The model is not involved and cannot see how it happened.
- You send the result back, and the model finishes. The data goes into the conversation as a
tool_result, you call the model again, and this time it writes the sentence a person reads.
Hold on to step two, because everything practical follows from it: the model's output is a request. An application that never executes it has no tools at all, whatever its schemas say. An application that executes it without checking has handed the model its credentials. The design is deliberately a handshake, and your half of the handshake is where the judgement lives.
It also means one answered question costs at least two model calls rather than one, and the second call carries everything the first did plus the request and the result. That is the price of the loop, and it is the reason a tool that returns half a megabyte of JSON is a cost problem as much as a quality one.
The same loop, with the weather
Filled in with the question from the top of the article, the four steps stop being abstract.
The exchange itself, both directions, with the parts your code produces marked:
Two details in the middle are the whole mechanism. The model chose the tool and filled in its argument, turning "San Francisco" into the "San Francisco, CA" the function expects - that is the part people mean when they say a model can use an API. And the temperature in the final sentence came from your JSON, not from the model: it is quoting your data back, which is exactly why the answer can be current and exactly why a broken tool produces a confident wrong answer.
The schema is what the model reads
The model cannot see your function. It sees a schema: a name, a description, and a description of the input, written in JSON Schema. That object is the entire interface, and the model decides whether and how to call your code from nothing else. JSON Schema itself is not an AI invention - it is an ordinary data-validation spec that predates all of this, adopted here because describing the shape of some arguments is exactly what it was built for.
Written out, the weather tool is four lines of meaning and a little punctuation:
The description is a prompt, not documentation. It is read by a model that is choosing, so write it for the choice:
- Say what it returns, not only what it does. "Returns temperature in Fahrenheit and a one-word condition" tells the model whether the answer it needs is even in there.
- Say when not to use it. One clause - "not for forecasts" - prevents most wrong picks, and it is the clause people leave out.
- Three or four sentences, not one line. A working description says what the tool does, when to reach for it, and what comes back - and every argument gets a sentence of its own. Under-describing is the common failure and the cheapest one to fix.
- Name parameters the way a person would say them.
locationgets filled correctly far more often thanqorloc_id, because the name is half the instruction. - Constrain in the schema, not in prose. An
enumof three units, or aformatofdate, is checked before your code runs; a sentence asking politely for ISO dates is not. - Optional arguments are a schema feature, not a convention. Give the property a
defaultand leave it out ofrequired. The model then fills it only when the question calls for it, and your function keeps its own default for every other call. - One job per tool. A tool that reads or writes depending on a
modeflag is two tools wearing one schema, and the model will pick the wrong mode eventually.
Schemas are also the cheapest thing to get wrong quietly. Nothing errors when a description is vague - the model simply picks a little worse, on some questions, in ways only an evaluation will show you.
The function behind the schema
The function itself is ordinary code, and it should stay ordinary. The only unusual thing about it is who chose its arguments.
The validation is not defensive habit, it is part of the interface. Error text goes back to the model as the tool result, and the model reads it: given "location cannot be empty" it will usually try again with a real city, where an unexplained 500 ends the conversation. Write the message for the reader you have.
The second piece is dispatch. The model returns a tool name as a string, so something has to map that string to a function, and that something should refuse anything it does not recognise.
A dictionary rather than a chain of conditionals, because the same dictionary can produce the schema list you send with the request - and a tool that exists in one place and not the other is the most common bug in a tool-using app. The keys are also the only names the model can ever invoke, which makes this seven-line function the narrowest place to put an approval check, a rate limit, or an audit log.
Keep the schema in the same file as the function it describes, under the same name with a suffix - get_weather beside get_weather_schema - and build the list you send to the model out of the same dictionary your dispatcher reads. That convention is what stops the two from drifting apart. You do not have to hand-write the JSON either: paste the function into Claude, ask for a tool-calling schema, and read what comes back. And on the Python SDK, wrapping the object in ToolParam from anthropic.types turns a mistyped key into an error at your desk rather than a silently worse tool choice in production.
Content blocks and the message list
Up to here a message has been "the question" or "the answer". Underneath, a message is a list of content blocks, and that is what makes a tool call expressible at all: one assistant turn can hold a sentence of prose and a tool request side by side.
The pairing has rules, and they are the rules that break first in real code. Every tool_use block needs a tool_result carrying the same tool_use_id, and it has to arrive in the very next message. If the model asked for two tools in one turn, both results go in that one message. The result is sent as a user message even though no user typed it, because in this protocol "user" means "the side that is not the model".
And the list only grows. Every call re-sends the whole conversation, including the requests and results of every earlier turn, which is why a chatty tool gets expensive twice: once when its output is read, and again on every turn after that.
Several tools, one choice
Nothing changes structurally when there is more than one tool. You send all their schemas with the question; the model returns the ones it wants.
Which it picks is decided almost entirely by the descriptions, and that has consequences worth designing for. Two tools whose descriptions overlap - a get_weather and a get_conditions that sound alike - produce a coin flip, and the flip is invisible in your logs unless you record which tool was chosen. A long list dilutes: past a couple of dozen tools, the useful move is usually to narrow the set per request rather than to keep writing better prose.
Two behaviours surprise people the first time. The model can pick no tool and answer directly, which is correct for "what is a barometer" and wrong if your code assumes a tool_use block will always be there. And it can ask for several at once, which is faster when the calls are independent and a trap if your dispatcher assumes one.
When one turn is not enough
"Will it rain tomorrow, and remind me to take an umbrella if it will" is two tools, and the second depends on the first. The model cannot know whether to set a reminder until it has the forecast, so it asks once, reads the answer, and asks again. Your code is not orchestrating that sequence; it is servicing it.
Which turns the four steps into a loop with one condition: while the response comes back with stop_reason: "tool_use", run what it asked for, append the result, and call again. When the stop reason is end_turn, the text in that response is the answer and the loop is done.
Two guards belong in that loop from the first version. A turn cap, because a model that keeps asking - a tool that always errors, a task it cannot finish - will otherwise spend your budget in a tidy while-loop. And a record of every request and result, because when the final answer is wrong, the only way to tell a bad tool from a bad choice of tool is to read what was asked and what came back.
This loop is also the shortest honest definition of an agent. Give it tools that change things rather than only read them, let it run more turns, and the machinery in Inside an AI agent is what you have built - the same handshake, with state and termination rules around it.
Tool use beside RAG
Retrieval and tool use are easy to confuse because the end state looks identical: text the model did not have arrives in the prompt, and the answer is written from it. The difference is upstream of that, and it is a difference of ownership.
Which to reach for is a real decision with real trade-offs - always-on cost against a round trip, build-time certainty against per-question judgement - and it is the whole subject of RAG vs MCP. The pipeline side is taken apart in its own article too.
Where MCP takes over
Everything so far lives in your application. You wrote the function, you wrote the schema, you wrote the dispatcher, and all three ship together. That is the right shape for a tool only your app needs, and the wrong shape the moment a second application wants the same capability, because the copy drifts.
The Model Context Protocol answers that by moving the definition out. A server declares its tools, any client can ask it what they are and ask it to run them, and the tools stop being part of any one application. What does not change is this article: the model still returns a request, something still executes it, the result still goes back as a tool_result, and the loop still ends on end_turn. MCP standardises where tools come from, not how a model calls them.
So the order to learn it in is this one: the handshake first, then the protocol. What is MCP? covers the 101, Inside MCP the parts underneath it, and Functions, MCP, and Skills how the three layers stacked up in the order they arrived.
The sentence to keep from all of it: a model with tools has not gained access to anything. It has gained the ability to ask, and every question of what it may actually do is answered by your code, on your side of the handshake.
References
- Claude Academy - Building with the Claude APIacademy.claude.com/courses
- Introducing tool useacademy.claude.com · the tool-use unit, lesson 1
- Tool functionsacademy.claude.com · lesson 3
- Tool schemasacademy.claude.com · lesson 4
- Handling message blocksacademy.claude.com · lesson 5
- Sending tool resultsacademy.claude.com · lesson 6
- Multi-turn conversations with toolsacademy.claude.com · lesson 7
- Using multiple toolsacademy.claude.com · lesson 9
- Understanding JSON Schemajson-schema.org
- Inside an AI agent: the agent loopstacknova · ai · agents
- What is Model Context Protocol?stacknova · ai · protocol