Skip to Content
AutomationsCustom toolsCustom tools

Custom tools

A custom tool lets AI call your application code. Tools live in your repository next to flexkit.config.ts, run on your own server, and reach Flexkit through a signed HTTPS endpoint. They do not depend on where the Studio runs: the same tools work with a self-hosted Studio, the hosted Studio, or the desktop app.

Define tools

Install @flexkit/tools and zod in the application that will serve the tools. Create a flexkit.tools.ts file next to flexkit.config.ts. This example converts a price between currencies with the European Central Bank reference rates published by frankfurter.dev , a public API that needs no key, so you can run it as written:

TypeScript
flexkit.tools.ts
import { defineTool, defineTools } from '@flexkit/tools';
import { z } from 'zod';
 
const currencyCode = z.string().regex(/^[A-Za-z]{3}$/, 'Use a three-letter ISO 4217 code such as EUR.');
 
export const convertCurrency = defineTool({
  name: 'convertCurrency',
  description:
    'Convert an amount between currencies using the latest European Central Bank reference rate. Returns the rate and its date so the result can be cited.',
  input: z.object({
    amount: z.number().positive().describe('Amount in the source currency.'),
    from: currencyCode.describe('Source currency, e.g. USD.'),
    to: currencyCode.describe('Target currency, e.g. EUR.'),
  }),
  execute: async ({ amount, from, to }) => {
    const base = from.toUpperCase();
    const target = to.toUpperCase();
 
    if (base === target) {
      return { amount, from: base, to: target, rate: 1, converted: amount, date: null };
    }
 
    const url = new URL('https://api.frankfurter.dev/v1/latest');
    url.searchParams.set('base', base);
    url.searchParams.set('symbols', target);
 
    const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
 
    if (!response.ok) {
      throw new Error(`Rate lookup failed for ${base}→${target} (${String(response.status)}). Check both currency codes.`);
    }
 
    const { date, rates } = (await response.json()) as { date: string; rates: Record<string, number> };
    const rate = rates[target];
 
    if (typeof rate !== 'number') {
      throw new Error(`No ${target} rate is published for ${base}.`);
    }
 
    return {
      amount,
      from: base,
      to: target,
      rate,
      converted: Math.round(amount * rate * 100) / 100,
      date,
      source: 'European Central Bank via frankfurter.dev',
    };
  },
});
 
export default defineTools({
  tools: [convertCurrency],
  skills: [],
});

defineTools is the file’s default export: it checks that tool and skill names are unique and is what flexkit dev loads. Input is validated against the Zod schema before execute runs, so the handler never sees a negative amount or a malformed code. The tool returns the rate and the date the API reported, which lets the agent cite them instead of inventing a figure, and a thrown error becomes the error message the agent sees.

execute also receives the actor as its second argument, a Chat user (userId, role, spaceCodes) or an automation (automationId, spaceId, spaceCodes). This tool is read-only and public, so it ignores it; a tool that writes to another system should authorize on it first. For APIs that need a key, read it from process.env inside execute: flexkit dev loads .env.local, and your hosting provides it in production.

Code skills go in the same file; see Define skills in code.

Serve the tools

createToolsHandler turns the definition into a standard (request: Request) => Promise<Response> function. Mount it on any HTTPS endpoint of your application. In Next.js:

TypeScript
app/api/flexkit/tools/route.ts
import { createToolsHandler } from '@flexkit/tools';
import tools from '../../../../flexkit.tools';
 
export const runtime = 'nodejs';
 
const handler = createToolsHandler(tools);
 
export { handler as GET, handler as POST };

The handler reads FLEXKIT_TOOLS_SECRET (and FLEXKIT_TOOLS_SECRET_PREVIOUS during a rotation) from the environment, or you can pass { secret, previousSecret } as the second argument. It serves the signed catalog on GET and executes a tool on POST. Astro endpoints, TanStack Start server routes, Hono, Vercel Functions and Cloudflare Workers can mount the same function because it only uses Web APIs. The Studio framework adapters are not involved.

Deploy and sync

  1. Set FLEXKIT_TOOLS_SECRET in the server environment using the project’s signing secret from Dashboard. Never put it in a public environment variable or client configuration.
  2. Deploy the application so the endpoint is reachable at a public HTTPS URL.
  3. Register the URL and read the catalog, either from Dashboard → project API → Custom tools (set the production tools URL, then Sync tools) or from your terminal or CI:
flexkit tools sync --origin https://app.example.com/api/flexkit/tools

Omit --origin on later runs to re-read the stored URL, for example after every deploy. See flexkit tools.

  1. Confirm convertCurrency appears in the production catalog.
  2. Attach it to an automation and ask the automation to quote a product price in another currency.
  3. Inspect the run’s tool output: the rate and date should match what frankfurter.dev returns for the same pair.

A browser opening the signed endpoint without a signature is not a valid discovery test. A catalog entry also does not attach the tool to an automation.

Local development

Run the local runtime from the folder that holds flexkit.config.ts and flexkit.tools.ts:

flexkit dev

flexkit dev loads your tools file, loads .env.local and .env when present, registers itself with Flexkit as your local runtime, and executes tool calls on your machine. While it is connected, Chat turns and manual automation runs started by you use the local tools instead of the production URL. Scheduled runs, webhooks, entity triggers and other members keep using the production catalog. Edits to the tools file are picked up automatically.

The Dashboard shows the connection under Your local runtime, together with the tools it advertises. flexkit dev needs a signed-in owner or developer session (flexkit login); API tokens cannot register a local runtime. See flexkit dev.

Before adding external side effects, read security and deployment and the tools reference.

Last updated on