Agent SDK

Tools

Functions that agents can call to interact with the world

Tools

Tools give agents the ability to interact with external systems — search the web, read files, call APIs, execute code, and more. A tool is a function with a typed schema that the model can invoke during its reasoning loop.

Defining Tools

import { Tool } from 'assistme-agent-sdk'
import { z } from 'zod'

const calculator = Tool.create({
  name: 'calculator',
  description: 'Perform mathematical calculations',
  parameters: z.object({
    expression: z.string().describe('A mathematical expression like "2 + 2" or "sqrt(144)"'),
  }),
  execute: async ({ expression }) => {
    // Safe math evaluation
    const result = evaluate(expression)
    return { result }
  },
})

Tool Schema

interface ToolConfig<TParams, TResult> {
  /** Unique name. The model uses this to identify the tool. */
  name: string

  /** Description of what the tool does. Critical for model decision-making. */
  description: string

  /** Zod schema for the tool's parameters */
  parameters: z.ZodType<TParams>

  /** The function that executes when the tool is called */
  execute: (params: TParams, context: ToolContext) => Promise<TResult>

  /** Optional: Restrict which agents can use this tool */
  allowedAgents?: string[]

  /** Optional: Mark tool as requiring human approval before execution */
  requiresApproval?: boolean | ((params: TParams) => boolean)

  /** Optional: Timeout in milliseconds */
  timeout?: number

  /** Optional: Error handler */
  onError?: (error: Error, params: TParams) => TResult | Promise<TResult>
}

Tool Context

Every tool execution receives a ToolContext with metadata about the current run:

const readFile = Tool.create({
  name: 'read_file',
  description: 'Read a file from the filesystem',
  parameters: z.object({
    path: z.string(),
  }),
  execute: async ({ path }, context) => {
    // Access run metadata
    console.log(`Agent "${context.agentName}" is reading ${path}`)
    console.log(`Run ID: ${context.runId}`)
    console.log(`Turn: ${context.turn}`)

    return await fs.readFile(path, 'utf-8')
  },
})

Human Approval

Mark tools that should require human confirmation before executing:

const sendEmail = Tool.create({
  name: 'send_email',
  description: 'Send an email to a recipient',
  parameters: z.object({
    to: z.string().email(),
    subject: z.string(),
    body: z.string(),
  }),
  // Always require approval
  requiresApproval: true,
  execute: async ({ to, subject, body }) => {
    return await emailService.send({ to, subject, body })
  },
})

const deleteRecords = Tool.create({
  name: 'delete_records',
  description: 'Delete records from the database',
  parameters: z.object({
    table: z.string(),
    filter: z.record(z.string()),
    count: z.number(),
  }),
  // Only require approval for bulk deletes
  requiresApproval: ({ count }) => count > 10,
  execute: async ({ table, filter }) => {
    return await db.delete(table, filter)
  },
})

When a tool requires approval, the Runner emits a tool_approval_required event and pauses execution until approved or denied. See Human-in-the-Loop.

Error Handling

Tools should handle errors gracefully. The model sees tool errors and can retry or try a different approach.

const apiCall = Tool.create({
  name: 'api_call',
  description: 'Call an external API',
  parameters: z.object({ url: z.string(), method: z.string() }),
  timeout: 10_000, // 10 second timeout
  execute: async ({ url, method }) => {
    const response = await fetch(url, { method })
    if (!response.ok) {
      throw new Error(`API returned ${response.status}: ${response.statusText}`)
    }
    return response.json()
  },
  onError: (error, params) => {
    return { error: error.message, suggestion: 'Try a different URL or check the API status.' }
  },
})

MCP Tool Integration

Connect to any MCP (Model Context Protocol) server to automatically discover and use its tools:

import { MCPToolProvider } from 'assistme-agent-sdk/mcp'

// Connect to an MCP server
const githubTools = await MCPToolProvider.connect({
  transport: 'stdio',
  command: 'npx',
  args: ['@modelcontextprotocol/server-github'],
  env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN },
})

const agent = new Agent({
  name: 'github-assistant',
  model: claude('claude-sonnet-4-6'),
  instructions: 'Help users manage their GitHub repositories.',
  tools: [
    ...githubTools.tools(), // All tools from the MCP server
  ],
})

MCP with Tool Filtering

// Only include specific tools from the MCP server
const agent = new Agent({
  name: 'reader',
  model: claude('claude-sonnet-4-6'),
  instructions: 'Read and summarize files.',
  tools: [
    ...githubTools.tools({
      include: ['read_file', 'list_files', 'search_code'],
      // exclude: ['delete_file', 'push_commit'],
    }),
  ],
})

When you have hundreds of tools (e.g., from multiple MCP servers), loading them all into context degrades performance. Use tool search to dynamically find relevant tools:

import { ToolSearch } from 'assistme-agent-sdk'

const toolSearch = new ToolSearch({
  tools: [
    ...githubTools.tools(),
    ...slackTools.tools(),
    ...jiraTools.tools(),
    ...dbTools.tools(),
  ],
  maxResults: 5, // Return top 5 matching tools
})

const agent = new Agent({
  name: 'omniscient',
  model: claude('claude-sonnet-4-6'),
  instructions: 'You have access to many tools. Use tool search to find the right one.',
  tools: [
    toolSearch.asTool(), // Meta-tool that searches for tools
  ],
})

The agent first calls tool_search with a description of what it needs, gets back matching tools, and then calls the appropriate tool.

Composing Tools

Tool Chains

Build higher-level tools from lower-level ones:

const searchAndSummarize = Tool.compose({
  name: 'search_and_summarize',
  description: 'Search the web and return a summary of findings',
  parameters: z.object({
    topic: z.string(),
    maxResults: z.number().default(3),
  }),
  steps: [
    { tool: webSearch, map: ({ topic }) => ({ query: topic }) },
    { tool: readUrl, forEach: (results) => results.slice(0, 3).map(r => ({ url: r.url })) },
    { reduce: (contents) => contents.join('\n---\n').substring(0, 10000) },
  ],
})

Tool Groups

Organize related tools into groups:

const dbTools = Tool.group({
  name: 'database',
  description: 'Database operations',
  tools: [queryTool, insertTool, updateTool, deleteTool],
  // Apply shared config to all tools in the group
  defaults: {
    timeout: 30_000,
    requiresApproval: false,
  },
})

const agent = new Agent({
  name: 'db-admin',
  model: claude('claude-sonnet-4-6'),
  instructions: 'Manage the database.',
  tools: dbTools.tools(),
})

Best Practices

  1. Write clear descriptions — The model decides which tool to call based on the description. Be specific and include examples of when to use (and not use) each tool.

  2. Use Zod .describe() — Add descriptions to every parameter. This significantly improves the model's tool-calling accuracy.

  3. Return structured data — Return objects, not strings. The model can reason better about structured tool results.

  4. Handle errors in tools — Don't let tools throw unhandled exceptions. Return error objects so the model can adapt its approach.

  5. Set timeouts — Always set timeouts for tools that make network calls. Default is no timeout, which can hang the agent indefinitely.

  6. Scope permissions — Use allowedAgents to restrict sensitive tools to specific agents. Don't give every agent access to the delete tool.

  7. Prefer MCP — When integrating with external services, prefer MCP servers over custom tool implementations. MCP provides a standardized, tested, community-maintained integration layer.