Yak Docs
Tool Adapters

Custom Adapters

For databases, custom services, or any logic not covered by schema-based adapters, you can build custom tool adapters.

Adapter Interface

A tool adapter implements the ToolSource interface:

import type { ToolSource, ToolDefinition } from "@yak-io/javascript/server";

const myAdapter: ToolSource = {
  id: "my-adapter",
  
  // Return available tool definitions
  getTools: async () => [
    {
      name: "my-adapter.action",
      description: "Performs an action",
      input_schema: {
        type: "object",
        properties: {
          param: { type: "string" },
        },
        required: ["param"],
      },
    },
  ],
  
  // Execute a tool call
  executeTool: async (name: string, args: Record<string, unknown>) => {
    if (name === "my-adapter.action") {
      return { result: `Executed with ${args.param}` };
    }
    throw new Error(`Unknown tool: ${name}`);
  },
};

Database Adapter Example

import type { ToolSource } from "@yak-io/javascript/server";
import { db } from "./database";

const databaseTools: ToolSource = {
  id: "database",
  
  getTools: async () => [
    {
      name: "db.searchOrders",
      description: "Search orders by customer email or status",
      input_schema: {
        type: "object",
        properties: {
          email: { type: "string" },
          status: { 
            type: "string", 
            enum: ["pending", "shipped", "delivered"] 
          },
          limit: { type: "number", default: 10 },
        },
      },
    },
    {
      name: "db.getOrderDetails",
      description: "Get full details of an order",
      input_schema: {
        type: "object",
        properties: {
          orderId: { type: "string" },
        },
        required: ["orderId"],
      },
    },
  ],
  
  executeTool: async (name, args) => {
    switch (name) {
      case "db.searchOrders":
        return db.orders.findMany({
          where: {
            ...(args.email && { customerEmail: args.email }),
            ...(args.status && { status: args.status }),
          },
          take: args.limit ?? 10,
        });
      
      case "db.getOrderDetails":
        return db.orders.findUnique({
          where: { id: args.orderId },
          include: { items: true, customer: true },
        });
      
      default:
        throw new Error(`Unknown tool: ${name}`);
    }
  },
};

Using Custom Adapters

Pass adapters to your handler:

import { createNextYakHandler } from "@yak-io/nextjs/server";

export const { GET, POST } = createNextYakHandler({
  tools: [databaseTools, otherAdapter],
});

Combining with Schema-Based Tools

export default function App() {
  return (
    <YakProvider
      getConfig={async () => ({
        // Schema-based tools
        schemaSources: [
          { name: "externalApi", type: "openapi", spec: externalSpec },
        ],
        // Explicit tools from adapters
        tools: await databaseTools.getTools(),
      })}
      // Handle schema-generated requests
      onRESTSchemaCall={async (schemaName, request) => {
        // Execute REST request...
      }}
      // Handle explicit tool calls
      onToolCall={async (name, args) => {
        if (name.startsWith("db.")) {
          return databaseTools.executeTool(name, args);
        }
        throw new Error(`Unknown tool: ${name}`);
      }}
    >
      <YakWidget />
    </YakProvider>
  );
}

Best Practices

Use Clear Naming

Prefix tool names with adapter ID:

{
  name: "db.searchOrders",      // Clear it's from database adapter
  name: "payments.refund",       // Clear it's from payments adapter
}

Write Descriptive Descriptions

Help the AI understand when to use each tool:

{
  name: "db.searchOrders",
  description: "Search orders by customer email, status, or date range. Returns a list of order summaries.",
}

Validate Inputs

Even with JSON Schema validation, add runtime checks:

executeTool: async (name, args) => {
  if (name === "db.getOrderDetails") {
    if (!args.orderId || typeof args.orderId !== "string") {
      throw new Error("orderId is required and must be a string");
    }
    // Continue...
  }
}

Handle Errors Gracefully

Return meaningful errors:

executeTool: async (name, args) => {
  try {
    return await db.orders.findUnique({ where: { id: args.orderId } });
  } catch (error) {
    if (error.code === "NOT_FOUND") {
      return { error: `Order ${args.orderId} not found` };
    }
    throw error;
  }
}

Add Authorization

Check user permissions:

executeTool: async (name, args, context) => {
  const user = await getUser(context.request);
  
  if (name === "db.searchOrders") {
    // Only allow users to search their own orders
    return db.orders.findMany({
      where: {
        userId: user.id,
        ...otherFilters,
      },
    });
  }
}

Always validate that the current user should be able to perform the requested operation.

On this page