Introduction
Once we have a clear picture of how the model's memory works, we know how it recalls previous conversation messages. But we may want to tell it to do something; read a file, fetch the latest data, update something in another system, and so on. At first, I thought tools used by models needed to be AI-native. While building the next phase, to enable our raw-claude-chat to execute tasks, I encountered that the model just asks, then I execute and return the result. Basically, I specify what operations are available and what shape their inputs and outputs take, Claude tells when to call them.
The model just needs what you want and what you return
We need to tell our model that we have a user defined tool available for use. By defining the shape for our tool, we set the contract. Let's work with a simple get_word_count tool to have a clear example. The model will be aware of the tool by adding the tools parameter with a defined contract to our model body request, including; the name of the tool, description and the shape of the input the tool needs and the output it returns.
{
model: 'claude-sonnet-4-5',
max_tokens: 1024,
messages,
tools: [
{
name: 'get_word_count',
description: 'Counts the number of words in a given text',
input_schema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'The text to count words in',
},
},
required: ['text'],
},
},
],
};
The model wants to run your tool
You tell the model to count the words in an attached text, but how can we tell when it's execution time?
Claude API response will contain a tool_use block with the tool name and a JSON object of arguments. Logging the API response I encountered the special property: stop_reason: 'tool_use', it clicked instantly how it all worked. The following snippet is the API response where we can see we received a stop_reason and the content type tool_use.
Count the words in the following text: 'Testing this count words tool'
Data {
model: 'claude-sonnet-4-5-20250929',
id: 'msg_011Cco1UzAozGMJNAWAf8DTd',
type: 'message',
role: 'assistant',
content: [
{
type: 'tool_use',
id: 'toolu_015jGi2W344MVoKvveW6fc6L',
name: 'get_word_count',
input: [Object],
caller: [Object]
}
],
stop_reason: 'tool_use', <---- This is the flag to run a tool
stop_sequence: null,
stop_details: null,
usage: {
input_tokens: 598,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
cache_creation: { ephemeral_5m_input_tokens: 0, ephemeral_1h_input_tokens: 0 },
output_tokens: 59,
service_tier: 'standard',
inference_geo: 'not_available'
}
}
The tool_use block is where our tool takes the arguments, runs the operation, and sends the output back in a tool_result block on the next request. Considering this workflow we can tell that Claude never sees the implementation; it's just a contract. This opens the door to running any code you want; file reads, database queries, HTTP calls. This works because the model never touches the implementation.
The other side
I built the MCP tool gitstoria to solve a problem bugging me and as a proof of concept for an MCP server tool. Once I implemented tool use into the model, I saw how MCP abstracts the transport, but the contract underneath is the same as the tool use I built. To build a clear mental model we can tell that every time Claude calls gitstoria MCP tool, it's doing exactly this: returning stop_reason: "tool_use", and the MCP layer handles the execution of the loop.
Next problem: Multiple tools
One tool works. But what happens when there are two, and the model has to pick? And what happens when it needs to call them in sequence without you in the middle?
Top comments (0)