Skip to content
RELKIT
Esc
↑↓navigate↵open⌘Jpreview
On this page

Define a tool

Let a model call an existing function with a clear description and execution policy.

A tool names an action an AI client can request, such as looking up an order or cancelling it. Use defineTool to name the action, explain when to use it, and point it at an existing function.

Function-backed tools use the lean app’s @relkit/app authoring facades; they need no native model SDK. Choose native agent installation only for model execution, or MCP installation only for that external protocol.

Scaffold a function-backed tool

From an installed minimal app with its hello service and function:

bun run relkit add tool greeting-lookup --service hello \
  --target hello --side-effect read --approval never

Creates src/services/hello/tools/greeting-lookup.tool.ts using the function’s .asTool() method. Select a callable function in the same service; the CLI accepts its ID, binding, or filename. The default side-effect class is read, and approval defaults to never. Choose accurate policies before exposing a real operation. The tool remains internal to the domain, and adding it does not automatically add it to an existing agent.

To scaffold the function and tool together, use --create-function instead of --target:

bun run relkit add tool find-order --service hello --create-function find-order

This creates src/services/hello/functions/find-order.function.ts and src/services/hello/tools/find-order.tool.ts, and exposes the new function through the service. Without either option, the interactive prompt lists existing functions and offers Create a new function. When no functions exist, it asks for a new function name directly.

For every tool scaffolding flag, see the tool command reference.

Define the input and output

Declare input and output on the function the tool will call:

/** Returns a validated named greeting and logs each invocation through RELKIT's request context. */
import { defineFunction } from "@relkit/app/functions";
import { z } from "@relkit/app/schema";

const hello = defineFunction({
  id: "hello.greet",

  input: z.object({ name: z.string().min(1).default("world") }),
  output: z.object({ message: z.string() }),

  /**
   * Logs the greeting invocation and returns the validated greeting response.
   * @param input - Validated name from the function schema.
   * @param context - RELKIT invocation context owning structured logging.
   * @returns Greeting response matching the output schema.
   */
  handler: async ({ name }, context) => {
    context.log.info("hello invoked", { name });

    return { message: `Hello, ${name}!` };
  },
});

export default hello;

Here, the tool will accept { "name": "Ada" } and return { "message": "Hello, Ada!" }. Input defaults and validation come from the same function schemas.

Define the tool

This tool calls the greeting function. Save it under your domain’s tools directory:

/** Checks lookup.tool.ts through the supported public RELKIT APIs. */
import { defineTool } from "@relkit/app/tools";
import hello from "@app/services/hello/functions/hello.function.js";

export default defineTool({
  id: "hello.lookup",
  target: hello,
  description: "Read a greeting for a supplied name",

  sideEffect: "read",
  approval: "never",
  timeoutMs: 2_000,
});

defineTool has no separate input or output options: it inherits both from target. You do not repeat the schemas in the tool definition. To expose a different input or output, define a function with those schemas and use it as the target.

Native LangChain tool() values can appear directly in an agent’s tools array. Use defineTool or .asTool() when the action should run through a RELKIT function or needs RELKIT side-effect, approval, timeout, or MCP metadata. Do not wrap native tools in a public adapter. Use the RELKIT tool reference for the supported policy fields.

Give the tool a stable id and a specific description: explain what it does, when to use it, and what it returns. “Look up an order by ID” is more useful than “Handle orders.”

If you prefer to start from the function, hello.asTool(...) creates the same kind of tool. Choose one form for each tool.

Describe the action accurately

sideEffect Use it for
none A calculation that does not read or change external state.
read Reading data without changing it.
write Changing application data.
external An external action, such as sending a message.

approval is also required. Use never for an operation that may run without a separate decision, on-write to require approval for write and external, or always to require approval on every call. See tool approvals before exposing sensitive actions.

The approval option on defineTool is a policy string, not a function. For a custom decision, pass a synchronous or asynchronous callback to tool.invoke(input, { approval }).

Set timeoutMs to limit how long the tool can run. It is optional in the tool API, but the current MCP runtime requires an explicit value. Check permissions inside the target function even for read-only tools.

Make the tool available

Add the tool to an agent’s tools array to let that agent call it.

Tools are also available through MCP by default. Set mcp: false in the tool options to keep a tool out of MCP; it can still be used by your RELKIT agents.