In the previous lessons, you learned how to create tool schemas and understand Claude's responses when it wants to use tools. You can now recognize when Claude requests tool execution through the stop_reason: "tool_use" signal and extract the necessary details from tool use blocks. However, knowing what Claude wants to do is only half the story — you still need to actually execute those tools and complete the conversation cycle.
In this lesson, you'll learn how to bridge that gap by executing the functions Claude requests, capturing their results, and sending those results back to Claude in the proper format. By the end of this lesson, you'll have a complete tool execution pipeline that can handle Claude's tool requests from start to finish, maintaining proper conversation flow throughout the entire process.
Before we dive into the implementation, let's understand the complete workflow we'll be building in this lesson. Here's the step-by-step process that transforms Claude from a simple chatbot into a capable agent:
- Set up the foundation - Create function mappings, tool schemas, and initial conversation messages
- Send the initial request - Make the first API call to Claude with the user's question and available tools
- Detect tool use requests - Check Claude's response for the
"tool_use"stop reason - Extract tool information - Pull out the function name, parameters, and unique ID from each tool use block
- Execute the requested functions - Use our function mapping to call the actual TypeScript functions with Claude's parameters
- Collect and format tool results - Structure all function outputs in the specific format Claude expects
- Send results back to Claude - Make a second API call with the complete conversation, including tool results
- Display the final response - Show Claude's natural language answer that incorporates the tool outputs
This complete cycle enables Claude to seamlessly use tools as part of its reasoning process, transforming raw function outputs into conversational responses that directly answer user questions.
Before diving into tool execution, we need to establish the foundation that connects Claude's tool requests to our actual TypeScript functions. As we covered in previous lessons, this involves creating a mapping object and preparing our tool schemas and initial messages. The critical component here is the function mapping object — this serves as the bridge between the tool names Claude uses and our actual TypeScript functions.
This setup creates everything we need for tool execution: the tools object enables dynamic function lookup, the systemPrompt guides Claude's behavior, the toolSchemas provide technical specifications, and the messages array starts the conversation. The function mapping is particularly important because it allows our code to execute the correct function based on Claude's string-based tool requests.
With our foundation in place, we can now send the initial request to Claude and immediately prepare for processing its response. This approach ensures we maintain proper conversation history from the start.
This API call provides Claude with the complete context: the user's question through messages, behavioral guidance through system, and available capabilities through tools. By immediately adding Claude's response to the messages array, we ensure proper conversation flow whether Claude needs tools or can answer directly.
After receiving Claude's response and adding it to our conversation history, we examine the stop_reason field to determine if tool execution is needed. When Claude determines that tools are required, it signals this through the "tool_use" stop reason.
The stop_reason === "tool_use" condition indicates that Claude has identified tools it wants to execute and is waiting for their results before continuing. We initialize a toolResults array to collect all the results from this tool execution cycle. The extraction process identifies each tool use block within Claude's response and pulls out the three critical pieces of information: the function name, the input parameters, and the unique identifier. The toolId is particularly important because we'll need it to match results back to their corresponding tool requests.
With the tool information extracted, we can now execute the actual TypeScript functions using our mapping object. This is where the tool names from Claude's requests get translated into actual function calls.
The try-catch block handles both missing tools and runtime errors (like division by zero). The tools[toolName] lookup retrieves the actual function from our mapping object, and we pass parameters as toolInput.a and toolInput.b.
When errors occur, they're returned as strings and sent back to Claude as tool results. Claude can then learn from the feedback and retry with corrected parameters, or inform the user it couldn't complete the operation.
Note that this approach assumes all tools take exactly two parameters named a and b, which works for our current math functions but wouldn't work for tools with different parameter structures. Later in the course, you'll learn more flexible approaches for handling varied parameter structures.
After executing each tool (or encountering an error), we must format the results in the specific structure that Claude expects and collect them all before sending them back. This ensures that all tool results are sent together as a single message.
The tool result structure has specific requirements: each result needs a "tool_result" type, the tool_use_id must exactly match the id from the original tool use block, and the content must be converted to a string. The String() conversion is necessary because Claude's API requires tool result content to be in string format — Claude will process and understand the content regardless of whether it represents a simple number, text, or complex data structure. If your function returns an object or array, you should use JSON.stringify(result) instead of String(result) to properly serialize complex data structures into a string format that Claude can parse and understand. For simple types like numbers or strings, works fine and converts the result directly. By collecting all results in an array and sending them as a single message, we maintain proper conversation structure while ensuring Claude receives results for all requested tools.
Once we've executed all requested tools and added their results to the messages array as a single message, we can send the updated conversation back to Claude to get the final response that incorporates the tool results.
This second API call uses the same parameters as the first, but now the messages array contains the complete conversation history, including all tool results in a single message. Claude can now provide a comprehensive answer that incorporates the tool execution results, transforming raw calculation outputs into natural, conversational responses.
The complete execution flow produces the following output, showing both the tool execution and Claude's final response:
This output demonstrates the complete tool execution cycle: our code executes the requested function with the provided parameters, captures the numerical result, and then Claude transforms that raw output into a natural, conversational response that directly answers the user's original question.
Not every user request will require tool usage. When Claude can answer directly without needing to execute functions, it will respond with a different stop_reason. Since we've already added Claude's response to our messages array, we just need to handle the display of non-tool responses.
This else block catches any stop_reason that isn't "tool_use" and handles it appropriately. The most common alternative stop_reason is "end_turn", which indicates Claude has finished its response and doesn't need any tools to answer the user's question.
If you prefer to be more explicit about which stop reasons you're handling, you can replace the general else with a specific condition:
Both approaches accomplish the same goal, but the explicit else if makes it clear that you're specifically handling the "end_turn" case. For example, if a user asks, "What is the capital of France?", Claude would respond directly with stop_reason: "end_turn" since no mathematical calculations are needed. This ensures your application handles both tool-requiring and non-tool scenarios gracefully, maintaining proper conversation flow in all cases.
Throughout the tool execution cycle, maintaining proper conversation history enables Claude to understand context and provide coherent responses. Let's examine how the complete conversation flow develops through each step of the process.
We check for strings vs. arrays because message content can be either format: simple user messages are often strings (like our initial "Please calculate 15 + 27"), while SDK responses and tool results are always arrays of structured content blocks. Without this check, iterating over a string would print each character separately instead of the full message.
This debugging output reveals the complete conversation structure that enables Claude's tool-using capabilities:
This conversation history shows the complete tool execution cycle: the user's initial request, Claude's response containing both explanation and tool request, our tool result with the matching ID, and Claude's final response incorporating the tool output. This structure demonstrates how proper message management creates seamless conversation flow that transforms Claude into a capable agent.
You now understand the complete tool execution workflow that transforms Claude from a text-only assistant into an agent capable of performing actions and calculations. The process involves detecting tool use requests through stop reasons, executing functions through mapping objects, collecting and formatting all results together, and maintaining conversation history throughout the entire cycle.
The key components work together to create a robust system: function mapping enables dynamic execution, proper result collection ensures all tools are handled, correct message formatting allows Claude to understand results, and conversation history maintains context across the entire interaction. This foundation allows Claude to act as an intelligent agent that can reason about when to use tools and incorporate their results into natural responses.
In the upcoming practice exercises, you'll implement this complete workflow yourself, working with different types of tools and handling various scenarios, including multiple tool uses and error handling. Happy coding!
