Functions as tools
Reuse a function as an AI tool without duplicating its schemas or business logic.
Turn an existing function into a tool with .asTool() so an agent or MCP client can call it. Keep the business logic in the function and add a description and execution policy for AI callers.
Define the function
This small function returns a greeting. In a real app, the function might look up a product or fetch an order the current user is allowed to see.
/** 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;Turn it into a tool
Import the function into a tool file and call .asTool():
/** Exposes the hello function as a read-only greeting tool with its existing two-second timeout. */
import hello from "@app/services/hello/functions/hello.function.js";
export default hello.asTool({
id: "hello.lookup",
description: "Read a greeting for a supplied name",
sideEffect: "read",
approval: "never",
timeoutMs: 2_000,
});The tool accepts the same input and returns the same output as hello. The function remains callable by the rest of your app.
Use a normal defineFunction here. Event-only functions created with defineEventFunction cannot become tools.
Use the tool
Add lookup to an agent’s tools, or call hello.lookup from an MCP client. Both use the same greeting function.
.asTool() and defineTool({ target: hello, ... }) are alternatives; you do not need both. Both expose the tool to MCP by default. Set mcp: false if the tool should be available only inside your app.