Back to Copilotkit

Server Tools

showcase/shell-docs/src/content/docs/integrations/built-in-agent/server-tools.mdx

1.57.04.8 KB
Original Source

What are Server Tools?

Server tools are functions that run on your backend that the Built-in Agent can invoke. They're defined using defineTool() with Zod schemas for type-safe parameters.

When should I use this?

  • Your agent needs to access databases, APIs, or other backend services
  • You want type-safe tool parameters with validation
  • The tool logic requires server-side secrets or resources

Defining a tool

typescript
import { BuiltInAgent, defineTool } from "@copilotkit/runtime/v2";
import { z } from "zod";

const getWeather = defineTool({
  name: "getWeather",
  description: "Get the current weather for a location",
  parameters: z.object({
    location: z.string().describe("The location's name"),
  }),
  execute: async ({ location }) => {
    // Your implementation here
    return { temperature: 72, condition: "sunny", location };
  },
});

const builtInAgent = new BuiltInAgent({
  model: "openai:gpt-5.4-mini",
  tools: [getWeather],
  maxSteps: 2                   //Important for tool calls
});

Tool response

Tools can return any JSON-serializable value. The agent uses the response to continue the conversation.

Multiple tools

Pass an array of tools — the agent chooses which to call based on the user's request:

typescript
const searchDocs = defineTool({
  name: "searchDocs",
  description: "Search the documentation for relevant articles",
  parameters: z.object({
    query: z.string().describe("The search query"),
  }),
  execute: async ({ query }) => {
    const results = await search(query);
    return { results, count: results.length };
  },
});

const createTicket = defineTool({
  name: "createTicket",
  description: "Create a support ticket",
  parameters: z.object({
    title: z.string().describe("Ticket title"),
    priority: z.enum(["low", "medium", "high"]).describe("Ticket priority"),
    description: z.string().describe("Detailed description of the issue"),
  }),
  execute: async ({ title, priority, description }) => {
    const ticket = await db.tickets.create({ title, priority, description });
    return { ticketId: ticket.id, status: "created" };
  },
});

const builtInAgent = new BuiltInAgent({
  model: "openai:gpt-5.4-mini",
  tools: [searchDocs, createTicket], // [!code highlight]
  maxSteps: 2                        
});

Complex Zod schemas

Use nested objects, arrays, enums, and optional fields for sophisticated tool parameters:

typescript
const bookFlight = defineTool({
  name: "bookFlight",
  description: "Search for and book flights",
  parameters: z.object({
    trip: z.object({
      origin: z.string().describe("Origin airport code (e.g., SFO)"),
      destination: z.string().describe("Destination airport code (e.g., JFK)"),
      date: z.string().describe("Departure date in YYYY-MM-DD format"),
    }),
    passengers: z.array(
      z.object({
        name: z.string(),
        seatPreference: z.enum(["window", "middle", "aisle"]).optional(),
      })
    ).describe("List of passengers"),
    class: z.enum(["economy", "business", "first"]).default("economy"),
  }),
  execute: async ({ trip, passengers, class: seatClass }) => {
    const flights = await searchFlights(trip, seatClass);
    return { flights, passengerCount: passengers.length };
  },
});

Error handling

Throw errors or return error objects from your tool — the agent will see the error and can inform the user or try a different approach:

typescript
const getUser = defineTool({
  name: "getUser",
  description: "Look up a user by email",
  parameters: z.object({
    email: z.string().email().describe("The user's email address"),
  }),
  execute: async ({ email }) => {
    const user = await db.users.findByEmail(email);
    if (!user) {
      throw new Error(`No user found with email: ${email}`); // [!code highlight]
    }
    return { id: user.id, name: user.name, role: user.role };
  },
});

Multi-step tool calling

By default, the agent performs a single step. If your agent needs to chain tool calls (e.g., search first, then create a ticket), set maxSteps:

typescript
const builtInAgent = new BuiltInAgent({
  model: "openai:gpt-5.4-mini",
  maxSteps: 5, // [!code highlight]
  tools: [searchDocs, createTicket, getUser],
});

With maxSteps: 5, the agent can:

  1. Call searchDocs to find relevant info
  2. Process the result
  3. Call createTicket with details from the search
  4. Continue until done (up to 5 iterations)
<Callout type="info"> See [Advanced Configuration](/built-in-agent/advanced-configuration) for more options like `toolChoice`, `temperature`, and `providerOptions`. </Callout> <Callout type="info" title="Using a custom backend?"> If you're using the [Custom Agent](/built-in-agent/custom-agent) instead of `BuiltInAgent`, see the "With Tools" examples for how to wire tools with AI SDK, TanStack AI, or custom backends. </Callout>