Docs
Adding a Tool

Adding a Tool

How to create a new tool with a Zod schema and register it with agents.

Tool Anatomy

// lib/ai/tools/shared/my-tool.ts
import { tool } from "ai";
import { z } from "zod";
 
export const myTool = tool({
  description: "What this tool does — the LLM reads this to decide when to call it",
  inputSchema: z.object({
    query: z.string().describe("The search query to look up"),
    maxResults: z.number().optional().describe("Maximum number of results, default 5"),
  }),
  execute: async ({ query, maxResults = 5 }) => {
    // Always return an object
    // Never throw — return { error: "..." } on failure
    try {
      const results = await someApiCall(query, maxResults);
      return { results };
    } catch (err) {
      return { error: "Failed to fetch results", details: String(err) };
    }
  },
});

Steps

Create the tool file

Location: lib/ai/tools/shared/your-tool-name.ts

Use kebab-case for the filename. Export the tool with a descriptive camelCase name.

Define the Zod schema

Every field must have a .describe() string. The LLM uses these descriptions to fill in parameters when calling the tool. Without descriptions, the model guesses — and guesses wrong.

inputSchema: z.object({
  topic: z.string().describe("The main topic or subject to research"),
  platform: z.enum(["LinkedIn", "Twitter", "Instagram", "Blog"])
    .describe("The target social media platform"),
  tone: z.string().optional()
    .describe("Writing tone: professional, casual, humorous. Default: professional"),
}),

Implement execute

  • Return a plain object — never throw
  • Return { error: "..." } on failure so the agent can surface the error in its response
  • If the output should be saved as an asset, structure it consistently (the ASSET_REGISTRY builder will read it)

Add to the agent's tools map

In the agent file that should have access to this tool:

const agent = new ToolLoopAgent({
  tools: {
    web_search: tavilySearch(),
    my_tool: myTool,     // add here — key is the tool name the LLM uses
  },
});

The key in the tools object is the name the LLM calls when it uses the tool.

Register in ASSET_REGISTRY (if asset-producing)

In lib/ai/utils.ts, add a builder under the tool's key name:

my_tool: ({ input, output }) => {
  const value = (output as any)?.value ?? output;
  if (!value?.result) return null;
  return {
    type: "my_asset_type",
    title: String(input.topic ?? "Output").slice(0, 100),
    content: String(value.result),
    metadata: { platform: input.platform },
  };
},

Factory Tools

If your tool needs team context (for namespaced storage, team-scoped DB queries, etc.), export a factory function:

export function buildMyTool(teamId: string) {
  return tool({
    description: "...",
    inputSchema: z.object({ ... }),
    execute: async (input) => {
      // teamId is available via closure
      await db.insert(someTable).values({ teamId, ...input });
      return { success: true };
    },
  });
}
 
// In the agent:
tools: {
  my_tool: buildMyTool(team.id),
}