Skip to content

Dynamic Tools

The registerDynamicTool() function provides a way to register tools at runtime without using class decorators. This is intended for customer-specific integrations, tools defined in configuration files, or tools backed by external API calls.

Import

import { registerDynamicTool } from "@modernpath/agent-framework";
import type {
  DynamicToolDefinition,
  DynamicToolContext,
  DynamicToolExecute,
} from "@modernpath/agent-framework";

Function Signature

function registerDynamicTool(
  registry: ToolRegistry,
  definition: DynamicToolDefinition
): void
Parameter Type Description
registry ToolRegistry The registry to register the tool into.
definition DynamicToolDefinition The tool definition including name, parameters, and execute function.

DynamicToolDefinition

interface DynamicToolDefinition {
  name: string;
  description: string;
  parameters?: Record<string, ToolParameter>;
  category?: string;
  tags?: string[];
  requiresAuth?: boolean;
  rateLimit?: number;
  version?: string;
  execute: DynamicToolExecute;
}

Properties

Name Type Required Default Description
name string Yes -- Unique tool identifier.
description string Yes -- Human-readable description. Used by LLM planners and tool catalogs.
parameters Record<string, ToolParameter> No undefined Parameter schema for validation and UI introspection. Uses the same ToolParameter type as @Tool.
category string No undefined Category for grouping (e.g. "external", "client").
tags string[] No undefined Freeform tags for filtering.
requiresAuth boolean No undefined Indicates the tool requires authentication.
rateLimit number No undefined Maximum calls per minute (informational).
version string No undefined Tool version string.
execute DynamicToolExecute Yes -- The function that performs the tool's work. Receives cleaned parameters and a DynamicToolContext.

DynamicToolContext

The context object passed as the second argument to the execute function.

type DynamicToolContext = {
  userId: number;
  auditingId: number;
  metadata?: Record<string, any>;
};
Name Type Description
userId number The authenticated user ID, injected by the agent or orchestrator at execution time.
auditingId number The audit trail ID, injected by the agent or orchestrator at execution time.
metadata Record<string, any> Optional metadata passed by the orchestrator (e.g. step number, plan type).

userId and auditingId are injected automatically

When a dynamic tool is executed through an agent (via useTool() or the orchestrator), the framework injects userId and auditingId from the AgentContext. You do not need to pass these manually.


DynamicToolExecute

The function signature for the tool's execution logic.

type DynamicToolExecute = (
  params: Record<string, any>,
  ctx: DynamicToolContext
) => Promise<any>;

The params object has userId, auditingId, and __metadata keys removed before being passed to your function. These values are extracted and provided via the DynamicToolContext instead.


When to Use Dynamic Tools vs @Tool

Criterion @Tool Decorator registerDynamicTool()
Tool logic is known at build time Best choice Works, but more verbose
Tool definitions come from config/DB Impractical Best choice
Tool wraps an external REST API Either works Simpler -- no class needed
Need TypeScript class features (DI, state) Best choice Possible with closures
Orchestrator tool steps Either works Preferred for external integrations

Use dynamic tools for customer-specific integrations

If your deployment needs to register tools based on per-tenant configuration or feature flags, registerDynamicTool() is the right choice. You can read tool definitions from a database or config file and register them at startup.


Code Examples

Basic Dynamic Tool

import { ToolRegistry, registerDynamicTool } from "@modernpath/agent-framework";

const registry = new ToolRegistry();

registerDynamicTool(registry, {
  name: "fetch-stock-price",
  description: "Fetches the current stock price for a given ticker symbol",
  category: "external",
  tags: ["finance", "stocks", "external"],
  parameters: {
    ticker: {
      type: "string",
      description: "Stock ticker symbol (e.g. AAPL, GOOGL)",
      pattern: "^[A-Z]{1,5}$",
    },
    currency: {
      type: "string",
      description: "Currency for the price",
      required: false,
      default: "USD",
      enum: ["USD", "EUR", "GBP"],
    },
  },
  execute: async (params, ctx) => {
    console.log(`User ${ctx.userId} requesting price for ${params.ticker}`);

    const response = await fetch(
      `https://api.stockdata.example/v1/price?ticker=${params.ticker}&currency=${params.currency || "USD"}`,
    );
    const data = await response.json();

    return {
      ticker: params.ticker,
      price: data.price,
      currency: params.currency || "USD",
      timestamp: new Date().toISOString(),
    };
  },
});

// The tool is now available in the registry
console.log(registry.has("fetch-stock-price")); // true

Registering Multiple Dynamic Tools from Configuration

import { ToolRegistry, registerDynamicTool } from "@modernpath/agent-framework";
import type { DynamicToolDefinition } from "@modernpath/agent-framework";

interface ToolConfig {
  name: string;
  description: string;
  endpoint: string;
  method: "GET" | "POST";
  parameters: Record<string, any>;
}

// Tool definitions loaded from a config file or database
const toolConfigs: ToolConfig[] = [
  {
    name: "crm-lookup",
    description: "Look up a customer in the CRM system",
    endpoint: "https://crm.example.com/api/customers",
    method: "GET",
    parameters: {
      customerId: { type: "string", description: "Customer ID" },
    },
  },
  {
    name: "ticket-create",
    description: "Create a support ticket",
    endpoint: "https://support.example.com/api/tickets",
    method: "POST",
    parameters: {
      title: { type: "string", description: "Ticket title" },
      body: { type: "string", description: "Ticket description" },
      priority: {
        type: "string",
        description: "Priority level",
        enum: ["low", "medium", "high", "critical"],
      },
    },
  },
];

const registry = new ToolRegistry();

for (const config of toolConfigs) {
  registerDynamicTool(registry, {
    name: config.name,
    description: config.description,
    category: "external",
    tags: ["client", "external"],
    parameters: config.parameters,
    execute: async (params, ctx) => {
      const url = new URL(config.endpoint);
      const options: RequestInit = {
        method: config.method,
        headers: {
          "Content-Type": "application/json",
          "X-User-Id": String(ctx.userId),
        },
      };

      if (config.method === "GET") {
        Object.entries(params).forEach(([k, v]) =>
          url.searchParams.set(k, String(v)),
        );
      } else {
        options.body = JSON.stringify(params);
      }

      const response = await fetch(url, options);
      return response.json();
    },
  });
}

console.log(registry.list());
// => ["crm-lookup", "ticket-create"]

Using Dynamic Tools with the Orchestrator

Dynamic tools registered with the "external" category or "external" / "client" tags are automatically exposed to the OrchestratorAgent planner when exposeToolsToPlanner is enabled:

import { ToolRegistry, registerDynamicTool } from "@modernpath/agent-framework";
import { OrchestratorAgent } from "@modernpath/agent-framework";

const registry = new ToolRegistry();

// Register a dynamic tool tagged as external
registerDynamicTool(registry, {
  name: "send-notification",
  description: "Send a notification to a user via email or SMS",
  category: "external",
  tags: ["notification", "external"],
  parameters: {
    channel: { type: "string", description: "Channel", enum: ["email", "sms"] },
    recipient: { type: "string", description: "Email or phone number" },
    message: { type: "string", description: "Notification body" },
  },
  execute: async (params, ctx) => {
    // Send the notification...
    return { sent: true, channel: params.channel };
  },
});

// The orchestrator can now include "send-notification" in its plans
const orchestrator = new OrchestratorAgent(
  registry,
  geminiClient,
  prompts,
  { documentAnalysis, qaChat },
  {
    exposeToolsToPlanner: true,   // (1)!
    toolCatalogMode: "externalOnly",
  },
);
  1. With exposeToolsToPlanner: true, the orchestrator includes tool names and schemas in the planning prompt, allowing the LLM to generate plan steps that invoke tools directly.