mcpField
C
cloudflare-agents

cloudflare-agents

github.com/jackspaceエージェントワークフロー1641 回インストール12 週間前に更新
リポジトリ

cloudflare-agents について

Cloudflare Agents SDKは、Cloudflare WorkersとDurable Objects上でステートフルなAIエージェントを構築する開発者向けSDKです。状態の永続化やリアルタイム通信に加え、タスクのスケジュールやMCPサーバーの実装に対応します。

主な機能

  1. 01Durable Objects上でのエージェント構築
  2. 02WebSocketsとSSEによるリアルタイム通信
  3. 03SQLiteによる状態の永続化
  4. 04遅延・日時・cronによるタスク実行
  5. 05非同期Cloudflare Workflowsの起動
  6. 06MCPサーバーの構築

活用シーン

  1. 01リアルタイムチャットエージェントの構築
  2. 02定期チェックやリマインダーの設定
  3. 03対話型エージェントとバックグラウンド処理の連携
  4. 04人による確認を含むプロセスの実装

MCP 対応

Model Context Protocolを実装するMCPサーバーを構築し、MCPクライアントに接続できます。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jackspace/claudeskillz --skill cloudflare-agents

インストール後、Claude Code で/cloudflare-agentsを入力して読み込まれたか確認します。

同梱ファイルREADME.mdSKILL.json

詳細

Cloudflare Agents SDK

ステータス:本番環境で利用可能 ✅ 最終更新日:2025-10-21 依存関係:cloudflare-worker-base(推奨) 最新バージョン:agents@latest、@modelcontextprotocol/sdk@latest 本番環境でのテスト済み:Cloudflare 独自の MCP サーバー(https://github.com/cloudflare/mcp-server-cloudflare)


Cloudflare Agents とは?

Cloudflare Agents SDK を使用すると、Cloudflare Workers + Durable Objects 上で動作する、AI を活用した自律型エージェントを構築できます。エージェントは次のことができます。

  • WebSocket と Server-Sent Events を介したリアルタイム通信
  • 組み込みの SQLite データベース(エージェントあたり最大 1GB)を使用した状態の永続化
  • 遅延時間、指定日、または cron 式を使用したタスクのスケジュール
  • Cloudflare Workflows を非同期で起動したワークフローの実行
  • Browser Rendering API + Puppeteer を使用したWeb の閲覧
  • Vectorize ベクトルデータベース + Workers AI の埋め込みを使用したRAG の実装
  • Model Context Protocol を実装したMCP サーバーの構築
  • レビューと承認のための人間参加型パターンのサポート
  • 世界中で数百万の独立したエージェントインスタンスにスケール

各エージェントインスタンスは、数秒、数分、または数時間動作できる、グローバルに一意なステートフルのマイクロサーバーです。


クイックスタート(10 分)

1. テンプレートでプロジェクトをスキャフォールディングする
npm create cloudflare@latest my-agent -- \
  --template=cloudflare/agents-starter \
  --ts \
  --git \
  --deploy false

作成されるもの:

  • 完全なエージェントプロジェクト構成
  • TypeScript の設定
  • Durable Objects バインディングを含む wrangler.jsonc
  • チャットエージェントの実装例
  • useAgent フックを備えた React クライアント
2. または既存の Worker に追加する
cd my-existing-worker
npm install agents

続いて、Agent クラスを作成します。

// src/index.ts
import { Agent, AgentNamespace } from "agents";

export class MyAgent extends Agent {
  async onRequest(request: Request): Promise<Response> {
    return new Response("Hello from Agent!");
  }
}

export default MyAgent;
3. Durable Objects バインディングを設定する

wrangler.jsonc を作成または更新します。

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-agent",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-21",
  "compatibility_flags": ["nodejs_compat"],
  "durable_objects": {
    "bindings": [
      {
        "name": "MyAgent",        // MUST match class name
        "class_name": "MyAgent"   // MUST match exported class
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["MyAgent"]  // CRITICAL: Enables SQLite storage
    }
  ]
}

重要な設定ルール:

  • ✅ name と class_name は必ず同一にする必要があります
  • ✅ new_sqlite_classes は最初のマイグレーションに必ず含める必要があります(後から追加できません)
  • ✅ Agent クラスは必ずエクスポートする必要があります(そうしないとバインディングが失敗します)
  • ✅ マイグレーションタグは再利用できません(各マイグレーションに一意のタグが必要です)
4. デプロイする
npx wrangler@latest deploy

エージェントは次の場所で実行されています:https://my-agent.<subdomain>.workers.dev


設定の詳細

wrangler.jsonc の完全な例
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-agent",
  "main": "src/index.ts",
  "account_id": "YOUR_ACCOUNT_ID",
  "compatibility_date": "2025-10-21",
  "compatibility_flags": ["nodejs_compat"],

  // Durable Objects configuration (REQUIRED)
  "durable_objects": {
    "bindings": [
      {
        "name": "MyAgent",
        "class_name": "MyAgent"
      }
    ]
  },

  // Migrations (REQUIRED)
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["MyAgent"]  // Enables state persistence
    }
  ],

  // Optional: Workers AI binding (for AI model calls)
  "ai": {
    "binding": "AI"
  },

  // Optional: Vectorize binding (for RAG)
  "vectorize": {
    "bindings": [
      {
        "binding": "VECTORIZE",
        "index_name": "my-agent-vectors"
      }
    ]
  },

  // Optional: Browser Rendering binding (for web browsing)
  "browser": {
    "binding": "BROWSER"
  },

  // Optional: Workflows binding (for async workflows)
  "workflows": [
    {
      "name": "MY_WORKFLOW",
      "class_name": "MyWorkflow",
      "script_name": "my-workflow-script"  // If in different project
    }
  ],

  // Optional: D1 binding (for additional persistent data)
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "my-agent-db",
      "database_id": "your-database-id"
    }
  ],

  // Optional: R2 binding (for file storage)
  "r2_buckets": [
    {
      "binding": "BUCKET",
      "bucket_name": "my-agent-files"
    }
  ],

  // Optional: Environment variables
  "vars": {
    "ENVIRONMENT": "production"
  },

  // Optional: Secrets (set with: wrangler secret put KEY)
  // OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.

  // Observability
  "observability": {
    "enabled": true
  }
}
マイグレーションのベストプラクティス

アトミックなデプロイ:マイグレーションはアトミックな操作であり、段階的にデプロイすることはできません。

{
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["MyAgent"]  // Initial: enable SQLite
    },
    {
      "tag": "v2",
      "renamed_classes": [
        {"from": "MyAgent", "to": "MyRenamedAgent"}
      ]
    },
    {
      "tag": "v3",
      "deleted_classes": ["OldAgent"]
    },
    {
      "tag": "v4",
      "transferred_classes": [
        {
          "from": "AgentInOldScript",
          "from_script": "old-worker",
          "to": "AgentInNewScript"
        }
      ]
    }
  ]
}

マイグレーションのルール:

  • ✅ 各マイグレーションには一意の tag が必要です
  • ✅ デプロイ済みの既存クラスで SQLite を有効にすることはできません(最初のマイグレーションに含める必要があります)
  • ✅ デプロイ時にマイグレーションが順番に適用されます
  • ✅ 以前のマイグレーションタグを編集または削除することはできません
  • ❌ 新しいマイグレーションを段階的にデプロイしないでください(アトミックなデプロイのみ)
環境ごとのマイグレーション
{
  "migrations": [{"tag": "v1", "new_sqlite_classes": ["MyAgent"]}],
  "env": {
    "staging": {
      "migrations": [
        {"tag": "v1", "new_sqlite_classes": ["MyAgent"]},
        {"tag": "v2-staging", "renamed_classes": [{"from": "MyAgent", "to": "StagingAgent"}]}
      ]
    }
  }
}

Agent クラス API

Agent クラスは Agents SDK の基盤です。これを拡張してエージェントを作成します。

基本的な Agent 構造
import { Agent } from "agents";

interface Env {
  // Environment variables and bindings
  OPENAI_API_KEY: string;
  AI: Ai;
  VECTORIZE: Vectorize;
  DB: D1Database;
}

interface State {
  // Your agent's persistent state
  counter: number;
  messages: string[];
  lastUpdated: Date | null;
}

export class MyAgent extends Agent<Env, State> {
  // Optional: Set initial state (first time agent is created)
  initialState: State = {
    counter: 0,
    messages: [],
    lastUpdated: null
  };

  // Optional: Called when agent instance starts or wakes from hibernation
  async onStart() {
    console.log('Agent started:', this.name, 'State:', this.state);
  }

  // Handle HTTP requests
  async onRequest(request: Request): Promise<Response> {
    return Response.json({ message: "Hello from Agent", state: this.state });
  }

  // Handle WebSocket connections (optional)
  async onConnect(connection: Connection, ctx: ConnectionContext) {
    console.log('Client connected:', connection.id);
    // Connections are automatically accepted
  }

  // Handle WebSocket messages (optional)
  async onMessage(connection: Connection, message: WSMessage) {
    if (typeof message === 'string') {
      connection.send(`Echo: ${message}`);
    }
  }

  // Handle WebSocket errors (optional)
  async onError(connection: Connection, error: unknown): Promise<void> {
    console.error('Connection error:', error);
  }

  // Handle WebSocket close (optional)
  async onClose(connection: Connection, code: number, reason: string, wasClean: boolean): Promise<void> {
    console.log('Connection closed:', code, reason);
  }

  // Called when state is updated from any source (optional)
  onStateUpdate(state: State, source: "server" | Connection) {
    console.log('State updated:', state, 'Source:', source);
  }

  // Custom methods (call from any handler)
  async customMethod(data: any) {
    this.setState({
      ...this.state,
      counter: this.state.counter + 1,
      lastUpdated: new Date()
    });
  }
}
Agent のプロパティへのアクセス

Agent の任意のメソッド内で:

export class MyAgent extends Agent<Env, State> {
  async someMethod() {
    // Access environment variables and bindings
    const apiKey = this.env.OPENAI_API_KEY;
    const ai = this.env.AI;

    // Access current state (read-only)
    const counter = this.state.counter;

    // Update state (persisted automatically)
    this.setState({ ...this.state, counter: counter + 1 });

    // Access SQL database
    const results = await this.sql`SELECT * FROM users`;

    // Get agent instance name
    const instanceName = this.name;  // e.g., "user-123"

    // Schedule tasks
    await this.schedule(60, "runLater", { data: "example" });

    // Call other methods
    await this.customMethod({ foo: "bar" });
  }
}

HTTP と Server-Sent Events

HTTP リクエストの処理
export class MyAgent extends Agent<Env> {
  async onRequest(request: Request): Promise<Response> {
    const url = new URL(request.url);
    const method = request.method;

    if (method === "POST" && url.pathname === "/increment") {
      const counter = (this.state.counter || 0) + 1;
      this.setState({ ...this.state, counter });
      return Response.json({ counter });
    }

    if (method === "GET" && url.pathname === "/status") {
      return Response.json({ state: this.state, name: this.name });
    }

    return new Response("Not Found", { status: 404 });
  }
}
Server-Sent Events(SSE)ストリーミング
export class MyAgent extends Agent<Env> {
  async onRequest(request: Request): Promise<Response> {
    const stream = new ReadableStream({
      async start(controller) {
        const encoder = new TextEncoder();

        // Send events to client
        controller.enqueue(encoder.encode('data: {"message": "Starting"}\n\n'));

        await new Promise(resolve => setTimeout(resolve, 1000));

        controller.enqueue(encoder.encode('data: {"message": "Processing"}\n\n'));

        await new Promise(resolve => setTimeout(resolve, 1000));

        controller.enqueue(encoder.encode('data: {"message": "Complete"}\n\n'));

        controller.close();
      }
    });

    return new Response(stream, {
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
      }
    });
  }
}

SSE と WebSockets の比較:

機能SSEWebSockets
方向サーバー → クライアントのみ双方向
プロトコルHTTPws:// または wss://
再接続自動手動
バイナリデータ制限あり完全にサポート
用途ストリーミング応答、通知チャット、リアルタイムコラボレーション

推奨:ほとんどのエージェントアプリケーションでは WebSockets を使用してください(全二重通信が可能で、長時間のセッションに適しています)。


WebSockets

WebSocket の完全な例
import { Agent, Connection, ConnectionContext, WSMessage } from "agents";

interface ChatState {
  messages: Array<{ id: string; text: string; sender: string; timestamp: number }>;
  participants: string[];
}

export class ChatAgent extends Agent<Env, ChatState> {
  initialState: ChatState = {
    messages: [],
    participants: []
  };

  async onConnect(connection: Connection, ctx: ConnectionContext) {
    // Access original HTTP request for auth
    const authHeader = ctx.request.headers.get('Authorization');
    const userId = ctx.request.headers.get('X-User-ID') || 'anonymous';

    // Connections are automatically accepted
    // Optionally close connection if unauthorized:
    // if (!authHeader) {
    //   connection.close(401, "Unauthorized");
    //   return;
    // }

    // Add to participants
    this.setState({
      ...this.state,
      participants: [...this.state.participants, userId]
    });

    // Send welcome message
    connection.send(JSON.stringify({
      type: 'welcome',
      message: 'Connected to chat',
      participants: this.state.participants
    }));
  }

  async onMessage(connection: Connection, message: WSMessage) {
    if (typeof message === 'string') {
      try {
        const data = JSON.parse(message);

        if (data.type === 'chat') {
          // Add message to state
          const newMessage = {
            id: crypto.randomUUID(),
            text: data.text,
            sender: data.sender || 'anonymous',
            timestamp: Date.now()
          };

          this.setState({
            ...this.state,
            messages: [...this.state.messages, newMessage]
          });

          // Broadcast to this connection (state sync will broadcast to all)
          connection.send(JSON.stringify({
            type: 'message_added',
            message: newMessage
          }));
        }
      } catch (e) {
        connection.send(JSON.stringify({ type: 'error', message: 'Invalid message format' }));
      }
    }
  }

  async onError(connection: Connection, error: unknown): Promise<void> {
    console.error('WebSocket error:', error);
    // Optionally log to external monitoring
  }

  async onClose(connection: Connection, code: number, reason: string, wasClean: boolean): Promise<void> {
    console.log(`Connection ${connection.id} closed:`, code, reason, wasClean);
    // Clean up connection-specific state if needed
  }
}
接続の管理
export class MyAgent extends Agent {
  async onMessage(connection: Connection, message: WSMessage) {
    // Connection properties
    const connId = connection.id;  // Unique connection ID
    const connState = connection.state;  // Connection-specific state

    // Update connection state (not agent state)
    connection.setState({ ...connection.state, lastActive: Date.now() });

    // Send to this connection only
    connection.send("Message to this client");

    // Close connection programmatically
    connection.close(1000, "Goodbye");
  }
}

状態管理

setState() の使用
interface UserState {
  name: string;
  email: string;
  preferences: { theme: string; notifications: boolean };
  loginCount: number;
  lastLogin: Date | null;
}

export class UserAgent extends Agent<Env, UserState> {
  initialState: UserState = {
    name: "",
    email: "",
    preferences: { theme: "dark", notifications: true },
    loginCount: 0,
    lastLogin: null
  };

  async onRequest(request: Request): Promise<Response> {
    if (request.method === "POST" && new URL(request.url).pathname === "/login") {
      // Update state
      this.setState({
        ...this.state,
        loginCount: this.state.loginCount + 1,
        lastLogin: new Date()
      });

      // State is automatically persisted and synced to connected clients
      return Response.json({ success: true, state: this.state });
    }

    return Response.json({ state: this.state });
  }

  onStateUpdate(state: UserState, source: "server" | Connection) {
    console.log('State updated:', state);
    console.log('Source:', source);  // "server" or Connection object

    // React to state changes
    if (state.loginCount > 10) {
      console.log('Frequent user!');
    }
  }
}

状態のルール:

  • ✅ 状態は JSON でシリアライズ可能です(オブジェクト、配列、文字列、数値、ブール値、null)
  • ✅ エージェントの再起動後も状態は保持されます
  • ✅ エージェント内では状態がすぐに一貫します
  • ✅ 状態は接続中の WebSocket クライアントに自動的に同期されます
  • ❌ 状態に関数や循環参照を含めることはできません
  • ❌ 状態の合計サイズはデータベースサイズによって制限されます(エージェントあたり最大 1GB)
SQL データベースの使用

各エージェントには、this.sql からアクセスできる SQLite データベースが組み込まれています。

export class MyAgent extends Agent {
  async onStart() {
    // Create tables on first start
    await this.sql`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `;

    await this.sql`
      CREATE INDEX IF NOT EXISTS idx_email ON users(email)
    `;
  }

  async addUser(name: string, email: string) {
    // Insert with prepared statement (prevents SQL injection)
    const result = await this.sql`
      INSERT INTO users (name, email)
      VALUES (${name}, ${email})
    `;

    return result;
  }

  async getUser(email: string) {
    // Query returns array of results
    const users = await this.sql`
      SELECT * FROM users WHERE email = ${email}
    `;

    return users[0] || null;
  }

  async getAllUsers() {
    const users = await this.sql`
      SELECT * FROM users ORDER BY created_at DESC
    `;

    return users;
  }

  async updateUser(id: number, name: string) {
    await this.sql`
      UPDATE users SET name = ${name} WHERE id = ${id}
    `;
  }

  async deleteUser(id: number) {
    await this.sql`
      DELETE FROM users WHERE id = ${id}
    `;
  }
}

SQL のベストプラクティス:

  • ✅ タグ付きテンプレートリテラルを使用します(SQL インジェクションを防止します)
  • ✅ 頻繁にクエリするカラムにはインデックスを作成します
  • ✅ 複数の関連する操作にはトランザクションを使用します
  • ✅ クエリ結果は常に配列です(1 行の場合も同様です)
  • ❌ SQL 文字列を手動で構築しないでください
  • ❌ データベースのサイズ上限が 1GB であることに注意してください

タスクのスケジュール設定

エージェントは this.schedule() を使用して、将来実行するタスクをスケジュールできます。

遅延(秒)
export class MyAgent extends Agent {
  async onRequest(request: Request): Promise<Response> {
    // Schedule task to run in 60 seconds
    const { id } = await this.schedule(60, "checkStatus", { requestId: "123" });

    return Response.json({ scheduledTaskId: id });
  }

  // This method will be called in 60 seconds
  async checkStatus(data: { requestId: string }) {
    console.log('Checking status for request:', data.requestId);
    // Perform check, update state, send notification, etc.
  }
}
特定の日付
export class MyAgent extends Agent {
  async scheduleReminder(reminderDate: string) {
    const date = new Date(reminderDate);

    const { id } = await this.schedule(date, "sendReminder", {
      message: "Time for your appointment!"
    });

    return id;
  }

  async sendReminder(data: { message: string }) {
    console.log('Sending reminder:', data.message);
    // Send email, push notification, etc.
  }
}
Cron 式
export class MyAgent extends Agent {
  async setupRecurringTasks() {
    // Every 10 minutes
    await this.schedule("*/10 * * * *", "checkUpdates", {});

    // Every day at 8 AM
    await this.schedule("0 8 * * *", "dailyReport", {});

    // Every Monday at 9 AM
    await this.schedule("0 9 * * 1", "weeklyReport", {});

    // Every hour on the hour
    await this.schedule("0 * * * *", "hourlyCheck", {});
  }

  async checkUpdates(data: any) {
    console.log('Checking for updates...');
  }

  async dailyReport(data: any) {
    console.log('Generating daily report...');
  }

  async weeklyReport(data: any) {
    console.log('Generating weekly report...');
  }

  async hourlyCheck(data: any) {
    console.log('Running hourly check...');
  }
}
スケジュール済みタスクの管理
export class MyAgent extends Agent {
  async manageSchedules() {
    // Get all scheduled tasks
    const allTasks = this.getSchedules();
    console.log('Total tasks:', allTasks.length);

    // Get specific task by ID
    const taskId = "some-task-id";
    const task = await this.getSchedule(taskId);

    if (task) {
      console.log('Task:', task.callback, 'at', new Date(task.time));
      console.log('Payload:', task.payload);
      console.log('Type:', task.type);  // "scheduled" | "delayed" | "cron"

      // Cancel the task
      const cancelled = await this.cancelSchedule(taskId);
      console.log('Cancelled:', cancelled);
    }

    // Get tasks in time range
    const upcomingTasks = this.getSchedules({
      timeRange: {
        start: new Date(),
        end: new Date(Date.now() + 24 * 60 * 60 * 1000)  // Next 24 hours
      }
    });

    console.log('Upcoming tasks:', upcomingTasks.length);

    // Filter by type
    const cronTasks = this.getSchedules({ type: "cron" });
    const delayedTasks = this.getSchedules({ type: "delayed" });
  }
}

スケジュール設定の制約:

  • 各タスクは SQL データベースの 1 行に対応します(タスクあたり最大 2 MB)
  • タスクの合計数は次の条件によって制限されます:(task_size * count) + other_state < 1GB
  • Cron タスクは明示的にキャンセルするまで実行され続けます
  • コールバックメソッドは Agent クラスに存在する必要があります(存在しない場合はエラーが発生します)

重大なエラー:コールバックメソッドが存在しない場合:

// ❌ BAD: Method doesn't exist
await this.schedule(60, "nonExistentMethod", {});

// ✅ GOOD: Method exists
await this.schedule(60, "existingMethod", {});

async existingMethod(data: any) {
  // Implementation
}

ワークフローの実行

エージェントは非同期の Cloudflare Workflows をトリガーできます。

ワークフローのバインディング設定

wrangler.jsonc:

{
  "workflows": [
    {
      "name": "MY_WORKFLOW",
      "class_name": "MyWorkflow"
    }
  ]
}

ワークフローが別のスクリプトにある場合:

{
  "workflows": [
    {
      "name": "EMAIL_WORKFLOW",
      "class_name": "EmailWorkflow",
      "script_name": "email-workflows"  // Different project
    }
  ]
}
ワークフローのトリガー
import { Agent } from "agents";
import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workers";

interface Env {
  MY_WORKFLOW: Workflow;
  MyAgent: AgentNamespace<MyAgent>;
}

export class MyAgent extends Agent<Env> {
  async onRequest(request: Request): Promise<Response> {
    const userId = new URL(request.url).searchParams.get('userId');

    // Trigger a workflow immediately
    const instance = await this.env.MY_WORKFLOW.create({
      id: `user-${userId}`,
      params: { userId, action: "process" }
    });

    // Or schedule a delayed workflow trigger
    await this.schedule(300, "runWorkflow", { userId });

    return Response.json({ workflowId: instance.id });
  }

  async runWorkflow(data: { userId: string }) {
    const instance = await this.env.MY_WORKFLOW.create({
      id: `delayed-${data.userId}`,
      params: data
    });

    // Monitor workflow status periodically
    await this.schedule("*/5 * * * *", "checkWorkflowStatus", { id: instance.id });
  }

  async checkWorkflowStatus(data: { id: string }) {
    // Check workflow status (see Workflows docs for details)
    console.log('Checking workflow:', data.id);
  }
}

// Workflow definition (can be in same or different file/project)
export class MyWorkflow extends WorkflowEntrypoint<Env> {
  async run(event: WorkflowEvent<{ userId: string }>, step: WorkflowStep) {
    // Workflow implementation
    const result = await step.do('process-data', async () => {
      return { processed: true };
    });

    return result;
  }
}
エージェントとワークフロー
機能エージェントワークフロー
目的インタラクティブで、ユーザー向けバックグラウンド処理
期間数秒から数時間数分から数時間
状態SQLite データベースステップベースのチェックポイント
インタラクションWebSocket、HTTP直接のインタラクションなし
再試行手動各ステップで自動
ユースケースチャット、リアルタイム UIETL、バッチ処理

ベストプラクティス:複数のワークフローを連携させるにはエージェントを使用します。エージェントはユーザーとのインタラクションを維持しながら、ワークフローをトリガー、監視し、その結果に応答できます。


Web の閲覧

エージェントは Browser Rendering を使用して Web を閲覧できます。

Browser Rendering バインディング

wrangler.jsonc:

{
  "browser": {
    "binding": "BROWSER"
  }
}
インストール
npm install @cloudflare/puppeteer
Web スクレイピングの例
import { Agent } from "agents";
import puppeteer from "@cloudflare/puppeteer";

interface Env {
  BROWSER: Fetcher;
  OPENAI_API_KEY: string;
}

export class BrowserAgent extends Agent<Env> {
  async browse(urls: string[]) {
    const responses = [];

    for (const url of urls) {
      const browser = await puppeteer.launch(this.env.BROWSER);
      const page = await browser.newPage();

      await page.goto(url);
      await page.waitForSelector("body");

      const bodyContent = await page.$eval("body", el => el.innerHTML);

      // Extract data with AI
      const data = await this.extractData(bodyContent);
      responses.push({ url, data });

      await browser.close();
    }

    return responses;
  }

  async extractData(html: string): Promise<any> {
    // Use OpenAI or Workers AI to extract structured data
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.env.OPENAI_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: 'gpt-4o-mini',
        messages: [{
          role: 'user',
          content: `Extract product info from HTML: ${html.slice(0, 4000)}`
        }],
        response_format: { type: "json_object" }
      })
    });

    const result = await response.json();
    return JSON.parse(result.choices[0].message.content);
  }

  async onRequest(request: Request): Promise<Response> {
    const url = new URL(request.url).searchParams.get('url');
    if (!url) {
      return new Response("Missing url parameter", { status: 400 });
    }

    const results = await this.browse([url]);
    return Response.json(results);
  }
}
スクリーンショットの取得
export class ScreenshotAgent extends Agent<Env> {
  async captureScreenshot(url: string): Promise<Buffer> {
    const browser = await puppeteer.launch(this.env.BROWSER);
    const page = await browser.newPage();

    await page.goto(url);
    const screenshot = await page.screenshot({ fullPage: true });

    await browser.close();

    return screenshot;
  }
}

検索拡張生成(RAG)

Vectorize と Workers AI の埋め込みを使用して RAG を実装します。

Vectorize バインディング

wrangler.jsonc:

{
  "ai": {
    "binding": "AI"
  },
  "vectorize": {
    "bindings": [
      {
        "binding": "VECTORIZE",
        "index_name": "my-agent-vectors"
      }
    ]
  }
}
インデックスの作成
npx wrangler vectorize create my-agent-vectors \
  --dimensions=768 \
  --metric=cosine
RAG の完全な実装
import { Agent } from "agents";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

interface Env {
  AI: Ai;
  VECTORIZE: Vectorize;
  OPENAI_API_KEY: string;
}

export class RAGAgent extends Agent<Env> {
  // Ingest documents
  async ingestDocuments(documents: Array<{ id: string; text: string; metadata: any }>) {
    const vectors = [];

    for (const doc of documents) {
      // Generate embedding with Workers AI
      const { data } = await this.env.AI.run('@cf/baai/bge-base-en-v1.5', {
        text: [doc.text]
      });

      vectors.push({
        id: doc.id,
        values: data[0],
        metadata: { ...doc.metadata, text: doc.text }
      });
    }

    // Insert into Vectorize
    await this.env.VECTORIZE.upsert(vectors);

    return { ingested: vectors.length };
  }

  // Query knowledge base
  async queryKnowledge(userQuery: string, topK: number = 5) {
    // Generate query embedding
    const { data } = await this.env.AI.run('@cf/baai/bge-base-en-v1.5', {
      text: [userQuery]
    });

    // Search Vectorize
    const results = await this.env.VECTORIZE.query(data[0], { topK });

    // Extract relevant documents
    const context = results.matches.map(match => match.metadata.text).join('\n\n');

    return context;
  }

  // RAG Chat
  async chat(userMessage: string) {
    // Retrieve relevant context
    const context = await this.queryKnowledge(userMessage);

    // Generate response with context
    const { text } = await generateText({
      model: openai('gpt-4o-mini'),
      messages: [
        {
          role: 'system',
          content: `You are a helpful assistant. Use the following context to answer questions:\n\n${context}`
        },
        {
          role: 'user',
          content: userMessage
        }
      ]
    });

    return { response: text, context };
  }

  async onRequest(request: Request): Promise<Response> {
    const { message } = await request.json();
    const result = await this.chat(message);
    return Response.json(result);
  }
}
メタデータのフィルタリング
// Create metadata indexes BEFORE inserting vectors
await this.env.VECTORIZE.createMetadataIndex("category");
await this.env.VECTORIZE.createMetadataIndex("language");

// Query with filters
const results = await this.env.VECTORIZE.query(queryVector, {
  topK: 10,
  filter: {
    category: { $eq: "documentation" },
    language: { $eq: "en" }
  }
});

参照:Vectorize の完全なガイドについては、cloudflare-vectorize スキルをご覧ください。


AI モデルの使用

AI SDK(Vercel)
npm install ai @ai-sdk/openai @ai-sdk/anthropic
import { Agent } from "agents";
import { generateText, streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";

export class AIAgent extends Agent {
  // Simple text generation
  async generateResponse(prompt: string) {
    const { text } = await generateText({
      model: openai('gpt-4o-mini'),
      prompt
    });

    return text;
  }

  // Streaming response
  async streamResponse(prompt: string): Promise<Response> {
    const result = streamText({
      model: anthropic('claude-sonnet-4-5'),
      prompt
    });

    return result.toTextStreamResponse();
  }

  // Structured output
  async extractData(text: string) {
    const { object } = await generateObject({
      model: openai('gpt-4o-mini'),
      schema: z.object({
        name: z.string(),
        email: z.string().email(),
        age: z.number().optional()
      }),
      prompt: `Extract user info from: ${text}`
    });

    return object;
  }
}
Workers AI
interface Env {
  AI: Ai;
}

export class WorkersAIAgent extends Agent<Env> {
  async generateText(prompt: string) {
    const response = await this.env.AI.run('@cf/meta/llama-3-8b-instruct', {
      messages: [{ role: 'user', content: prompt }]
    });

    return response;
  }

  async generateImage(prompt: string) {
    const response = await this.env.AI.run('@cf/black-forest-labs/flux-1-schnell', {
      prompt
    });

    return response;
  }
}

参照:Workers AI の完全なガイドについては、cloudflare-workers-ai スキルをご覧ください。


エージェントの呼び出し

routeAgentRequest の使用

URL パターン /agents/:agent/:name に基づいて、エージェントにリクエストを自動的にルーティングします。

import { Agent, AgentNamespace, routeAgentRequest } from 'agents';

interface Env {
  MyAgent: AgentNamespace<MyAgent>;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Routes to: /agents/my-agent/user-123
    const response = await routeAgentRequest(request, env);

    if (response) {
      return response;
    }

    return new Response("Not Found", { status: 404 });
  }
} satisfies ExportedHandler<Env>;

export class MyAgent extends Agent<Env> {
  async onRequest(request: Request): Promise<Response> {
    return Response.json({ agent: this.name });
  }
}

URL パターン:/agents/my-agent/user-123

  • my-agent = kebab-case のクラス名
  • user-123 = エージェントのインスタンス名
getAgentByName の使用

カスタムルーティング、または Workers からエージェントを呼び出す場合:

import { Agent, AgentNamespace, getAgentByName } from 'agents';

interface Env {
  MyAgent: AgentNamespace<MyAgent>;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const userId = new URL(request.url).searchParams.get('userId') || 'anonymous';

    // Get or create agent instance
    const agent = getAgentByName<Env, MyAgent>(env.MyAgent, `user-${userId}`);

    // Pass request to agent
    return (await agent).fetch(request);
  }
} satisfies ExportedHandler<Env>;

export class MyAgent extends Agent<Env> {
  // Agent implementation
}
エージェントメソッドの直接呼び出し
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const agent = getAgentByName<Env, MyAgent>(env.MyAgent, 'user-123');

    // Call custom methods on agent using RPC
    const result = await (await agent).customMethod({ data: "example" });

    return Response.json({ result });
  }
}

export class MyAgent extends Agent<Env> {
  async customMethod(params: { data: string }): Promise<any> {
    return { processed: params.data };
  }
}
マルチエージェント通信
interface Env {
  AgentA: AgentNamespace<AgentA>;
  AgentB: AgentNamespace<AgentB>;
}

export class AgentA extends Agent<Env> {
  async processData(data: any) {
    // Call another agent
    const agentB = getAgentByName<Env, AgentB>(this.env.AgentB, 'processor-1');
    const result = await (await agentB).analyze(data);

    return result;
  }
}

export class AgentB extends Agent<Env> {
  async analyze(data: any) {
    return { analyzed: true, data };
  }
}
認証パターン
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Authenticate BEFORE invoking agent
    const authHeader = request.headers.get('Authorization');
    if (!authHeader) {
      return new Response("Unauthorized", { status: 401 });
    }

    const userId = await verifyToken(authHeader);
    if (!userId) {
      return new Response("Forbidden", { status: 403 });
    }

    // Only create/access agent for authenticated users
    const agent = getAgentByName<Env, MyAgent>(env.MyAgent, `user-${userId}`);
    return (await agent).fetch(request);
  }
}

重要:認証は常に Worker で行い、Agent では行わないでください。Agent は呼び出し元が認可済みであることを前提とします。


クライアント API

AgentClient(ブラウザー)
import { AgentClient } from "agents/client";

// Connect to agent instance
const client = new AgentClient({
  agent: "chat-agent",        // Class name in kebab-case
  name: "room-123",           // Instance name
  host: window.location.host
});

client.onopen = () => {
  console.log("Connected");
  client.send(JSON.stringify({ type: "join", user: "alice" }));
};

client.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log("Received:", data);
};

client.onclose = () => {
  console.log("Disconnected");
};
agentFetch(HTTP リクエスト)
import { agentFetch } from "agents/client";

async function getData() {
  const response = await agentFetch(
    { agent: "my-agent", name: "user-123" },
    {
      method: "GET",
      headers: { "Authorization": `Bearer ${token}` }
    }
  );

  const data = await response.json();
  return data;
}
useAgent フック(React)
import { useAgent } from "agents/react";
import { useState } from "react";

function ChatUI() {
  const [messages, setMessages] = useState([]);

  const connection = useAgent({
    agent: "chat-agent",
    name: "room-123",
    onMessage: (event) => {
      const data = JSON.parse(event.data);
      if (data.type === 'message') {
        setMessages(prev => [...prev, data.message]);
      }
    },
    onOpen: () => console.log("Connected"),
    onClose: () => console.log("Disconnected")
  });

  const sendMessage = (text: string) => {
    connection.send(JSON.stringify({ type: 'chat', text }));
  };

  return (
    <div>
      {messages.map((msg, i) => <div key={i}>{msg.text}</div>)}
      <button onClick={() => sendMessage("Hello")}>Send</button>
    </div>
  );
}
状態の同期
import { useAgent } from "agents/react";
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  const agent = useAgent({
    agent: "counter-agent",
    name: "my-counter",
    onStateUpdate: (newState) => {
      setCount(newState.counter);
    }
  });

  const increment = () => {
    agent.setState({ counter: count + 1 });
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}
useAgentChat フック
import { useAgentChat } from "agents/ai-react";

function ChatInterface() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useAgentChat({
    agent: "ai-chat-agent",
    name: "chat-session-123"
  });

  return (
    <div>
      <div>
        {messages.map((msg, i) => (
          <div key={i}>
            <strong>{msg.role}:</strong> {msg.content}
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Type a message..."
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading}>
          {isLoading ? "Thinking..." : "Send"}
        </button>
      </form>
    </div>
  );
}

モデルコンテキストプロトコル(MCP)

Agents SDK を使用して MCP サーバーを構築します。

MCP サーバーのセットアップ
npm install @modelcontextprotocol/sdk agents
基本的な MCP サーバー
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export class MyMCP extends McpAgent {
  server = new McpServer({ name: "Demo", version: "1.0.0" });

  async init() {
    // Define a tool
    this.server.tool(
      "add",
      "Add two numbers together",
      {
        a: z.number().describe("First number"),
        b: z.number().describe("Second number")
      },
      async ({ a, b }) => ({
        content: [{ type: "text", text: String(a + b) }]
      })
    );
  }
}
ステートフルな MCP サーバー
type State = { counter: number };

export class StatefulMCP extends McpAgent<Env, State> {
  server = new McpServer({ name: "Counter", version: "1.0.0" });

  initialState: State = { counter: 0 };

  async init() {
    // Resource
    this.server.resource(
      "counter",
      "mcp://resource/counter",
      (uri) => ({
        contents: [{ uri: uri.href, text: String(this.state.counter) }]
      })
    );

    // Tool
    this.server.tool(
      "increment",
      "Increment the counter",
      { amount: z.number() },
      async ({ amount }) => {
        this.setState({
          ...this.state,
          counter: this.state.counter + amount
        });

        return {
          content: [{
            type: "text",
            text: `Counter is now ${this.state.counter}`
          }]
        };
      }
    );
  }
}
MCP トランスポートの設定
import { Hono } from 'hono';

const app = new Hono();

// Modern streamable HTTP transport (recommended)
app.mount('/mcp', MyMCP.serve('/mcp').fetch, { replaceRequest: false });

// Legacy SSE transport (deprecated)
app.mount('/sse', MyMCP.serveSSE('/sse').fetch, { replaceRequest: false });

export default app;

トランスポートの比較:

  • /mcp:ストリーミング可能な HTTP(モダンで、推奨)
  • /sse:Server-Sent Events(レガシー、非推奨)
OAuth を使用した MCP
import { OAuthProvider } from '@cloudflare/workers-oauth-provider';

export default new OAuthProvider({
  apiHandlers: {
    '/sse': MyMCP.serveSSE('/sse'),
    '/mcp': MyMCP.serve('/mcp')
  },
  // OAuth configuration
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  // ... other OAuth settings
});
MCP サーバーのテスト
# Run MCP inspector
npx @modelcontextprotocol/inspector@latest

# Connect to: http://localhost:8788/mcp

Cloudflare の MCP サーバー:本番環境での例については、リファレンスを参照してください。


パターンと概念

チャットエージェント(AIChatAgent)
import { AIChatAgent } from "agents/ai-chat-agent";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";

export class MyChatAgent extends AIChatAgent<Env> {
  async onChatMessage(onFinish) {
    const result = streamText({
      model: openai('gpt-4o-mini'),
      messages: this.messages,
      onFinish
    });

    return result.toTextStreamResponse();
  }

  // Optional: Customize message persistence
  async onStateUpdate(state, source) {
    console.log('Chat state updated:', this.messages.length, 'messages');
  }
}
Human-in-the-Loop(HITL)
export class ApprovalAgent extends Agent {
  async processRequest(data: any) {
    // Process automatically
    const processed = await this.autoProcess(data);

    // If confidence low, request human review
    if (processed.confidence < 0.8) {
      this.setState({
        ...this.state,
        pendingReview: {
          data: processed,
          requestedAt: Date.now(),
          status: 'pending'
        }
      });

      // Send notification to human
      await this.notifyHuman(processed);

      return { status: 'pending_review', id: processed.id };
    }

    // High confidence, proceed automatically
    return this.complete(processed);
  }

  async approveReview(id: string, approved: boolean) {
    const pending = this.state.pendingReview;

    if (approved) {
      await this.complete(pending.data);
    } else {
      await this.reject(pending.data);
    }

    this.setState({
      ...this.state,
      pendingReview: null
    });
  }
}
ツールの概念

ツールを使うと、エージェントは外部サービスと連携できます。

export class ToolAgent extends Agent {
  // Tool: Search flights
  async searchFlights(from: string, to: string, date: string) {
    const response = await fetch(`https://api.flights.com/search`, {
      method: 'POST',
      body: JSON.stringify({ from, to, date })
    });
    return response.json();
  }

  // Tool: Book flight
  async bookFlight(flightId: string, passengers: any[]) {
    const response = await fetch(`https://api.flights.com/book`, {
      method: 'POST',
      body: JSON.stringify({ flightId, passengers })
    });
    return response.json();
  }

  // Tool: Send confirmation email
  async sendEmail(to: string, subject: string, body: string) {
    // Use email service
    return { sent: true };
  }

  // Orchestrate tools
  async bookTrip(params: any) {
    const flights = await this.searchFlights(params.from, params.to, params.date);
    const booking = await this.bookFlight(flights[0].id, params.passengers);
    await this.sendEmail(params.email, "Booking Confirmed", `Booking ID: ${booking.id}`);

    return booking;
  }
}
マルチエージェントのオーケストレーション
interface Env {
  ResearchAgent: AgentNamespace<ResearchAgent>;
  WriterAgent: AgentNamespace<WriterAgent>;
  EditorAgent: AgentNamespace<EditorAgent>;
}

export class OrchestratorAgent extends Agent<Env> {
  async createArticle(topic: string) {
    // 1. Research agent gathers information
    const researcher = getAgentByName<Env, ResearchAgent>(
      this.env.ResearchAgent,
      `research-${topic}`
    );
    const research = await (await researcher).research(topic);

    // 2. Writer agent creates draft
    const writer = getAgentByName<Env, WriterAgent>(
      this.env.WriterAgent,
      `writer-${topic}`
    );
    const draft = await (await writer).write(research);

    // 3. Editor agent reviews
    const editor = getAgentByName<Env, EditorAgent>(
      this.env.EditorAgent,
      `editor-${topic}`
    );
    const final = await (await editor).edit(draft);

    return final;
  }
}

重要なルール

必ず実施すること ✅
  1. Agent クラスをエクスポートする - バインディングを機能させるにはエクスポートが必要です
  2. v1 マイグレーションに new_sqlite_classes を含める - SQLite は後から追加できません
  3. バインディング名をクラス名と一致させる - 「binding not found」エラーを防ぎます
  4. Agent ではなく Worker で認証する - セキュリティ上のベストプラクティスです
  5. SQL にはタグ付きテンプレートリテラルを使う - SQL インジェクションを防ぎます
  6. WebSocket の切断を処理する - 状態は保持されますが、接続は保持されません
  7. スケジュール済みタスクのコールバックが存在することを確認する - メソッドがない場合はエラーが発生します
  8. グローバルに一意なインスタンス名を使う - 同じ名前はグローバルで同じエージェントを指します
  9. 状態のサイズ制限を確認する - エージェントごとの合計上限は 1GB です
  10. タスクのペイロードサイズを監視する - スケジュール済みタスクごとの上限は 2MB です
  11. ワークフローバインディングを正しく使う - wrangler.jsonc で設定する必要があります
  12. 挿入前に Vectorize インデックスを作成する - メタデータのフィルタリングに必要です
  13. ブラウザーのインスタンスを閉じる - リソースリークを防ぎます
  14. 永続化には setState() を使う - this.state を変更するだけでは不十分です
  15. まずローカルでマイグレーションをテストする - マイグレーションはアトミックであり、ロールバックできません
絶対にしないこと ❌
  1. デプロイ済みの既存クラスに SQLite を追加しない - 最初のマイグレーションに含める必要があります
  2. マイグレーションを段階的にデプロイしない - アトミックにのみ実行できます
  3. Worker での認証を省略しない - エージェントにアクセスする前に必ず認証してください
  4. SQL 文字列を手動で構築しない - タグ付きテンプレートを使ってください
  5. エージェントごとに 1GB を超える状態を保存しない - 厳格な上限です
  6. 存在しないコールバックを指定してタスクをスケジュールしない - 実行時エラーになります
  7. 同じ名前なら別のエージェントだと思い込まない - グローバルに一意です
  8. MCP に SSE を使わない - 非推奨です。/mcp トランスポートを使ってください
  9. ブラウザーバインディングを忘れない - ウェブ閲覧に必要です
  10. this.state を直接変更しない - 代わりに setState() を使ってください

既知の問題の防止

このスキルは、文書化されている 15 件以上の問題を防ぎます。

問題 1:マイグレーションがアトミックでない

エラー:「Cannot gradually deploy migration」 ソース:https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ 理由:マイグレーションはすべてのインスタンスに同時に適用されます 防止策:コード変更とは独立してマイグレーションをデプロイし、npx wrangler versions deploy を使います

問題 2:new_sqlite_classes がない

エラー:「Cannot enable SQLite on existing class」 ソース:https://developers.cloudflare.com/agents/api-reference/configuration/ 理由:最初のマイグレーションで SQLite を有効にする必要があります 防止策:タグ「v1」のマイグレーションに new_sqlite_classes を含めます

問題 3:Agent クラスがエクスポートされていない

エラー:「Binding not found」または「Cannot access undefined」 ソース:https://developers.cloudflare.com/agents/api-reference/agents-api/ 理由:Durable Objects ではクラスのエクスポートが必要です 防止策:export class MyAgent extends Agent(export キーワードを付ける)を使います

問題 4:バインディング名が一致しない

エラー:「Binding 'X' not found」 ソース:https://developers.cloudflare.com/agents/api-reference/configuration/ 理由:バインディング名はクラス名と完全に一致する必要があります 防止策:wrangler.jsonc で name と class_name が同一であることを確認します

問題 5:グローバルな一意性が理解されていない

エラー:エージェントのインスタンスで予期しない動作が発生する ソース:https://developers.cloudflare.com/agents/api-reference/agents-api/ 理由:同じ名前を指定すると、常にグローバルで同じエージェントインスタンスが返されます 防止策:インスタンス名には一意の識別子(userId、sessionId)を使います

Issue 6: WebSocket の状態が永続化されない

エラー: 切断後に接続状態が失われます 参照元: https://developers.cloudflare.com/agents/api-reference/websockets/ 原因: WebSocket 接続は永続化されませんが、エージェントの状態は永続化されます 予防策: 接続状態ではなく、setState() を使って重要なデータをエージェントの状態に保存してください

Issue 7: スケジュールされたタスクのコールバックが存在しない

エラー: 「Agent にメソッド X が存在しません」 参照元: https://developers.cloudflare.com/agents/api-reference/schedule-tasks/ 原因: this.schedule() が定義されていないメソッドを呼び出します 予防策: スケジュールする前に、コールバックメソッドが存在することを確認してください

Issue 8: 状態のサイズ制限を超過

エラー: 「データベースの最大サイズを超過しました」 参照元: https://developers.cloudflare.com/agents/api-reference/store-and-sync-state/ 原因: エージェントの状態とスケジュールされたタスクの合計が 1GB を超えています 予防策: 状態のサイズを監視し、大きなデータには外部ストレージ(D1、R2)を使用してください

Issue 9: スケジュールされたタスクが大きすぎる

エラー: 「タスクのペイロードが 2MB を超えています」 参照元: https://developers.cloudflare.com/agents/api-reference/schedule-tasks/ 原因: 各タスクは、2MB の制限があるデータベース行に対応します 予防策: タスクのペイロードを最小限に抑え、大きなデータはエージェントの状態または SQL に保存してください

Issue 10: Workflow バインディングが未設定

エラー: 「undefined のプロパティ 'create' を読み取れません」 参照元: https://developers.cloudflare.com/agents/api-reference/run-workflows/ 原因: wrangler.jsonc で Workflow バインディングが設定されていません 予防策: this.env.WORKFLOW を使用する前に Workflow バインディングを追加してください

Issue 11: Browser バインディングが必要

エラー: 「BROWSER バインディングが undefined です」 参照元: https://developers.cloudflare.com/agents/api-reference/browse-the-web/ 原因: Browser Rendering には明示的なバインディングが必要です 予防策: wrangler.jsonc に "browser": { "binding": "BROWSER" } を追加してください

Issue 12: Vectorize インデックスが見つからない

エラー: 「インデックスが存在しません」 参照元: https://developers.cloudflare.com/agents/api-reference/rag/ 原因: 使用する前に Vectorize インデックスを作成する必要があります 予防策: エージェントをデプロイする前に wrangler vectorize create を実行してください

Issue 13: MCP トランスポートの混同

エラー: 「SSE トランスポートは非推奨です」 参照元: https://developers.cloudflare.com/agents/model-context-protocol/transport/ 原因: SSE トランスポートはレガシーであり、ストリーミング可能な HTTP が推奨されています 予防策: /sse ではなく、MyMCP.serve('/mcp') を使って /mcp エンドポイントを使用してください

Issue 14: 認証のバイパス

エラー: セキュリティ上の脆弱性 参照元: https://developers.cloudflare.com/agents/api-reference/calling-agents/ 原因: Worker ではなく Agent 内で認証を行っています 予防策: getAgentByName() を呼び出す前に、必ず Worker で認証してください

Issue 15: インスタンス名のエラー

エラー: ユーザー間のデータ漏えい 参照元: https://developers.cloudflare.com/agents/api-reference/calling-agents/ 原因: インスタンス名の付け方が不適切なため、別のエージェントへのアクセスが可能になっています 予防策: user-${userId} のような名前空間付きの名前を使用し、所有権を検証してください


依存関係

必須
  • cloudflare-worker-base - 基盤(Hono、Vite、Workers のセットアップ)
任意(機能別)
  • cloudflare-workers-ai - Workers AI のモデル呼び出し用
  • cloudflare-vectorize - Vectorize を使った RAG 用
  • cloudflare-d1 - エージェントの状態以外の追加の永続ストレージ用
  • cloudflare-r2 - ファイルストレージ用
  • cloudflare-queues - メッセージキュー用
NPM パッケージ
  • agents - Agents SDK(必須)
  • @modelcontextprotocol/sdk - MCP サーバーの構築用
  • @cloudflare/puppeteer - ウェブブラウジング用
  • ai - モデル呼び出し用の AI SDK
  • @ai-sdk/openai - OpenAI モデル
  • @ai-sdk/anthropic - Anthropic モデル

公式ドキュメント


同梱リソース

テンプレート(templates/)
  • wrangler-agents-config.jsonc - 設定の完全な例
  • basic-agent.ts - 最小構成の HTTP エージェント
  • websocket-agent.ts - WebSocket ハンドラー
  • state-sync-agent.ts - 状態管理のパターン
  • scheduled-agent.ts - タスクのスケジューリング
  • workflow-agent.ts - ワークフローとの連携
  • browser-agent.ts - Web ブラウジング
  • rag-agent.ts - RAG の実装
  • chat-agent-streaming.ts - ストリーミングチャット
  • calling-agents-worker.ts - エージェントのルーティング
  • react-useagent-client.tsx - React クライアント
  • mcp-server-basic.ts - MCP サーバー
  • hitl-agent.ts - Human-in-the-loop
リファレンス(references/)
  • agent-class-api.md - Agent クラスの完全なリファレンス
  • client-api-reference.md - ブラウザークライアント API
  • state-management-guide.md - 状態と SQL の詳細ガイド
  • websockets-sse.md - WebSocket と SSE の比較
  • scheduling-api.md - タスクスケジューリングの詳細
  • workflows-integration.md - Workflows ガイド
  • browser-rendering.md - Web ブラウジングのパターン
  • rag-patterns.md - RAG のベストプラクティス
  • mcp-server-guide.md - MCP サーバー開発
  • mcp-tools-reference.md - MCP ツール API
  • hitl-patterns.md - Human-in-the-loop ワークフロー
  • best-practices.md - 本番環境向けのパターン
例(examples/)
  • chat-bot-complete.md - 完全なチャットエージェント
  • multi-agent-workflow.md - エージェントのオーケストレーション
  • scheduled-reports.md - 定期タスク
  • browser-scraper-agent.md - Web スクレイピング
  • rag-knowledge-base.md - RAG システム
  • mcp-remote-server.md - 本番環境向け MCP サーバー

最終確認日: 2025-10-21 パッケージバージョン: agents@latest 準拠基準: Cloudflare Agents SDK 公式ドキュメント

コメント