> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt
> Use this file to discover all available pages before exploring further.

# Dynamic Tools Example

> Build a chat agent where tools are defined dynamically by the client at runtime - the SDK/platform pattern

Demonstrates dynamic client-defined tools - the **SDK/platform pattern** where tools are registered at runtime by the embedding application, not known by the server at deploy time.

## What it demonstrates

* **`createToolsFromClientSchemas()`** - Convert client-provided schemas into AI SDK tools
* **Dynamic tool registration** - Tools defined at runtime by the client
* **SDK/platform architecture** - Generic server infrastructure with client-specific tools
* **WebSocket protocol integration** - Tool schemas automatically sent via WebSocket

## The Pattern

This example shows how to build a chat agent where:

1. The **server** is generic infrastructure - it accepts whatever tools the client sends
2. The **client** defines tools dynamically (schemas + execute functions)
3. Tool schemas are automatically sent to the server via the WebSocket protocol
4. The LLM calls the tools, and results are routed back to the client for execution

This is the same architecture you would use when building an **SDK or platform** where third-party developers define tools in their embedding application.

## Server Implementation

The server uses `createToolsFromClientSchemas()` to convert client-provided schemas into AI SDK tools:

```typescript src/server.ts theme={null}
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";
import { AIChatAgent, createToolsFromClientSchemas } from "@cloudflare/ai-chat";
import {
  streamText,
  convertToModelMessages,
  pruneMessages,
  stepCountIs
} from "ai";

export class DynamicToolsAgent extends AIChatAgent {
  async onChatMessage(
    _onFinish: Parameters<AIChatAgent["onChatMessage"]>[0],
    options: Parameters<AIChatAgent["onChatMessage"]>[1]
  ) {
    const workersai = createWorkersAI({ binding: this.env.AI });

    const result = streamText({
      model: workersai("@cf/zai-org/glm-4.7-flash"),
      system:
        "You are a helpful assistant. You have access to tools provided by " +
        "the embedding application. Use them when asked. If no tools are " +
        "available, let the user know they can enable tools in the sidebar.",
      messages: pruneMessages({
        messages: await convertToModelMessages(this.messages),
        toolCalls: "before-last-2-messages",
        reasoning: "before-last-message"
      }),
      // Dynamic tools from client - server doesn't know these at deploy time
      tools: createToolsFromClientSchemas(options?.clientTools),
      stopWhen: stepCountIs(5)
    });

    return result.toUIMessageStreamResponse();
  }
}

export default {
  async fetch(request: Request, env: Env) {
    return (
      (await routeAgentRequest(request, env)) ||
      new Response("Not found", { status: 404 })
    );
  }
} satisfies ExportedHandler<Env>;
```

## Client Implementation

The client passes tools via the `tools` option on `useAgentChat`:

```typescript src/client.tsx theme={null}
import { useAgentChat, type AITool } from "@cloudflare/ai-chat/react";
import { useAgent } from "agents/react";

function ChatApp() {
  const agent = useAgent({
    agent: "DynamicToolsAgent",
    name: sessionId
  });

  // Define tools dynamically - these are sent to the server
  const tools: Record<string, AITool> = {
    getPageTitle: {
      description: "Get the current page title",
      parameters: {
        type: "object",
        properties: {}
      },
      execute: async () => {
        return { title: document.title };
      }
    },
    getUserLocation: {
      description: "Get the user's approximate location from browser",
      parameters: {
        type: "object",
        properties: {}
      },
      execute: async () => {
        return new Promise((resolve) => {
          navigator.geolocation.getCurrentPosition(
            (pos) => resolve({
              latitude: pos.coords.latitude,
              longitude: pos.coords.longitude
            }),
            (err) => resolve({ error: err.message })
          );
        });
      }
    },
    getBrowserInfo: {
      description: "Get information about the user's browser",
      parameters: {
        type: "object",
        properties: {}
      },
      execute: async () => {
        return {
          userAgent: navigator.userAgent,
          language: navigator.language,
          platform: navigator.platform,
          cookiesEnabled: navigator.cookieEnabled
        };
      }
    }
  };

  const { messages, sendMessage } = useAgentChat({
    agent,
    tools  // Tools are automatically sent to the server
  });

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>
          {msg.content}
        </div>
      ))}
      <input onSubmit={(e) => sendMessage(e.target.value)} />
    </div>
  );
}
```

## How It Works

<Steps>
  <Step title="Client defines tools">
    The client application defines tools with JSON Schema parameters and execute functions. These tools are specific to the embedding application.
  </Step>

  <Step title="Schemas sent to server">
    When the WebSocket connection is established, tool schemas are automatically sent to the server via the `@cloudflare/ai-chat` protocol.
  </Step>

  <Step title="Server converts schemas">
    `createToolsFromClientSchemas()` converts the JSON Schema into AI SDK tools that the LLM can call.
  </Step>

  <Step title="LLM calls tools">
    The LLM can now call the client-defined tools. Tool calls are sent back to the client for execution.
  </Step>

  <Step title="Client executes and returns results">
    The client's `execute` function runs, and the result is sent back to the server to continue the LLM conversation.
  </Step>
</Steps>

## When to Use This Pattern

### Use Dynamic Client Tools When:

* **Building an SDK or platform** where third-party developers define tools
* **Multi-tenant systems** where each tenant has different tools
* **Plugin architectures** where tools are registered at runtime
* **Rapid prototyping** where tools change frequently

### Use Server-side Tools When:

* **Most applications** - simpler code, full type safety
* **Security-sensitive operations** - execute on the server
* **Shared tools** - all clients use the same tool set
* **TypeScript integration** - Zod schemas with full type inference

## Comparison: Dynamic vs Server-side Tools

<CodeGroup>
  ```typescript Dynamic (Client-defined) theme={null}
  import { useAgentChat, type AITool } from "@cloudflare/ai-chat/react";

  const tools: Record<string, AITool> = {
    myTool: {
      description: "Do something",
      parameters: {
        type: "object",
        properties: {
          arg: { type: "string" }
        }
      },
      execute: async ({ arg }) => {
        // Runs on client
        return { result: arg };
      }
    }
  };

  const { messages } = useAgentChat({ agent, tools });
  ```

  ```typescript Server-side (Traditional) theme={null}
  import { tool } from "ai";
  import { z } from "zod";

  const result = streamText({
    model,
    tools: {
      myTool: tool({
        description: "Do something",
        inputSchema: z.object({
          arg: z.string()
        }),
        execute: async ({ arg }) => {
          // Runs on server
          return { result: arg };
        }
      })
    }
  });
  ```
</CodeGroup>

## Running the Example

```bash theme={null}
npm install && npm start
```

Then visit [http://localhost:5173](http://localhost:5173) and try:

* "What's the page title?" (calls `getPageTitle`)
* "Where am I?" (calls `getUserLocation`)
* "What browser am I using?" (calls `getBrowserInfo`)

<Note>
  This example uses Workers AI (no API key needed) with `@cf/zai-org/glm-4.7-flash`.
</Note>

## Related Examples

<CardGroup cols={2}>
  <Card title="AI Chat" icon="comments" href="/examples/ai-chat">
    Server-side tools with approval and onToolCall
  </Card>

  <Card title="Codemode" icon="code" href="/examples/codemode">
    LLMs write code to orchestrate tools
  </Card>

  <Card title="MCP Client" icon="plug" href="/examples/mcp-client">
    Connect to MCP servers for dynamic tools
  </Card>

  <Card title="Playground" icon="grid" href="/examples/playground">
    Kitchen-sink showcase of all SDK features
  </Card>
</CardGroup>
