Deco
Full-Code Guides

Building Tools

Build tools in TypeScript with full type safety

Tools are typed functions that agents, workflows, and views can call. This guide shows you how to build tools using TypeScript for maximum control and extensibility.

Not a developer? See Creating Tools to create tools via chat.

Anatomy of a Tool

Every tool requires four components:

  1. ID - Unique identifier following RESOURCE_ACTION pattern (e.g., EMAIL_SEND , CUSTOMER_FETCH )
  2. Description - Tells agents when and how to use the tool
  3. Schemas - Zod schemas for input validation and output typing
  4. Execute - The implementation logic

Basic Example

 import { createTool } from "@deco/workers-runtime";
import { z } from "zod";

const createEmailTool = (env: Env) =>
  createTool({
    id: "EMAIL_SEND",
    description: "Send an email via Gmail. Use for notifications and communications.",
    inputSchema: z.object({
      to: z.string().email(),
      subject: z.string(),
      body: z.string(),
    }),
    outputSchema: z.object({ 
      success: z.boolean(),
      messageId: z.string().optional(),
    }),
    execute: async ({ context }) => {
      const response = await env["gmail"].SEND_EMAIL({
        to: context.to,
        subject: context.subject,
        body: context.body,
      });
      return { 
        success: true,
        messageId: response.id,
      };
    },
  });  

Key points:

  • Use context to access validated input
  • Return data matching your outputSchema
  • Access integrations via env["integration-id"] where integration-id is the app’s ID from your workspace

Common Patterns

External API Calls

 const createWeatherTool = (env: Env) =>
  createTool({
    id: "WEATHER_FETCH",
    description: "Get current weather for a city using OpenWeather API",
    inputSchema: z.object({
      city: z.string().min(1),
    }),
    outputSchema: z.object({
      temperature: z.number(),
      condition: z.string(),
      humidity: z.number().optional(),
    }),
    execute: async ({ context }) => {
      const response = await fetch(
        `https://api.openweathermap.org/data/2.5/weather?q=${context.city}&appid=${env.OPENWEATHER_API_KEY}`
      );
      
      if (!response.ok) {
        throw new Error(`Weather API error: ${response.statusText}`);
      }
      
      const data = await response.json();
      return {
        temperature: data.main.temp,
        condition: data.weather[0].description,
        humidity: data.main.humidity,
      };
    },
  }); 

Using Integrations

Call installed integrations through the environment:

 const createNotificationTool = (env: Env) =>
  createTool({
    id: "SLACK_NOTIFY",
    description: "Send a notification to a Slack channel",
    inputSchema: z.object({
      channel: z.string(),
      message: z.string(),
    }),
    outputSchema: z.object({ 
      sent: z.boolean(),
      timestamp: z.string(),
    }),
    execute: async ({ context }) => {
      const result = await env["slack"].POST_MESSAGE({
        channel: context.channel,
        text: context.message,
      });
      return {
        sent: true,
        timestamp: result.ts,
      };
    },
  }); 

Install integrations from Apps in your workspace. Each integration’s tools are available at env["integration-id"] where the ID is shown in the Apps section (e.g., env["gmail"] , env["slack"] ).

Database Operations

Every workspace includes built-in SQLite with Drizzle ORM:

 import { getDb } from "./db";
import { customers } from "./schema";

const createCustomerTool = (env: Env) =>
  createTool({
    id: "CUSTOMER_CREATE",
    description: "Create a new customer in the database",
    inputSchema: z.object({
      name: z.string().min(1),
      email: z.string().email(),
      city: z.string(),
      state: z.string().length(2),
    }),
    outputSchema: z.object({ 
      id: z.number(),
      createdAt: z.date(),
    }),
    execute: async ({ context }) => {
      const db = await getDb(env);
      const [result] = await db
        .insert(customers)
        .values({
          name: context.name,
          email: context.email,
          city: context.city,
          state: context.state,
        })
        .returning();

      return { 
        id: result.id,
        createdAt: result.createdAt,
      };
    },
  }); 

Each project gets isolated SQLite storage on Cloudflare’s D1. No credentials needed.

Registering Tools

Add tools to server/main.ts :

 import { withRuntime } from "@deco/workers-runtime";
import { createEmailTool } from "./tools/email";
import { createCustomerTool } from "./tools/customers";

const { Workflow, ...runtime } = withRuntime<Env>({
  tools: [
    createEmailTool,
    createCustomerTool,
    // Add more tools here
  ],
  workflows: [],
  views: [],
});

export { Workflow };
export default runtime; 

After registration, run npm run gen:self to generate types for your frontend.

Testing Tools

Start your dev server and test in two ways:

1. Via Admin UI:

  1. npm run dev
  2. Copy preview URL
  3. Admin β†’ Apps β†’ Add Integration (paste URL + /mcp )
  4. Test tools through the auto-generated interface

2. Via Code:

 // In your React component
import { client } from "./lib/rpc";

const result = await client.tools.EMAIL_SEND({
  to: "user@example.com",
  subject: "Test",
  body: "Hello!",
}); 

Tools are immediately available via typed RPC, no API layer needed.

Best Practices

Single Responsibility

 βœ… EMAIL_SEND, CUSTOMER_CREATE, INVOICE_GENERATE
❌ CUSTOMER_CREATE_AND_EMAIL_AND_NOTIFY 

Descriptive Names - Follow RESOURCE_ACTION pattern:

 βœ… ORDER_TOTAL_CALCULATE, LEAD_QUALIFY
❌ calculateTotal, qualify_lead 

Strong Typing - Zod schemas provide runtime validation and compile-time types:

 inputSchema: z.object({
  email: z.string().email(),        // Email validation
  amount: z.number().positive(),    // Must be > 0
  tier: z.enum(["A", "B", "C"]),   // Only these values
}) 

Error Handling

 execute: async ({ context }) => {
  try {
    const result = await api.call(context);
    return result;
  } catch (error) {
    throw new Error(`Failed to process: ${error.message}`);
  }
} 

Organizing Tools

Group related tools in files:

 server/tools/
β”œβ”€β”€ index.ts          # Export all tools
β”œβ”€β”€ customers.ts      # CUSTOMER_CREATE, CUSTOMER_FETCH, etc.
β”œβ”€β”€ emails.ts         # EMAIL_SEND, EMAIL_VALIDATE
└── billing.ts        # INVOICE_CREATE, PAYMENT_PROCESS 

Export from index.ts :

 export { createCustomerTool, createCustomerFetchTool } from "./customers";
export { createEmailTool } from "./emails";
export { createInvoiceTool } from "./billing"; 

Found an error or want to improve this page?

Edit this page