Skip to main content

Tools

A tool is a typed capability an agent can call to read data or take an action beyond the model.

Why tools exist

Models can explain what to do, but they cannot safely inspect your inventory, query an internal service, or send a message. A Forge tool is the boundary around that operation: it describes the inputs the model may supply and classifies the action before it executes.

Your first custom tool

Use defineTool for an operation that reads data. Give it a descriptive name, a useful model-facing description, and an input schema. Schemas are optional in the public helper, but strongly recommended: without one, the model has no reliable argument contract.

import { createAgent } from "@forge/agentkit/providers";
import { defineTool } from "@forge/agentkit/tools";

const inventory = new Map([["SKU-1", { name: "Blue mug", inStock: 14 }]]);

const checkStock = defineTool({
name: "check_stock",
label: "Check stock",
description: "Look up stock by SKU.",
category: "inventory",
effect: "read",
inputSchema: {
type: "object",
properties: { sku: { type: "string" } },
required: ["sku"],
},
execute: async (input: { sku: string }) => inventory.get(input.sku) ?? { error: "Unknown SKU" },
});

const agent = createAgent({
manifest: {
id: "stock-agent",
name: "Stock agent",
instructions: "Use the inventory tool before answering stock questions.",
modelPolicy: { role: "smart" },
},
tools: [{ id: "inventory", listTools: async () => [checkStock] }],
});

ToolProvider is the interface between an agent and one or more tools. The inline provider above is appropriate for a fixed example; use toolProvider("inventory", [checkStock]) for a fixed set, or implement listTools(context) when available tools depend on the tenant or caller.

This example is compile-checked in website/examples/agent-with-tool.ts.

Effects, approval, and idempotency

An effect states what the tool can change. Forge uses it to apply safety rules before the tool function runs.

EffectMeaningDefault approval behavior
readReturns data without changing statenever
internal-writeChanges data owned by this deploymentnever; a tool may explicitly request policy
external-writeSends, publishes, or changes an external systemalways
destructiveDeletes or irreversibly changes dataalways

For an external write, use confirms(). It fixes the effect to external-write, requires approval, and requires an idempotency key together. Use destroys() for an irreversible action.

import { confirms } from "@forge/agentkit/tools";

const sendMessage = confirms({
name: "send_message",
description: "Send an approved message to a customer.",
inputSchema: { type: "object", properties: { recipient: { type: "string" }, text: { type: "string" } }, required: ["recipient", "text"] },
execute: async ({ recipient, text }: { recipient: string; text: string }) => ({ queuedFor: recipient, text }),
});

In embedded mode, an unapproved action returns an approval-required result. In server mode, the run can pause durably until a person approves or denies the stored action. See Human-in-the-Loop for the decision and resume lifecycle.

What runs at execution time

In the normal agent runtime, tools are filtered before discovery and checked again when called. Tool input is validated before execution. External and destructive effects need idempotency protection, so a retry returns the original result rather than repeating the side effect.

Tools can also return a normal result envelope: { ok: true, data } on success, or { ok: false, error } when the operation cannot run. Throw from a defineTool callback when the underlying service fails; Forge converts it to that safe error shape.

Choose the right starting point

NeedStart with
Wrap one function your application ownsdefineTool and a ToolProvider
Make a write safe by defaultconfirms() or destroys()
Add maintained vendor capabilitiesAn integration package
Connect a tenant-owned external tool serverMCP
Use Forge's supplied utilitiesBuilt-in tools

Next