Get Started

Tools

A standard interface for agent capabilities. Built-in tools cover common tasks out of the box. Register custom tools via webhook to extend agents with your own APIs. For connecting to external MCP servers, see MCP.

Built-in tools

Pass any built-in tool name in the tools array when creating an agent. No configuration required.

web_searchquery: string, limit?: number

Search the web and return structured results. Powered by Brave Search.

http_requesturl, method, headers?, body?

Make HTTP requests to any URL. Supports GET, POST, PUT, DELETE with custom headers and body.

write_filepath: string, content: string | Buffer

Write content to a file in the agent's sandbox filesystem. Creates parent directories automatically.

read_filepath: string

Read a file from the sandbox filesystem. Returns content as a string.

screenshotselector?: string (defaults to full page)

Capture a screenshot of the current browser viewport. Returns a base64 PNG.

pdf_readersource: string (path or URL)

Extract text and structure from a PDF file path or URL. Returns pages as structured text.

send_emailto, subject, body, cc?, bcc?, attachments?

Send an email via your configured SMTP or email provider integration.

read_spreadsheetpath: string, sheet?: string

Parse a CSV or XLSX file into a structured array of rows. Handles headers automatically.

Using built-in tools

agent-with-tools.ts
import { Theazo } from 'theazo'

const theazo = new Theazo({ apiKey: 'th_live_...' })
const session = await theazo.sessions.forUser('user_123')

const agent = await session.agents.create({
  name:  'researcher',
  tools: ['web_search', 'http_request', 'write_file', 'pdf_reader'],
})

const result = await agent.run(
  'Search for the latest Y Combinator batch companies, download their one-pagers, ' +
  'and write a comparison report to /output/yc-analysis.md'
)

console.log(result.output)
console.log(result.artifacts)  // ['/output/yc-analysis.md']
console.log(result.cost)       // { amount: 189, currency: 'usd' }

Custom tools

Register a custom tool by providing a name, description, parameter schema, and a webhook URL. When an agent calls the tool, Theazo POSTs the arguments to your URL and returns the response to the agent.

register-tool.ts
const tool = await theazo.tools.register({
  name:        'lookup_customer',
  description: 'Look up a customer by email and return their account details and subscription status.',
  parameters: {
    type:       'object',
    properties: {
      email:  { type: 'string',  description: 'Customer email address' },
      fields: { type: 'array',   items: { type: 'string' }, description: 'Fields to include' },
    },
    required: ['email'],
  },
  handler: {
    type:   'webhook',
    url:    'https://your-api.com/tools/lookup-customer',
    secret: process.env.TOOL_WEBHOOK_SECRET,
  },
  requiresApproval: false,
})

console.log(tool.name)      // 'lookup_customer'
console.log(tool.id)        // 'tool_abc123'

Implementing the tool handler

Theazo POSTs to your webhook URL with the agent-provided arguments. Return a JSON response — the agent receives it as the tool result.

tool-handler.ts
// Your API endpoint
export async function POST(req: Request) {
  // Verify signature
  const signature = req.headers.get('X-Theazo-Signature')
  if (!verifySignature(signature, await req.text(), process.env.TOOL_WEBHOOK_SECRET!)) {
    return Response.json({ error: 'Invalid signature' }, { status: 401 })
  }

  const { email, fields } = await req.json()

  // Your business logic
  const customer = await db.customers.findByEmail(email)
  if (!customer) {
    return Response.json({ found: false })
  }

  return Response.json({
    found:        true,
    id:           customer.id,
    email:        customer.email,
    plan:         customer.plan,
    mrr:          customer.mrr,
    createdAt:    customer.createdAt,
    // Only include requested fields if specified
  })
}
Tool handler responses must be JSON-serializable. Keep responses focused — agents perform better when tool output is concise and structured. Avoid returning large blobs of text or binary data.

Tools that require approval

Set requiresApproval: true to gate a custom tool through the Approvals system. The agent will pause when it tries to call the tool.

await theazo.tools.register({
  name:        'delete_account',
  description: 'Permanently delete a customer account and all associated data.',
  parameters: {
    type:       'object',
    properties: {
      customerId: { type: 'string', description: 'Customer ID to delete' },
      reason:     { type: 'string', description: 'Reason for deletion' },
    },
    required: ['customerId', 'reason'],
  },
  handler: {
    type:   'webhook',
    url:    'https://your-api.com/tools/delete-account',
    secret: process.env.TOOL_WEBHOOK_SECRET,
  },
  requiresApproval: true,   // human must approve before handler is called
})

Listing and testing tools

List tools

const tools = await theazo.tools.list()

// tools = [
//   { name: 'web_search',       source: 'builtin',  requiresApproval: false },
//   { name: 'http_request',     source: 'builtin',  requiresApproval: false },
//   { name: 'lookup_customer',  source: 'custom',   requiresApproval: false },
//   { name: 'create_issue',     source: 'mcp',      serverId: 'mcp_abc' },
// ]

Test a tool

Call theazo.tools.test() to invoke a tool directly without running an agent. Useful for verifying your webhook handler responds correctly.

const result = await theazo.tools.test('lookup_customer', {
  email:  'alice@acme.com',
  fields: ['id', 'plan', 'mrr'],
})

console.log(result.output)   // { found: true, id: 'cus_...', plan: 'pro', mrr: 299 }
console.log(result.duration) // 142  (milliseconds)
console.log(result.status)   // 'success' | 'error' | 'timeout'

Delete a tool

await theazo.tools.delete('lookup_customer')
// Agents that have this tool in their config will error if they try to call it

Agents with custom tools

agent-custom-tools.ts
import { Theazo } from 'theazo'

const theazo = new Theazo({ apiKey: 'th_live_...' })
const session = await theazo.sessions.forUser('user_123')

// Register custom tool
await theazo.tools.register({
  name:        'get_pipeline_status',
  description: 'Get the current status of a sales pipeline for a given account.',
  parameters: {
    type:       'object',
    properties: {
      accountId: { type: 'string' },
    },
    required: ['accountId'],
  },
  handler: {
    type:   'webhook',
    url:    'https://api.yourcrm.com/tools/pipeline-status',
    secret: process.env.CRM_TOOL_SECRET,
  },
  requiresApproval: false,
})

// Create agent that uses both built-in and custom tools
const agent = await session.agents.create({
  name:  'sales-assistant',
  tools: [
    'web_search',
    'send_email',
    'get_pipeline_status',  // custom tool
  ],
  approvals: {
    require:       ['send_email'],
    timeout:       '4h',
    defaultAction: 'deny',
  },
})

const result = await agent.run(
  'Check the pipeline status for account ACC_789, research their recent news, ' +
  'and draft a personalized follow-up email'
)

console.log(result.output)
console.log(result.cost)  // { amount: 87, currency: 'usd' }

API reference

theazo.tools.register(config)Promise<Tool>Register a custom tool with a webhook handler. Built-in tools cannot be registered.
theazo.tools.list()Promise<Tool[]>List all tools available: built-in, custom, and MCP-sourced.
theazo.tools.test(name, input)Promise<ToolTestResult>Invoke a tool directly. Returns output, duration, and status.
theazo.tools.delete(name)Promise<void>Delete a custom tool. Built-in tools cannot be deleted.

MCP stdio transport

MCP servers can run as local processes — not just over HTTP. Theazo spawns the process using StdioClientTransport from @modelcontextprotocol/sdk and communicates over stdin/stdout. Tools are discovered live at agent run start, so the tool list always reflects the server's current capabilities.

Theazo tracks process health automatically. If a stdio server crashes twice, it is marked unavailable and agents will skip it until it is re-registered or manually re-enabled.

Registering a stdio MCP server

mcp-stdio.ts
import { Theazo } from 'theazo'

const theazo = new Theazo({ apiKey: 'th_live_...' })

// Connect an MCP server running as a local process
await theazo.mcp.connect({
  name:      'filesystem',
  transport: 'stdio',
  command:   'node',
  args:      ['mcp-filesystem-server.js'],
})

// Tools are discovered automatically at agent run start
const agent = await session.agents.create({
  name:  'file-worker',
  tools: ['filesystem_readFile', 'filesystem_writeFile'],
})

const result = await agent.run('List all .ts files in /src and summarize them')
console.log(result.output)
Stdio transport is ideal for local development and self-hosted deployments where the MCP server runs on the same machine as the Theazo worker. For remote servers, use the HTTP transport described in the MCP docs.

Tool namespacing

When multiple MCP servers are connected, tool names are automatically namespaced to prevent collisions. The format is <serverName>_<toolName> — for example, a tool called readFile on a server named filesystem becomes filesystem_readFile.

Built-in tools (like web_search and write_file) keep their original names with no prefix. Only MCP-sourced tools are namespaced. The agent model sees the namespaced names in the tool definitions it receives, so prompts and tool calls always use the full namespaced name.

namespacing-example.ts
// Connect two MCP servers
await theazo.mcp.connect({ name: 'filesystem', transport: 'stdio', command: 'node', args: ['fs-server.js'] })
await theazo.mcp.connect({ name: 'github',     transport: 'stdio', command: 'node', args: ['gh-server.js'] })

const tools = await theazo.tools.list()
tools-response.json
// tools = [
//   { name: 'web_search',            source: 'builtin' },           // no prefix
//   { name: 'write_file',            source: 'builtin' },           // no prefix
//   { name: 'filesystem_readFile',   source: 'mcp', server: 'filesystem' },
//   { name: 'filesystem_writeFile',  source: 'mcp', server: 'filesystem' },
//   { name: 'github_createIssue',    source: 'mcp', server: 'github' },
//   { name: 'github_listPRs',        source: 'mcp', server: 'github' },
// ]

Per-user MCP credentials

Some MCP servers require per-user authentication — for example, an agent acting on behalf of a user needs that user's own GitHub token or Slack OAuth token. Theazo stores these credentials in the encrypted secrets vault, scoped per user so each user's tokens are isolated.

Set credentialsType: 'per_user' on the MCP connection. When the agent runs, Theazo resolves the correct credentials for the session's user. For OAuth2 providers, Theazo handles token refresh automatically — you only need to store the initial tokens.

Setting up per-user credentials

per-user-mcp.ts
import { Theazo } from 'theazo'

const theazo = new Theazo({ apiKey: 'th_live_...' })

// Register an MCP server that requires per-user auth
await theazo.mcp.connect({
  name:            'github',
  transport:       'stdio',
  command:         'node',
  args:            ['mcp-github-server.js'],
  credentialsType: 'per_user',
  oauth: {
    provider:     'github',
    clientId:     process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    scopes:       ['repo', 'read:org'],
  },
})

// Store a user's token in the secrets vault
await theazo.secrets.set('user_123', 'github', {
  accessToken:  'gho_xxxxxxxxxxxx',
  refreshToken: 'ghr_xxxxxxxxxxxx',
  expiresAt:    '2025-07-01T00:00:00Z',
})

// Agent runs with the user's own GitHub credentials
const session = await theazo.sessions.forUser('user_123')
const agent = await session.agents.create({
  name:  'github-assistant',
  tools: ['github_createIssue', 'github_listPRs', 'web_search'],
})

const result = await agent.run('Create an issue for the login bug in acme/frontend')
console.log(result.output)
Per-user credentials are encrypted at rest with AES-256-GCM and scoped by user ID. Agents can only access credentials for the user associated with their session. OAuth2 tokens are refreshed automatically when they expire — your MCP server always receives a valid token.
Was this page helpful?
Ask anything...⌘I