Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cline.bot/llms.txt

Use this file to discover all available pages before exploring further.

Custom tools extend what your agent can do. A tool is a function with a name, a description the LLM reads, a schema for inputs, and an execute function that does the actual work.

Basic Tool

Use createTool with a zod schema for the simplest approach:
import { createTool } from "@cline/sdk"
import { z } from "zod"

const getCurrentTime = createTool({
  name: "get_current_time",
  description: "Get the current date and time. Optionally specify a timezone.",
  inputSchema: z.object({
    timezone: z.string().optional().describe("IANA timezone (e.g., 'America/New_York'). Defaults to UTC."),
  }),
  async execute(input) {
    const tz = input.timezone ?? "UTC"
    const date = new Date()
    return {
      iso: date.toISOString(),
      timezone: tz,
      formatted: date.toLocaleString("en-US", { timeZone: tz }),
    }
  },
})
The SDK converts the zod schema to JSON Schema automatically. Input is fully typed in the execute function. For working examples of tools in real agents, see the cli-agent (shell tool with zod) and code-review-bot (multiple tools with completion lifecycle).

Anatomy of a Tool

Every tool has four parts:
FieldPurpose
nameUnique identifier (snake_case recommended)
descriptionLLM reads this to decide when to use the tool
inputSchemaZod schema or JSON Schema defining accepted arguments
executeFunction that does the work

The Name

Use snake_case. Keep it descriptive but concise:
  • search_database, not db or performDatabaseSearchOperation
  • send_email, not email or handleEmailSending

The Description

This is the most important field for tool call accuracy. The LLM uses it to decide when and how to use the tool.
// Weak: model won't know when to use this
description: "Handles data."

// Strong: model knows exactly what this does and when to use it
description: "Search the PostgreSQL database by executing a read-only SQL query. Returns matching rows as JSON. Use this when you need to look up user records, order history, or product data. Maximum 100 rows per query."
Include:
  • What the tool does
  • What it returns
  • When to use it (and when not to)
  • Constraints (rate limits, read-only, max results, etc.)

The Input Schema

With zod, use .describe() on every field:
inputSchema: z.object({
  query: z.string().describe("Search query. Supports wildcards (*) and exact phrases."),
  limit: z.number().min(1).max(100).optional().describe("Maximum results. Default: 10."),
  status: z.enum(["active", "archived", "deleted"]).optional().describe("Filter by record status."),
})
Use z.enum for fields with a fixed set of values. This dramatically improves accuracy.

Using AgentToolContext

The execute function receives a context object with execution metadata:
const longProcess = createTool({
  name: "long_process",
  description: "Process data in batches.",
  inputSchema: z.object({}),
  async execute(_input, context) {
    console.log(`Agent: ${context.agentId}, Iteration: ${context.iteration}`)

    for (const batch of batches) {
      if (context.signal?.aborted) {
        return { status: "cancelled", processed: count }
      }
      await processBatch(batch)
    }

    return { status: "complete" }
  },
})

Error Handling

Return errors as structured data rather than throwing:
const apiTool = createTool({
  name: "call_api",
  description: "Make an authenticated API call.",
  inputSchema: z.object({
    endpoint: z.string().describe("API endpoint path"),
  }),
  async execute(input) {
    try {
      const response = await fetch(`https://api.example.com${input.endpoint}`, {
        headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
      })

      if (!response.ok) {
        return {
          output: { error: `API returned ${response.status}` },
          isError: true,
        }
      }

      return await response.json()
    } catch (err) {
      return {
        output: { error: `Network error: ${(err as Error).message}` },
        isError: true,
      }
    }
  },
})
When a tool returns an error, the agent sees it and can adjust its approach. When a tool throws, it counts as a “mistake” and increments the consecutive mistake counter.

Completion Tools

Mark a tool with lifecycle: { completesRun: true } to make it end the agent loop when called successfully:
const submitResult = createTool({
  name: "submit_result",
  description: "Submit the final result and end the run.",
  inputSchema: z.object({
    summary: z.string(),
    approve: z.boolean(),
  }),
  lifecycle: { completesRun: true },
  async execute(input) {
    return JSON.stringify(input)
  },
})
See the code-review-bot example for this pattern in a complete application.

Testing Tools

Test your tools in isolation before giving them to an agent:
import { describe, it, expect } from "vitest"

describe("get_current_time", () => {
  it("returns ISO timestamp", async () => {
    const result = await getCurrentTime.execute(
      { timezone: "UTC" },
      {
        agentId: "test",
        runId: "run_test",
        iteration: 1,
        toolCallId: "tool_test",
        snapshot: {} as never,
        emitUpdate: () => {},
      }
    )

    expect(result.iso).toMatch(/^\d{4}-\d{2}-\d{2}T/)
  })
})

Registering Tools

await cline.start({
  prompt: "Get the current time",
  config: {
    // ...
    extraTools: [getCurrentTime],
  },
})

Tool Design Rules

Good tools are specific and predictable.
  • Use action-oriented names: get_pull_request, search_database, deploy_service.
  • Describe what the tool does, when to use it, and what it returns.
  • Put constraints in the description: rate limits, read-only behavior, required permissions.
  • Add descriptions for every input property.
  • Return structured JSON instead of prose when possible.
  • Respect context.abortSignal in long-running tools.