or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

batches.mdbeta-features.mdclient.mdcompletions.mderrors.mdfiles.mdindex.mdmessages.mdmodels.mdskills.mdstreaming.mdtools.mdtypes.md
tile.json

tools.mddocs/

Tool Use

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.

Overview

The SDK provides three layers for working with tools:

  1. Basic Tool Use: Manual tool definition and execution loop
  2. Tool Helpers: Zod or JSON Schema-based tool creation with automatic validation
  3. Tool Runner: Automatic execution loop that handles tool calling for you

Basic Tool Use

Defining 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'],
    },
  },
];

Tool Execution Loop

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);

Tool Result Format

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,
}

Tool Choice

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' }],
});

Tool Helpers

The SDK provides helpers to create runnable tools with automatic validation.

Zod Tool Helper

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],
});

JSON Schema Tool Helper

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);
  },
});

Returning Structured Content

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,
        },
      },
    ];
  },
});

Output Format Helpers

The SDK provides helpers for structured output formats that parse the response content automatically.

Zod Output Format

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' }

JSON Schema Output Format

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 }

Memory Tool Helper

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],
});

Tool Runner

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>;
}

Basic Usage

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."

Iteration

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);

Streaming with Tool Runner

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);

Max Iterations

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;

Dynamic Parameters

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;

Add Messages

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;

Generate Tool Response

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);
  }
}

Waiting for Completion

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;

Complex Tool Examples

Multi-Tool Workflow

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],
});

Database Tool

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),
      },
    ];
  },
});

File Operations Tool

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}`);
    }
  },
});

API Call Tool

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),
      },
    ];
  },
});

Error Handling

Tool Execution Errors

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}`;
    }
  },
});

Validation Errors

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 handled

Runner Errors

try {
  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);
  }
}

Best Practices

Tool Naming

// ✅ 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', /* ... */ });

Tool Descriptions

// ✅ 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',
  /* ... */
});

Input Validation

// ✅ 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(),
  }),
  /* ... */
});

Error Messages

// ✅ 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);
  },
});

Security

// ✅ 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);
  },
});

See Also

  • Messages API - Core message creation
  • Streaming - Streaming with tools
  • Beta Features - Advanced tool capabilities
  • Types - Tool type definitions