Toolforge Docs
DocsConceptsTool

Tool

The fundamental building block for creating interactive applications in Tool Forge.

What is a Tool?

A Tool is the fundamental building block in Tool Forge. It's a TypeScript function that defines what inputs to collect from users, what logic to execute, and what output to display. Tool Forge automatically generates the UI for your tools—you just write the backend logic.

Think of a tool as a backend-defined workflow that:

  1. Collects structured inputs from users
  2. Executes your business logic
  3. Returns results to the dashboard
import { defineTool } from '@toolforge-js/sdk/components'
import * as z from 'zod'

export default defineTool({
  name: 'Hello World',
  description: 'A simple greeting tool',
  handler: async ({ io }) => {
    const name = await io.textInput({
      label: 'Enter your name',
      validationSchema: z.string().min(1),
    })

    return `Hello, ${name}!`
  },
})

Tool Structure

Every tool is defined using the defineTool() function, which accepts an options object with the following properties:

PropertyTypeRequiredDescription
namestringYesThe display name of the tool in the dashboard
descriptionstringNoA brief description of what the tool does
handlerfunctionYesThe async function containing your tool's logic

The Handler Function

The handler is where your tool's logic lives. It receives an object with four powerful properties:

handler: async ({ io, block, signal, metadata }) => {
  // Your tool logic here
}
PropertyDescription
ioIO methods for collecting user input and displaying feedback
blockBlock methods for creating rich output components (charts, tables, KPIs)
signalAn AbortSignal for handling user cancellation
metadataInformation about the current tool session

IO Methods

The io object is your primary interface for interacting with users. It provides methods to collect various types of input:

Input Methods

Interaction Methods

Progress Methods

Block Outputs

The block object lets you create rich, visual output components. Instead of returning plain text or JSON, you can return beautifully rendered charts, tables, and layouts.

Charts

MethodDescription
block.areaChart()Area chart for visualizing trends over time
block.barChart()Bar chart for comparing categorical data
block.lineChart()Line chart for continuous data visualization
block.donutChart()Donut chart for proportional data
block.comboChart()Combination of bar and line charts
block.heatmapChart()Heatmap for matrix-style data visualization
block.sparkChart()Small, inline chart for trends

Progress & Metrics

MethodDescription
block.progressBar()Visual progress bar
block.progressCircle()Circular progress indicator
block.kpiCard()Key Performance Indicator card with value and optional trend
block.tracker()Status tracker for multiple items
block.categoryBar()Horizontal bar for categorical comparison
block.barList()Vertical list of horizontal bars

Data Display

MethodDescription
block.table()Structured tabular data display
block.text()Formatted text with markdown support
block.image()Image display with caption
block.object()Key-value pair display for structured data

Layout

MethodDescription
block.layout()Grid layout for arranging multiple blocks

Example: Rich Output

import { defineTool } from '@toolforge-js/sdk/components'

export default defineTool({
  name: 'Sales Dashboard',
  description: 'View sales metrics and trends',
  handler: async ({ io, block }) => {
    const region = await io.selectInput({
      label: 'Select Region',
      options: [
        { label: 'North America', value: 'NA' },
        { label: 'Europe', value: 'EU' },
        { label: 'Asia Pacific', value: 'APAC' },
      ],
    })

    // Fetch your data based on selection
    const salesData = await fetchSalesData(region)

    return block.layout({
      children: [
        {
          element: block.kpiCard({
            name: 'Total Revenue',
            value: salesData.totalRevenue,
            valueFormat: { type: 'currency', currency: 'USD' },
          }),
          colSpan: 4,
        },
        {
          element: block.kpiCard({
            name: 'Orders',
            value: salesData.orderCount,
          }),
          colSpan: 4,
        },
        {
          element: block.kpiCard({
            name: 'Avg Order Value',
            value: salesData.avgOrderValue,
            valueFormat: { type: 'currency', currency: 'USD' },
          }),
          colSpan: 4,
        },
        {
          element: block.areaChart({
            title: 'Revenue Trend',
            data: salesData.monthlyTrend,
            index: 'month',
            categories: ['revenue'],
          }),
          colSpan: 12,
        },
      ],
    })
  },
})

Handling Cancellation

Users can stop a running tool at any time. The signal property is an AbortSignal that lets you handle this gracefully:

handler: async ({ io, signal }) => {
  const items = await fetchLargeDataset()

  for (const item of items) {
    // Check if user cancelled
    if (signal.aborted) {
      return 'Operation cancelled by user'
    }

    await processItem(item)
  }

  return 'All items processed!'
}

Always check signal.aborted in long-running loops to ensure your tool responds promptly to user cancellation.

Tool Metadata

The metadata object provides context about the current tool execution:

PropertyDescription
sessionIdUnique identifier for this tool execution (useful as idempotency key)
runnerIdIdentifier of the runner executing the tool
toolNameThe name of the tool
toolDescriptionThe description of the tool
toolIdUnique identifier for the tool definition
handler: async ({ io, metadata }) => {
  console.log(`Session: ${metadata.sessionId}`)
  console.log(`Running on: ${metadata.runnerId}`)

  // Use sessionId as idempotency key for external API calls
  await externalApi.processPayment({
    idempotencyKey: metadata.sessionId,
    // ...
  })
}

Tool Organization

Tools are organized in your project's tools/ directory. Each file should export a default tool definition:

tools/
├── hello-world.ts
├── orders/
│   ├── create-order.ts
│   ├── refund-order.ts
│   └── list-orders.ts
└── users/
    ├── create-user.ts
    └── update-user.ts

Tool Forge automatically discovers all tools in subdirectories, making it easy to organize by domain or feature.

Files and folders starting with - or _ are ignored by Tool Forge. Use this for utility files like -lib/helpers.ts.

Best Practices

1. Use Descriptive Names

// ✅ Good
defineTool({
  name: 'Process Customer Refund',
  description: 'Review and process refund requests for customer orders',
  // ...
})

// ❌ Avoid
defineTool({
  name: 'Refund',
  // Missing description
  // ...
})

2. Validate Inputs Early

Use Zod schemas with IO methods to catch invalid input before processing:

const email = await io.textInput({
  label: 'Customer Email',
  validationSchema: z.email('Please enter a valid email'),
})

3. Use Confirmations for Destructive Actions

const shouldProceed = await io.confirm({
  title: 'Delete User Account',
  description: 'This action cannot be undone. Are you sure?',
})

if (!shouldProceed) {
  return 'Operation cancelled'
}

4. Show Progress for Long Operations

const progress = await io
  .progressLoader({
    title: 'Processing records',
    itemsInQueue: records.length,
  })
  .start()

for (const record of records) {
  await processRecord(record)
  await progress.increment()
}

await progress.stop()

5. Handle Errors Gracefully

try {
  const result = await riskyOperation()
  return result
} catch (error) {
  await io.message({
    title: 'Operation Failed',
    description: error.message,
    type: 'error',
  })
  throw error // Re-throw to mark the session as failed
}

Next Steps

On this page