> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://platform.bctrl.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://platform.bctrl.ai/_mcp/server.

# Give an Agent Custom Tools

> Let a hosted agent call your backend mid-task through a webhook tool - and audit every call it makes.

Agents that can only browse stop at the edge of the page. A [webhook tool](/sdk/tools) lets the agent call *your* API mid-task - create the order it just priced, look up a customer record, post a result - and every call is recorded for audit.

```ts
import { Bctrl } from "@bctrl/sdk";

const bctrl = new Bctrl({ apiKey: process.env.BCTRL_API_KEY! });

// 1. A tool that calls your backend.
const tool = await bctrl.tools.create({
  type: "webhook",
  name: "create-order",
  url: "https://api.acme.com/orders",
  timeoutMs: 10_000,
});

// Try it before an agent does.
const test = await bctrl.tools.test(tool.id, {
  input: { sku: "WIDGET-1", quantity: 2 },
});

// 2. Bundle it with the builtins the task needs.
const toolset = await bctrl.toolsets.create({
  name: "ordering",
  builtins: ["captcha", "vault"],
  toolIds: [tool.id],
});

// 3. The agent browses AND acts on your system.
const runtime = await bctrl.runtimes.create({ type: "browser", name: "ordering-agent" });
const { runId } = await bctrl.runtimes.start(runtime.id);

await bctrl.runtimes.invocations.createAndWait(
  runtime.id,
  {
    action: "browserUse",
    instruction:
      "Find the current price for WIDGET-1 on supplier.com. If it's under $20, create an order for 2 units using the create-order tool.",
    toolsetId: toolset.id,
  },
  { timeoutMs: 300_000 }
);

await bctrl.runtimes.stop(runtime.id);

// 4. Audit: every tool call the agent made, with inputs and outputs.
const { data: calls } = await bctrl.toolCalls.list({ runId });
for (const call of calls) {
  console.log(call.id, call.status);
}
```

The tool call log is the part to lean on in production: when an agent does something surprising, `toolCalls.list({ runId })` plus the [recording](/cookbook/customer-replay) reconstructs exactly what it saw and what it did about it.

## Hosted tools

No endpoint to call? Ship the code to BCTRL instead and it runs server-side:

```ts
const tool = await bctrl.tools.create({
  type: "hosted",
  name: "summarize",
  source: "export default async (input) => ({ summary: input.text.slice(0, 280) })",
});
```

## Next

* [Tools & Toolsets](/sdk/tools) - the full tool surface
* [Run a Hosted Agent](/cookbook/hosted-agent) - the agent side of this recipe
* [Vault](/sdk/vault) - secrets a tool can reference