Claude Tool Use: How to Give Claude Access to Tools
Practical guide to Claude tool use — from basic concepts to building an agent with tools.
What Is Tool Use
Tool use is Claude’s ability to call external functions during response generation. Instead of just generating text, Claude can decide: “I need to look this up” or “I need to calculate this” — and call a function you provide. This is the core technology behind AI agents.
Without tool use, Claude is a smart text generator. With tool use, Claude becomes an agent that can act on the real world — search databases, call APIs, read files, send messages.
How It Works
The flow is simple:
- You describe available tools in JSON Schema format
- Send a request to Claude API along with tool descriptions
- Claude analyzes the user’s request and decides which tool to call (or none)
- Claude returns a
tool_useresponse with the tool name and parameters - You execute the call on your side and return the result
- Claude uses the result to form the final response
User: "What's the weather in London?"
↓
Claude: tool_use → get_weather(city="London")
↓
Your code: calls weather API → returns {temp: 15, condition: "cloudy"}
↓
Claude: "It's 15°C and cloudy in London right now."
The key insight: Claude never executes code directly. It generates structured JSON, and your code handles the actual execution. This keeps you in control.
Defining Tools
Each tool needs three things: a name, a description, and an input schema. The description is critical — Claude uses it to decide when to call the tool.
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "search_web",
"description": "Searches the internet for current information. Use this when the user asks about recent events, current data, or anything that requires up-to-date information.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
}
},
"required": ["query"]
}
},
{
"name": "get_stock_price",
"description": "Gets the current stock price for a given ticker symbol. Returns price in USD.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Stock ticker symbol, e.g. AAPL, GOOGL, MSFT"
}
},
"required": ["ticker"]
}
}
]
Pro tip: Write descriptions as if you’re explaining to a smart colleague when to use the tool. Vague descriptions like “searches stuff” lead to poor tool selection. Specific descriptions like “searches the internet for current information — use when the user asks about recent events” work much better.
Making the API Call
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "What's the current stock price of Apple?"}
]
)
Claude will analyze the message and decide whether to use a tool or respond directly.
Handling tool_use Responses
When Claude decides to use a tool, the response has stop_reason: "tool_use" and contains a tool_use block:
import json
def handle_response(response):
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
tool_use_id = block.id
print(f"Claude wants to call: {tool_name}({json.dumps(tool_input)})")
# Execute the tool
result = execute_tool(tool_name, tool_input)
return tool_use_id, result
elif block.type == "text":
print(f"Claude says: {block.text}")
return None, None
def execute_tool(name: str, input_data: dict) -> str:
if name == "get_stock_price":
# Your actual implementation here
return json.dumps({"ticker": input_data["ticker"], "price": 198.50, "currency": "USD"})
elif name == "search_web":
# Your search implementation
return json.dumps({"results": [{"title": "Result 1", "snippet": "..."}]})
return json.dumps({"error": f"Unknown tool: {name}"})
Multi-Turn Tool Use (The Agent Loop)
Real agents don’t stop at one tool call. They chain multiple calls together. Here’s the full pattern:
def run_agent(user_message: str, max_turns: int = 10):
messages = [{"role": "user", "content": user_message}]
for turn in range(max_turns):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=tools,
messages=messages,
)
# Append assistant response
messages.append({"role": "assistant", "content": response.content})
# If Claude is done — return the text
if response.stop_reason == "end_turn":
for block in response.content:
if hasattr(block, "text"):
return block.text
return "Done."
# If Claude wants to use tools — execute them all
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
# Return results to Claude
messages.append({"role": "user", "content": tool_results})
return "Max turns reached."
This loop is the heart of every Claude agent. Claude keeps calling tools until it has enough information to answer.
Error Handling
Tools can fail. Networks go down, APIs return errors, files don’t exist. Always return structured errors — Claude handles them gracefully:
def execute_tool(name: str, input_data: dict) -> str:
try:
if name == "get_stock_price":
price = fetch_stock_price(input_data["ticker"])
return json.dumps({"price": price})
except ConnectionError:
return json.dumps({"error": "Could not connect to stock API. Try again later."})
except KeyError:
return json.dumps({"error": f"Unknown ticker: {input_data.get('ticker', 'N/A')}"})
except Exception as e:
return json.dumps({"error": f"Unexpected error: {str(e)}"})
return json.dumps({"error": f"Unknown tool: {name}"})
When Claude receives an error, it typically explains the issue to the user or tries an alternative approach. Don’t swallow errors silently — return them as structured data.
Streaming Tool Use
For real-time applications, you can stream tool use responses:
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "Look up Apple stock price"}],
) as stream:
for event in stream:
if event.type == "content_block_start":
if event.content_block.type == "tool_use":
print(f"Calling tool: {event.content_block.name}")
elif event.type == "text":
print(event.text, end="", flush=True)
Streaming is essential for chat interfaces where you want to show Claude “thinking” in real time.
Claude vs OpenAI Tool Use
| Aspect | Claude Tool Use | OpenAI Function Calling |
|---|---|---|
| Protocol | Native + MCP (open standard) | Proprietary |
| State | Stateless (you manage messages) | Stateless (or Assistants API for managed) |
| Parallel calls | Yes | Yes |
| Streaming | Yes | Yes |
| Tool descriptions | JSON Schema | JSON Schema |
| Ecosystem | MCP servers (1000+ community tools) | Plugin ecosystem (limited) |
The biggest advantage of Claude’s approach: MCP compatibility. Instead of writing custom tool integrations, you can connect pre-built MCP servers for GitHub, Slack, databases, and hundreds of other services.
Best Practices
-
Write detailed tool descriptions — Claude picks tools based on descriptions, not names. A good description tells Claude exactly when and why to use the tool.
-
Validate all input — Claude generates JSON parameters, but don’t blindly trust them. Validate types, check ranges, sanitize strings.
-
Set iteration limits — always cap the agent loop (10-20 turns max). Runaway loops burn tokens and money.
-
Return structured data — return JSON, not freeform text. Claude works better with structured tool results.
-
Log everything — log every tool call with inputs and outputs. Essential for debugging when the agent misbehaves.
-
Use
tool_choiceto control behavior:"auto"— Claude decides (default){"type": "tool", "name": "search_web"}— force a specific tool"any"— force Claude to use at least one tool
# Force Claude to use the search tool
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "search_web"},
messages=[{"role": "user", "content": "Find MCP server news"}],
)
When NOT to Use Tool Use
Don’t overcomplicate things. You don’t need tools when:
- The question can be answered from Claude’s training data
- You’re doing text generation, summarization, or translation
- A simple prompt handles the task
Tools add latency, cost, and complexity. Use them when Claude genuinely needs external data or actions — not as a default.
Next Steps
- Build a full agent from scratch: Build Your First AI Agent
- Connect pre-built tools via MCP: What Is MCP
- Compare with OpenAI’s approach: OpenAI Function Calling