Cloudflare Agents Starter

repository·main·Indexed 23 days ago

https://github.com/cloudflare/agents-starter

A starter template for building AI chat agents on Cloudflare using the Agents SDK and Workers AI. It provides a full-stack foundation featuring a chat UI, tool-calling patterns (auto-execute, client-side, and human-in-the-loop approval), task scheduling, and vision capabilities. The template supports integration with Model Context Protocol (MCP) servers, typed RPC via the @callable() decorator, and allows switching between Workers AI, OpenAI, and Anthropic providers.

Tokens
2.8K
Snippets
10
Records
12
Agent score
30%

What's inside cloudflare-agents-starter

  1. How to add and configure tools

    main

    Tools are defined in the tools object within server.ts. There are three distinct patterns for implementing tools depending on where the execution logic resides and whether user interaction is required:

    1. Auto-execute (Server-side)

    Runs automatically on the server without user interaction. Use this for API calls or database queries.

    myTool: tool({
      description: "...",
      inputSchema: z.object({ /* ... */ }),
      execute: async (input) => { /* return result */ }
    }),

    2. Client-side (Browser-side)

    Does not include an execute function. The browser provides the result, which you must handle in app.tsx via the onToolCall callback. This is useful for accessing browser-specific APIs like geolocation.

    browserTool: tool({
      description: "...",
      inputSchema: z.object({ /* ... */ })
    }),

    3. Approval (Human-in-the-loop)

    Gated execution that requires user permission before the execute function runs. Use the needsApproval property to define the gating logic.

    sensitiveTool: tool({
      description: "...",
      inputSchema: z.object({ /* ... */ }),
      needsApproval: async (input) => true, // or conditional logic
      execute: async (input) => { /* runs after approval */ }
    }),
    // Auto-execute example
    getWeather: tool({
      description: "Get the current weather for a city",
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => {
        const res = await fetch(`https://api.weather.example/${city}`);
        return res.json();
      }
    }),
  2. Use OpenAI or Anthropic instead of Workers AI

    main

    While the starter uses Workers AI by default, you can switch to other providers by installing the appropriate SDK and updating the streamText call in server.ts.

    OpenAI

    1. Install the SDK:
    npm install @ai-sdk/openai
    1. Update server.ts:
    import { openai } from "@ai-sdk/openai";
    
    // Inside onChatMessage:
    const result = streamText({
      model: openai("gpt-5.2")
      // ...
    });
    1. Add OPENAI_API_KEY=your-key-here to your .env file.

    Anthropic

    1. Install the SDK:
    npm install @ai-sdk/anthropic
    1. Update server.ts:
    import { anthropic } from "@ai-sdk/anthropic";
    
    const result = streamText({
      model: anthropic("claude-sonnet-4-20250514")
      // ...
    });
    1. Add ANTHROPIC_API_KEY=your-key-here to your .env file.
    // OpenAI example
    import { openai } from "@ai-sdk/openai";
    
    // Inside onChatMessage:
    const result = streamText({
      model: openai("gpt-5.2")
      // ...
    });
  3. Deploy your agent to Cloudflare

    main

    To deploy your agent to the Cloudflare global network, run:

    npm run deploy

    Once deployed, your agent's messages are persisted in SQLite, streams can resume upon reconnection, and the agent will automatically hibernate when idle.

  4. Quick start with Agent Starter

    main

    To create and run a new AI agent project using this template, follow these steps:

    1. Scaffold the project using the Cloudflare CLI:
    npx create-cloudflare@latest --template cloudflare/agents-starter
    1. Navigate to the directory and install dependencies:
    cd agents-starter
    npm install
    1. Start the local development server:
    npm run dev

    Important: Authentication Requirements This template uses Workers AI with "ai": { "remote": true } in wrangler.jsonc. Because there is no local simulator for Workers AI, npm run dev opens a remote proxy session. You must be authenticated with Cloudflare. You can either:

    • Run wrangler login in your terminal.
    • Set a CLOUDFLARE_API_TOKEN environment variable (e.g., in a .env file).

    Once running, access the agent at http://localhost:5173.

    npx create-cloudflare@latest --template cloudflare/agents-starter
    cd agents-starter
    npm install
    npm run dev
  5. Customize scheduled task behavior

    main

    When a scheduled task is triggered, the executeTask method runs on the server. To perform work and notify connected clients without polluting the chat history, use this.broadcast() instead of saveMessages().

    Using this.broadcast() sends a one-off event that the client can display (e.g., as a toast notification) without the AI seeing the notification as new context, which prevents infinite loops.

    async executeTask(description: string, task: Schedule<string>) {
      // Do the actual work
      await sendEmail({ to: "user@example.com", subject: description });
    
      // Notify connected clients
      this.broadcast(
        JSON.stringify({ type: "scheduled-task", description, timestamp: new Date().toISOString() })
      );
    }
  6. Schedule tasks with the agent

    main

    The ChatAgent provides built-in scheduling capabilities. You can schedule tasks using the this.schedule(input, taskName, description, options) method.

    • Scheduling a task: Use this.schedule(input, "executeTask", description, { idempotent: true }). The input can be a date, a delay in seconds, or a cron expression.
    • Executing a task: Define a method (e.g., executeTask) that matches the name used in this.schedule. This method is called when the scheduled time arrives.
    • Communicating results: Use this.broadcast(payload) inside your task execution method to notify connected clients without injecting the notification into the chat history (which prevents infinite loops).
    • Managing tasks: Use tools like getScheduledTasks (to list tasks) and cancelScheduledTask (to cancel by ID) to give the AI control over the schedule.
  7. Define and use tools in ChatAgent

    main

    Tools allow your agent to perform actions. In onChatMessage, you define a tools object passed to streamText. There are three main types of tools:

    1. Server-side tools: Tools with an execute function that runs on the server (e.g., calling a weather API).
    2. Client-side tools: Tools without an execute function. The browser handles the execution (e.g., getting a user's timezone).
    3. Approval tools: Tools that include a needsApproval function. The agent will request user confirmation before running execute if the condition is met (e.g., math operations with large numbers).

    You can also spread this.mcp.getAITools() into your tools object to include tools provided by connected MCP servers.

  8. Connect to MCP servers

    main

    You can integrate external tools from Model Context Protocol (MCP) servers within the onChatMessage lifecycle method of your agent.

    async onChatMessage(onFinish, options) {
      // Connect to an MCP server
      await this.mcp.connect("https://my-mcp-server.example/sse");
    
      const result = streamText({
        // ...
        tools: {
          ...myTools,
          ...this.mcp.getAITools() // Include MCP tools
        }
      });
    }
  9. Expose agent methods as callable RPC

    main

    You can expose agent methods as typed RPC (Remote Procedure Call) that your client can call directly using the @callable() decorator. This allows the client to trigger specific logic on the agent instance.

    Server-side implementation:

    import { callable } from "agents";
    
    export class ChatAgent extends AIChatAgent<Env> {
      @callable()
      async getStats() {
        return { messageCount: this.messages.length };
      }
    }

    Client-side invocation:

    const stats = await agent.call("getStats");
    import { callable } from "agents";
    
    export class ChatAgent extends AIChatAgent<Env> {
      @callable()
      async getStats() {
        return { messageCount: this.messages.length };
      }
    }
    
    // Client-side:
    const stats = await agent.call("getStats");
  10. Manage MCP servers via callable methods

    main

    You can manage Model Context Protocol (MCP) servers by calling these methods on your ChatAgent instance. These are decorated with @callable(), making them accessible via the agent's API:

    • addServer(name: string, url: string): Adds a new MCP server.
    • removeServer(serverId: string): Removes an existing MCP server by its ID.
    @callable()
      async addServer(name: string, url: string) {
        return await this.addMcpServer(name, url);
      }
    
      @callable()
      async removeServer(serverId: string) {
        await this.removeMcpServer(serverId);
      }
  11. Deploy the Agent Server

    main

    The project exports a default handler for Cloudflare Workers. The fetch handler uses routeAgentRequest(request, env) to route incoming requests to the appropriate agent logic. This is the standard entry point for deploying the agent to a Cloudflare Worker environment.

    export default {
      async fetch(request: Request, env: Env) {
        return (
          (await routeAgentRequest(request, env)) ||
          new Response("Not found", { status: 404 })
        );
      }
    } satisfies ExportedHandler<Env>;
  12. Extend ChatAgent to build custom AI agents

    main

    The ChatAgent class, extending AIChatAgent, is the core component for building AI agents. You can customize its behavior by overriding lifecycle methods and defining tools.

    Key configuration properties:

    • maxPersistedMessages: Limits the number of messages stored in history (default: 100).
    • chatRecovery: Enables/disables chat recovery (default: true).
    • waitForMcpConnections: If true, waits for MCP connections to re-establish after hibernation before processing messages (default: true).

    Use onStart() to configure MCP-specific behaviors, such as OAuth callback handlers for authenticated MCP servers.

    export class ChatAgent extends AIChatAgent<Env> {
      maxPersistedMessages = 100;
      chatRecovery = true;
      waitForMcpConnections = true;
    
      onStart() {
        this.mcp.configureOAuthCallback({
          customHandler: (result) => {
            // Handle OAuth success or failure
          }
        });
      }
    }