Tool use (also called function calling) allows Claude to request that you execute specific functions and return results, enabling Claude to interact with external systems, perform calculations, access databases, and more.
The SDK provides three layers for working with tools:
interface Tool {
name: string;
description: string;
input_schema: {
type: 'object';
properties: Record<string, JSONSchema>;
required?: string[];
};
}Example:
const tools: Anthropic.Tool[] = [
{
name: 'get_weather',
description: 'Get the current weather in a given location',
input_schema: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city and state, e.g. San Francisco, CA',
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'The unit of temperature',
},
},
required: ['location'],
},
},
];import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const conversation: Anthropic.MessageParam[] = [
{
role: 'user',
content: 'What is the weather like in San Francisco?',
},
];
let message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages: conversation,
});
// Continue loop while Claude wants to use tools
while (message.stop_reason === 'tool_use') {
// Add assistant's response to conversation
conversation.push({
role: 'assistant',
content: message.content,
});
// Execute all requested tools
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of message.content) {
if (block.type === 'tool_use') {
console.log(`Tool: ${block.name}`);
console.log(`Input: ${JSON.stringify(block.input)}`);
// Execute the tool
const result = await executeToolFunction(block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result,
});
}
}
// Add tool results to conversation
conversation.push({
role: 'user',
content: toolResults,
});
// Get next response
message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
messages: conversation,
});
}
// Final response without tool use
console.log(message.content[0].text);interface ToolResultBlockParam {
type: 'tool_result';
tool_use_id: string;
content?: string | ContentBlockParam[];
is_error?: boolean;
cache_control?: CacheControlEphemeral;
}Text result:
{
type: 'tool_result',
tool_use_id: 'toolu_123',
content: 'The weather in San Francisco is 65°F and sunny.',
}Structured result:
{
type: 'tool_result',
tool_use_id: 'toolu_123',
content: [
{
type: 'text',
text: 'Weather data:',
},
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/png',
data: '...', // Weather map image
},
},
],
}Error result:
{
type: 'tool_result',
tool_use_id: 'toolu_123',
content: 'Error: Location not found',
is_error: true,
}Control which tools Claude can use:
type ToolChoice =
| ToolChoiceAuto
| ToolChoiceAny
| ToolChoiceNone
| ToolChoiceTool;
interface ToolChoiceAuto {
type: 'auto';
// Let Claude decide whether to use a tool
}
interface ToolChoiceAny {
type: 'any';
// Force Claude to use at least one tool
}
interface ToolChoiceNone {
type: 'none';
// Prevent tool use (tools are in context but not callable)
}
interface ToolChoiceTool {
type: 'tool';
name: string;
// Force use of a specific tool
}Examples:
// Auto (default) - Claude decides
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
tool_choice: { type: 'auto' },
messages: [{ role: 'user', content: 'What time is it?' }],
});
// Force tool use
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
tool_choice: { type: 'any' }, // Must use a tool
messages: [{ role: 'user', content: 'Help me' }],
});
// Force specific tool
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools,
tool_choice: {
type: 'tool',
name: 'get_weather', // Must use this specific tool
},
messages: [{ role: 'user', content: 'Give me information' }],
});
// Disable tool use
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
tools, // Tools in context for reference
tool_choice: { type: 'none' }, // But can't call them
messages: [{ role: 'user', content: 'Explain the weather tool' }],
});The SDK provides helpers to create runnable tools with automatic validation.
Create tools with Zod schema validation:
import { betaZodTool } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
function betaZodTool<InputSchema extends ZodType>(options: {
name: string;
inputSchema: InputSchema;
description: string;
run: (args: z.infer<InputSchema>) => Promise<string | BetaToolResultContentBlockParam[]> | string | BetaToolResultContentBlockParam[];
}): BetaRunnableTool<z.infer<InputSchema>>;Example:
import { betaZodTool } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
const weatherTool = betaZodTool({
name: 'get_weather',
inputSchema: z.object({
location: z.string().describe('City and state, e.g. San Francisco, CA'),
unit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit'),
}),
description: 'Get the current weather in a given location',
run: async (input) => {
// Automatically validated against schema
console.log('Getting weather for:', input.location);
console.log('Unit:', input.unit);
// Call weather API
const weather = await fetchWeather(input.location, input.unit);
return `The weather in ${input.location} is ${weather.temp}° and ${weather.conditions}`;
},
});
// Use with toolRunner (automatic execution)
const result = await client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'What is the weather in New York?',
}
],
tools: [weatherTool],
});Create tools with JSON Schema:
import { betaTool } from '@anthropic-ai/sdk/helpers/json-schema';
function betaTool<Schema extends JSONSchema>(options: {
name: string;
input_schema: Schema;
description: string;
run: (args: InferredTypeFromSchema<Schema>) => Promise<string | BetaToolResultContentBlockParam[]> | string | BetaToolResultContentBlockParam[];
}): BetaRunnableTool<InferredTypeFromSchema<Schema>>;Example:
import { betaTool } from '@anthropic-ai/sdk/helpers/json-schema';
const calculatorTool = betaTool({
name: 'calculator',
input_schema: {
type: 'object',
properties: {
operation: {
type: 'string',
enum: ['add', 'subtract', 'multiply', 'divide'],
description: 'The mathematical operation',
},
a: {
type: 'number',
description: 'First number',
},
b: {
type: 'number',
description: 'Second number',
},
},
required: ['operation', 'a', 'b'],
},
description: 'Perform basic arithmetic operations',
run: (input) => {
// Input is type-safe based on JSON Schema
const { operation, a, b } = input;
let result: number;
switch (operation) {
case 'add':
result = a + b;
break;
case 'subtract':
result = a - b;
break;
case 'multiply':
result = a * b;
break;
case 'divide':
result = a / b;
break;
default:
throw new Error(`Unknown operation: ${operation}`);
}
return String(result);
},
});Tools can return text or structured content blocks:
import { betaZodTool } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
const imageTool = betaZodTool({
name: 'generate_chart',
inputSchema: z.object({
data: z.array(z.number()),
title: z.string(),
}),
description: 'Generate a chart from data',
run: async (input) => {
const chartImage = await generateChart(input.data, input.title);
// Return structured content
return [
{
type: 'text',
text: `Generated chart: ${input.title}`,
},
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/png',
data: chartImage,
},
},
];
},
});The SDK provides helpers for structured output formats that parse the response content automatically.
Create a structured output format using Zod schemas:
import { betaZodOutputFormat } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
function betaZodOutputFormat<ZodInput extends ZodType>(
zodObject: ZodInput
): AutoParseableBetaOutputFormat<z.infer<ZodInput>>;Example:
import { betaZodOutputFormat } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
const schema = z.object({
name: z.string(),
age: z.number(),
email: z.string().email(),
});
const message = await client.beta.messages.parse({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
output_format: betaZodOutputFormat(schema),
messages: [
{
role: 'user',
content: 'Extract user information: John Doe, 30 years old, john@example.com',
}
],
});
// Typed and validated parsed output
console.log(message.parsed); // { name: 'John Doe', age: 30, email: 'john@example.com' }Create a structured output format using JSON Schema:
import { betaJSONSchemaOutputFormat } from '@anthropic-ai/sdk/helpers/json-schema';
import { JSONSchema } from 'json-schema-to-ts';
function betaJSONSchemaOutputFormat<Schema extends JSONSchema>(
jsonSchema: Schema,
options?: { transform?: boolean }
): AutoParseableBetaOutputFormat<InferredTypeFromSchema<Schema>>;Example:
import { betaJSONSchemaOutputFormat } from '@anthropic-ai/sdk/helpers/json-schema';
const schema = {
type: 'object',
properties: {
sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] },
confidence: { type: 'number', minimum: 0, maximum: 1 },
},
required: ['sentiment', 'confidence'],
} as const;
const message = await client.beta.messages.parse({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
output_format: betaJSONSchemaOutputFormat(schema),
messages: [
{
role: 'user',
content: 'Analyze the sentiment of this text: "I love this product!"',
}
],
});
console.log(message.parsed); // { sentiment: 'positive', confidence: 0.95 }Create a memory tool with custom handlers for memory operations:
import { betaMemoryTool } from '@anthropic-ai/sdk/helpers/memory';
type MemoryToolHandlers = {
[Command in MemoryCommand]: (
command: MemoryCommandObject<Command>
) => Promise<string | BetaToolResultContentBlockParam[]> | string | BetaToolResultContentBlockParam[];
};
function betaMemoryTool(
handlers: MemoryToolHandlers
): BetaRunnableTool<BetaMemoryTool20250818Command>;Example:
import { betaMemoryTool } from '@anthropic-ai/sdk/helpers/memory';
const memoryStorage = new Map<string, string>();
const memoryTool = betaMemoryTool({
read: (command) => {
const value = memoryStorage.get(command.key);
return value || `No value found for key: ${command.key}`;
},
write: (command) => {
memoryStorage.set(command.key, command.value);
return `Successfully wrote to key: ${command.key}`;
},
delete: (command) => {
const existed = memoryStorage.delete(command.key);
return existed ? `Deleted key: ${command.key}` : `Key not found: ${command.key}`;
},
list: () => {
const keys = Array.from(memoryStorage.keys());
return `Available keys: ${keys.join(', ')}`;
},
});
// Use with toolRunner
const result = await client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['memory-20250818'],
messages: [
{
role: 'user',
content: 'Remember that my favorite color is blue',
}
],
tools: [memoryTool],
});Automatic tool execution loop that handles the conversation for you:
client.beta.messages.toolRunner(
params: BetaToolRunnerParams
): BetaToolRunner<Stream>;
interface BetaToolRunnerParams {
model: string;
max_tokens: number;
messages: BetaMessageParam[];
tools: Array<BetaToolUnion | BetaRunnableTool>;
// Optional
stream?: boolean;
max_iterations?: number; // Max tool execution rounds
system?: string | SystemBlockParam[];
temperature?: number;
// ... other message params
}
class BetaToolRunner<Stream extends boolean> {
// Async iteration
[Symbol.asyncIterator](): AsyncIterator<Stream extends true ? BetaMessageStream : BetaMessage>;
// Properties
params: BetaToolRunnerParams; // Current parameters
// Methods
setMessagesParams(params: BetaToolRunnerParams | ((prev: BetaToolRunnerParams) => BetaToolRunnerParams)): void;
pushMessages(...messages: BetaMessageParam[]): void;
generateToolResponse(): Promise<BetaMessageParam | null>;
done(): Promise<void>;
runUntilDone(): Promise<BetaMessage>;
}import Anthropic from '@anthropic-ai/sdk';
import { betaZodTool } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
const client = new Anthropic();
const weatherTool = betaZodTool({
name: 'get_weather',
inputSchema: z.object({
location: z.string(),
}),
description: 'Get the current weather',
run: (input) => {
return `The weather in ${input.location} is sunny and 72°F`;
},
});
// Automatic tool execution
const finalMessage = await client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'What is the weather in San Francisco?',
}
],
tools: [weatherTool],
});
console.log(finalMessage.content[0].text);
// "Based on the weather data, it's currently sunny and 72°F in San Francisco."Process intermediate messages:
const runner = client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Calculate 15 * 23 and then add 100',
}
],
tools: [calculatorTool],
});
// Process each turn
for await (const message of runner) {
console.log('Turn:', message.role);
console.log('Content:', message.content);
console.log('Stop reason:', message.stop_reason);
}
// Get final result
const final = await runner;
console.log('Final answer:', final.content[0].text);const runner = client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
stream: true, // Enable streaming
messages: [
{
role: 'user',
content: 'Search for restaurants and calculate tip',
}
],
tools: [searchTool, calculatorTool],
});
// Each iteration yields a BetaMessageStream
for await (const stream of runner) {
console.log('--- New message stream ---');
stream.on('text', (text) => {
process.stdout.write(text);
});
stream.on('contentBlock', (block) => {
if (block.type === 'tool_use') {
console.log(`\nTool: ${block.name}`);
}
});
await stream.done();
}
// Final message
const final = await runner;
console.log('\nFinal:', final.content);Limit tool execution rounds:
const runner = client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
max_iterations: 5, // Stop after 5 tool execution rounds
messages: [
{
role: 'user',
content: 'Complex multi-step task',
}
],
tools: [tool1, tool2, tool3],
});
const final = await runner;Update parameters during execution:
const runner = client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Help me with a task',
}
],
tools: [myTool],
});
// Update parameters
runner.setMessagesParams({
...runner.params,
temperature: 0.5, // Change temperature
});
// Or use mutator function
runner.setMessagesParams((prevParams) => ({
...prevParams,
max_tokens: prevParams.max_tokens * 2,
}));
const final = await runner;Inject messages during execution:
const runner = client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Start a task',
}
],
tools: [myTool],
});
// Add context mid-execution
runner.pushMessages(
{
role: 'user',
content: 'Additional information: ...',
}
);
const final = await runner;Get tool response for last message:
const runner = client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Task' }],
tools: [myTool],
});
for await (const message of runner) {
// Get tool response that was generated
const toolResponse = await runner.generateToolResponse();
if (toolResponse) {
console.log('Tool results:', toolResponse.content);
}
}Multiple ways to wait:
const runner = client.beta.messages.toolRunner({ /* ... */ });
// Method 1: Iterate and wait
for await (const message of runner) {
console.log('Processing:', message);
}
await runner.done();
// Method 2: Run until done
const final = await runner.runUntilDone();
// Method 3: Direct await (equivalent to runUntilDone)
const final = await runner;import { betaZodTool } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
const searchTool = betaZodTool({
name: 'search',
inputSchema: z.object({
query: z.string(),
}),
description: 'Search the web',
run: async (input) => {
const results = await searchWeb(input.query);
return JSON.stringify(results);
},
});
const analyzeTool = betaZodTool({
name: 'analyze',
inputSchema: z.object({
data: z.string(),
}),
description: 'Analyze data',
run: async (input) => {
const analysis = await analyzeData(input.data);
return analysis;
},
});
const summarizeTool = betaZodTool({
name: 'summarize',
inputSchema: z.object({
text: z.string(),
length: z.enum(['short', 'medium', 'long']),
}),
description: 'Summarize text',
run: async (input) => {
const summary = await summarize(input.text, input.length);
return summary;
},
});
// Claude will use multiple tools in sequence
const result = await client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
messages: [
{
role: 'user',
content: 'Search for recent AI news, analyze the trends, and give me a short summary',
}
],
tools: [searchTool, analyzeTool, summarizeTool],
});const dbTool = betaZodTool({
name: 'query_database',
inputSchema: z.object({
query: z.string().describe('SQL query to execute'),
database: z.enum(['users', 'orders', 'products']),
}),
description: 'Query the database',
run: async (input) => {
// Validate and execute query safely
if (!isSafeQuery(input.query)) {
throw new Error('Unsafe query detected');
}
const results = await db.query(input.database, input.query);
return [
{
type: 'text',
text: `Found ${results.length} results`,
},
{
type: 'text',
text: JSON.stringify(results, null, 2),
},
];
},
});const fileTool = betaZodTool({
name: 'file_operations',
inputSchema: z.object({
operation: z.enum(['read', 'write', 'list']),
path: z.string(),
content: z.string().optional(),
}),
description: 'Perform file operations',
run: async (input) => {
switch (input.operation) {
case 'read':
const content = await fs.readFile(input.path, 'utf-8');
return content;
case 'write':
await fs.writeFile(input.path, input.content || '');
return `Wrote to ${input.path}`;
case 'list':
const files = await fs.readdir(input.path);
return files.join('\n');
default:
throw new Error(`Unknown operation: ${input.operation}`);
}
},
});const apiTool = betaZodTool({
name: 'call_api',
inputSchema: z.object({
endpoint: z.string().url(),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
body: z.record(z.any()).optional(),
headers: z.record(z.string()).optional(),
}),
description: 'Make HTTP API calls',
run: async (input) => {
const response = await fetch(input.endpoint, {
method: input.method,
headers: {
'Content-Type': 'application/json',
...input.headers,
},
body: input.body ? JSON.stringify(input.body) : undefined,
});
const data = await response.json();
return [
{
type: 'text',
text: `Status: ${response.status}`,
},
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
];
},
});const riskyTool = betaZodTool({
name: 'risky_operation',
inputSchema: z.object({
param: z.string(),
}),
description: 'A risky operation',
run: async (input) => {
try {
const result = await riskyOperation(input.param);
return result;
} catch (error) {
// Return error to Claude
return `Error: ${error.message}`;
}
},
});const tool = betaZodTool({
name: 'validated_tool',
inputSchema: z.object({
email: z.string().email(),
age: z.number().min(0).max(120),
}),
description: 'Tool with validation',
run: async (input) => {
// Input is automatically validated
// This will only run if validation passes
return `Processing ${input.email}`;
},
});
// If Claude provides invalid input, validation error is automatically handledtry {
const runner = client.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Task' }],
tools: [myTool],
});
const result = await runner;
} catch (error) {
if (error instanceof Anthropic.APIError) {
console.error('API error:', error.status, error.message);
} else if (error instanceof Anthropic.AnthropicError) {
console.error('SDK error:', error.message);
} else {
console.error('Tool execution error:', error);
}
}// ✅ Good: Clear, descriptive names
betaZodTool({ name: 'get_weather', /* ... */ });
betaZodTool({ name: 'search_database', /* ... */ });
betaZodTool({ name: 'calculate_mortgage', /* ... */ });
// ❌ Bad: Vague or unclear
betaZodTool({ name: 'do_thing', /* ... */ });
betaZodTool({ name: 'tool1', /* ... */ });// ✅ Good: Detailed, explains when to use
betaZodTool({
name: 'get_stock_price',
description: 'Get the current stock price for a given ticker symbol. Use this when the user asks about stock prices, market data, or company valuations.',
/* ... */
});
// ❌ Bad: Too brief
betaZodTool({
name: 'get_stock_price',
description: 'Gets stock data',
/* ... */
});// ✅ Good: Strict validation
betaZodTool({
name: 'send_email',
inputSchema: z.object({
to: z.string().email().describe('Recipient email address'),
subject: z.string().min(1).max(200).describe('Email subject line'),
body: z.string().max(10000).describe('Email body content'),
}),
/* ... */
});
// ❌ Bad: Loose validation
betaZodTool({
name: 'send_email',
inputSchema: z.object({
to: z.string(),
subject: z.string(),
body: z.string(),
}),
/* ... */
});// ✅ Good: Helpful error messages
const tool = betaZodTool({
name: 'divide',
inputSchema: z.object({
a: z.number(),
b: z.number(),
}),
description: 'Divide two numbers',
run: (input) => {
if (input.b === 0) {
return 'Error: Cannot divide by zero. Please provide a non-zero divisor.';
}
return String(input.a / input.b);
},
});
// ❌ Bad: Generic errors
const tool = betaZodTool({
name: 'divide',
/* ... */
run: (input) => {
if (input.b === 0) {
throw new Error('Error');
}
return String(input.a / input.b);
},
});// ✅ Good: Validate and sanitize inputs
const dbTool = betaZodTool({
name: 'query_db',
inputSchema: z.object({
query: z.string(),
}),
description: 'Query database',
run: async (input) => {
// Validate query before execution
if (!isAllowedQuery(input.query)) {
return 'Error: Query not allowed';
}
// Use parameterized queries
const results = await db.execute(input.query);
return JSON.stringify(results);
},
});
// ❌ Bad: No validation
const dbTool = betaZodTool({
name: 'query_db',
/* ... */
run: async (input) => {
// Direct execution - SQL injection risk!
const results = await db.execute(input.query);
return JSON.stringify(results);
},
});