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.
tools restricts the agent. An agent created with tools: ['read_file'] can read files and cannot write them or execute code — the restriction is enforced when the tool is called, not only in what the model is offered. Omit tools to give the agent every built-in and every custom tool you have registered. See Restricting an agent's tools.
exec_codelanguage?: 'python' | 'bash', code: stringExecute Python code in an isolated sandbox. Returns stdout.
write_filepath: string, content: stringWrite content to a file in the sandbox.
read_filepath: stringRead content from a file in the sandbox.
list_filesdirectory?: stringList files in a directory.
search_knowledge is attached conditionally — The agent is attached to a knowledge base. Anything beyond this list is either a custom tool you register or a tool discovered from a connected MCP server.Using built-in tools
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({
tools: ['exec_code', 'read_file', 'write_file', 'list_files'],
})
const result = await agent.run(
'Read /data/yc-batch.csv, compare the companies by funding stage, ' +
'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' }Restricting an agent's tools
Use tools to limit what an agent can do — for example a review agent that may read a codebase but never modify it.
// Read-only agent — cannot write files or execute code
const reviewer = await session.agents.create({
tools: ['read_file', 'list_files'],
})
// Unrestricted — every built-in plus every custom tool you've registered
const worker = await session.agents.create({})The restriction applies to built-in tools and to custom tools you register — the shared pools every agent draws from. It does not apply to capabilities you attach to an agent individually: MCP servers (mcp), skills, and knowledge (search_knowledge) are already scoped by their own attachment, so listing tools never silently removes them.
If the agent calls a tool it doesn't have, the call is refused and the agent is told so — it can adapt rather than failing the run. Naming a tool that doesn't exist is logged and ignored.
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.
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',
},
requiresApproval: false,
})
console.log(tool.name) // 'lookup_customer'
console.log(tool.builtin) // false
console.log(tool.requiresApproval) // falseRegister options
The options accepted by tools.register(). This table is generated from the @theazo/contracts schema — it can't drift from the SDK.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Unique tool name within the platform. |
description | string | yes | What the tool does; shown to the model when it decides to call it. |
parameters | Record<string, unknown> | yes | JSON Schema describing the tool input. |
handler | object | yes | How the tool is executed. |
requiresApproval | boolean | — | Gate every call behind a human approval. Default false. |
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.
Request signing (an X-Theazo-Signature header) is not available yet — secure your endpoint with an unguessable URL or a shared token you check yourself until it ships.
// Your API endpoint
export async function POST(req: Request) {
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
})
}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',
},
requiresApproval: true, // human must approve before handler is called
})Listing and testing tools
List tools
const tools = await theazo.tools.list()
// tools = [
// { name: 'exec_code', builtin: true, requiresApproval: false },
// { name: 'write_file', builtin: true, requiresApproval: false },
// { name: 'lookup_customer', builtin: false, requiresApproval: false,
// handler: { type: 'webhook', url: 'https://your-api.com/tools/lookup-customer' } },
// ]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) // the tool's output (string)
console.log(!result.error) // true when the tool succeeded (ToolResult has no `success`)Delete a tool
await theazo.tools.delete('lookup_customer')
// Agents that have this tool in their config will error if they try to call itAgents with custom tools
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' },
requiresApproval: false,
})
// Create an agent that uses both built-in and custom tools
const agent = await session.agents.create({
tools: ['exec_code', 'write_file', 'get_pipeline_status'],
approvals: {
require: ['get_pipeline_status'],
notifyVia: 'webhook',
timeout: '4h',
defaultAction: 'deny',
},
})
const result = await agent.run(
'Check the pipeline status for account ACC_789 and write a summary ' +
'of the open opportunities to /output/acc-789.md'
)
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<ToolResult>Invoke a tool directly. Returns { tool, input, output, error?, duration }.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
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)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 exec_code 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.
// 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 = [
// { name: 'exec_code', builtin: true }, // no prefix
// { name: 'write_file', builtin: true }, // no prefix
// { name: 'filesystem_readFile', builtin: false }, // namespaced MCP tool
// { name: 'filesystem_writeFile', builtin: false },
// { name: 'github_createIssue', builtin: false },
// { name: 'github_listPRs', builtin: false },
// ]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
import { Theazo } from 'theazo'
const theazo = new Theazo({ apiKey: 'th_live_...' })
// Register an MCP server that requires per-user auth
const github = await theazo.mcp.connect({
name: 'github',
transport: 'stdio',
command: 'node',
args: ['mcp-github-server.js'],
credentialsType: 'per_user',
})
// Store a user's token as their per-user auth header for this connection
await theazo.mcp.setUserCredentials(github.id, 'user_123', {
headers: { 'Authorization': 'Bearer gho_xxxxxxxxxxxx' },
})
// Agent runs with the user's own GitHub credentials
const session = await theazo.sessions.forUser('user_123')
const agent = await session.agents.create({
mcp: ['github'],
})
const result = await agent.run('Create an issue for the login bug in acme/frontend')
console.log(result.output)