npm package: @super_studio/ecforce-ai-agent-react
Description: このドキュメントでは、`@super_studio/ecforce-ai-agent-react` と `@super_studio/ecforce-ai-agent-server` を使って、Webアプリに AI Agent のチャット UI とサーバー連携を組み込む手順を説明します。
Latest version: 1.2.0

# セットアップ

このドキュメントでは、`@super_studio/ecforce-ai-agent-react` と `@super_studio/ecforce-ai-agent-server` を使って、Webアプリに AI Agent のチャット UI とサーバー連携を組み込む手順を説明します。

想定構成は以下です。

- フロントエンドで `@super_studio/ecforce-ai-agent-react` を使ってチャット UI を表示する
- バックエンドで `@super_studio/ecforce-ai-agent-server` を使ってセッショントークンを発行する
- 必要に応じて MCP エンドポイントでトークンを検証し、アプリ固有のツールを公開する

## 1. インストール

```bash
pnpm add @super_studio/ecforce-ai-agent-react @super_studio/ecforce-ai-agent-server
```

## 2. 環境変数を設定する

通常の本番環境 (`https://agent.ec-force.com`) に接続するだけであれば、`NEXT_PUBLIC_CHATBOT_URL` は不要です。

`@super_studio/ecforce-ai-agent-server` を使う場合は、API キーとして `AI_AGENT_API_KEY` を設定してください。

MCP を実装する場合は、あわせて `MCP_TOKEN_SECRET` も必要です。

`AI_AGENT_API_KEY` と `MCP_TOKEN_SECRET` は AI チームから共有された値を使用してください。

```env
AI_AGENT_API_KEY=your-api-key
MCP_TOKEN_SECRET=your-mcp-token-secret
```

開発環境やプレビュー環境など、本番以外の AI Agent に接続したい場合のみ、接続先 URL を設定します。

開発用のエンドポイントは以下です。

```env
NEXT_PUBLIC_CHATBOT_URL=https://dev-agent.ec-force.com
AI_AGENT_API_ENDPOINT=https://dev-agent.ec-force.com
```

- `NEXT_PUBLIC_CHATBOT_URL`: フロントエンドの `ChatbotSheet` / `ChatbotFrame` を dev / preview 環境へ向けたい場合に使います
- `AI_AGENT_API_ENDPOINT`: サーバー側の `createClient()` を dev / preview 環境へ向けたい場合に使います
- `AI_AGENT_API_KEY`: サーバー SDK から API を呼ぶために必須です
- `MCP_TOKEN_SECRET`: MCP トークンの署名・検証に使います。MCP を実装する場合に必須です

## 3. `@super_studio/ecforce-ai-agent-react` のセットアップ

### 3-1. スタイルを読み込む

グローバル CSS で、パッケージが提供するスタイルを読み込みます。

```css
@import "@super_studio/ecforce-ai-agent-react/preset.css";
@import "@super_studio/ecforce-ai-agent-react/chatbot-sheet.css";
```

### 3-2. アプリを `ChatbotProvider` でラップする

チャット UI や `useChatbot()` を使う場合は、アプリ全体または必要な範囲を `ChatbotProvider` で囲みます。

```tsx
"use client";

import { ChatbotProvider } from "@super_studio/ecforce-ai-agent-react";

type Props = {
  children: React.ReactNode;
};

export function AppProviders({ children }: Props) {
  return <ChatbotProvider>{children}</ChatbotProvider>;
}
```

### 3-3. チャット UI を表示する

`ChatbotSheet` にアプリ名、セッション取得関数を渡します。通常は本番環境へ接続されるため、URL 指定は不要です。

```tsx
"use client";

import { ChatbotSheet } from "@super_studio/ecforce-ai-agent-react";

export function Chatbot() {
  return (
    <ChatbotSheet
      appName="your-app-name"
      getSession={async () => {
        const res = await fetch("/api/agent/session", {
          method: "POST",
        });

        if (!res.ok) {
          throw new Error("Failed to create AI agent session");
        }

        return (await res.json()) as {
          token: string;
          expiresAt: string;
        };
      }}
    />
  );
}
```

#### 各 props の役割

- `url`: AI Agent サーバーのベース URL。dev / preview 環境に向けたい場合のみ指定します
- `appName`: 呼び出し元アプリを識別する名前
- `getSession`: AI Agent に接続するためのセッショントークンを返す関数

dev / preview 環境へ接続する場合のみ、以下のように `url` を指定してください。

```tsx
<ChatbotSheet
  url={process.env.NEXT_PUBLIC_CHATBOT_URL}
  appName="your-app-name"
  getSession={...}
/>
```

### 3-4. アプリ側の状態や文脈を AI Agent に渡す

`useChatbot()` の `sendAppMessage()` を使うと、ホストアプリから AI Agent に対してプログラム経由でメッセージを送れます。

典型的には、以下のような場面で使います。

- 現在表示中の商品、注文、顧客などを前提に AI へ依頼したい
- ユーザーが押したボタンに応じて、定型プロンプトをそのまま送信したい
- 画面には見せたくない補足情報を AI にだけ渡したい
- 画像やファイル URL を添付して、その内容を前提に会話を始めたい

`sendAppMessage()` を呼ぶと、チャットが閉じていても自動で開きます。チャット iframe の初期化がまだ終わっていない場合でも、メッセージは内部で一時保持され、準備完了後に送信されます。

```tsx
"use client";

import { useChatbot } from "@super_studio/ecforce-ai-agent-react";

export function SendContextButton() {
  const { sendAppMessage } = useChatbot();

  return (
    <button
      onClick={() => {
        sendAppMessage({
          title: "売上ダッシュボードの要約",
          message:
            "現在の売上ダッシュボードを前提に、注目すべき変化を3つ挙げてください。",
          hiddenMessage:
            "dashboardId=dashboard_123, dashboardName=売上ダッシュボード",
        });
      }}
    >
      コンテキストを送信
    </button>
  );
}
```

#### `sendAppMessage()` の payload

```ts
type SendAppMessagePayload = {
  title?: string;
  message: string;
  hiddenMessage?: string;
  startOnNewChat?: boolean;
  attachments?: {
    fileName: string;
    mediaType: string;
    url: string;
  }[];
};
```

- `message`: AI Agent に実際に依頼したい本文です。必須です
- `title`: 依頼の要約タイトルです。UI 上でタスク名のように扱いたいときに使います
- `hiddenMessage`: ユーザーに見せなくてよい補足文脈です。内部 ID、画面状態、回答方針などを渡す用途に向いています
- `startOnNewChat`: `true` なら新しいチャットを開始して送信します。省略時は `true` です。現在の会話の続きに送りたい場合だけ `false` を指定します
- `attachments`: AI Agent に参照させたい添付ファイルです。公開 URL で取得できるファイルを渡してください

#### visible な依頼と hidden な文脈の使い分け

基本的には、ユーザーが見ても自然な内容を `message` に書き、アプリ内部の補足情報を `hiddenMessage` に分けるのがおすすめです。

- `message`: 「この注文の要点を要約して」
- `hiddenMessage`: `orderId=ord_123`, `status=paid`, `customerTier=gold`

このように分けると、ユーザー向けの依頼文を保ちながら、AI 側には必要な文脈も渡せます。

#### 新しいチャットで送るか、現在の会話に続けるか

新しい依頼を独立した会話として扱いたい場合は、デフォルトのまま `startOnNewChat: true` を使います。すでに進行中の会話に追加指示したい場合は `false` を指定します。

```tsx
sendAppMessage({
  title: "続きの依頼",
  message: "最も良い案を、2行のヒーローコピーに言い換えてください。",
  startOnNewChat: false,
});
```

#### 添付ファイルを渡す例

```tsx
sendAppMessage({
  title: "バナー案のレビュー",
  message: "添付画像を確認し、改善案を3つ提案してください。",
  attachments: [
    {
      fileName: "campaign-banner.png",
      mediaType: "image/png",
      url: "https://example.com/campaign-banner.png",
    },
  ],
});
```

これにより、AI Agent 側で現在の画面や対象データに応じた応答をしやすくなります。

### 3-5. `useClientTool()` でクライアントツールを登録する

ブラウザ上で実行したい処理を AI Agent から呼ばせたい場合は、`useClientTool()` を使います。

典型的には、以下のような用途で使います。

- 現在開いている画面の React state を参照して結果を返す
- ユーザー確認付きでクライアント側の state を更新する
- クライアント側の UI 操作をトリガーする

`useClientTool()` は `ChatbotProvider` 配下で呼び出してください。引数定義には `zod` を使います。

```tsx
"use client";

import { useState } from "react";
import { z } from "zod";
import { useClientTool } from "@super_studio/ecforce-ai-agent-react";

export function CartToolRegistration() {
  const [selectedSku, setSelectedSku] = useState("sku_001");
  const [isPinned, setIsPinned] = useState(false);

  useClientTool({
    name: "checkSelectedSku",
    displayName: "選択中SKU確認",
    description: "現在画面で選択中の商品 SKU を返します。",
    summary: "選択中の SKU を返します。",
    parameters: z.object({}),
    execute: () => {
      return {
        selectedSku,
        isPinned,
      };
    },
  });

  useClientTool({
    name: "togglePinnedState",
    displayName: "ピン留め切り替え",
    description: "現在の商品をピン留めするかどうかを切り替えます。",
    summary: "ピン留め状態を更新します。",
    requireConfirmation: true,
    parameters: z.object({
      pinned: z.boolean().describe("更新後のピン留め状態"),
    }),
    execute: ({ pinned }) => {
      setIsPinned(pinned);

      return {
        selectedSku,
        isPinned: pinned,
      };
    },
  });

  return (
    <div>
      <p>selectedSku: {selectedSku}</p>
      <p>isPinned: {String(isPinned)}</p>
      <button onClick={() => setSelectedSku("sku_002")}>SKU を切り替える</button>
    </div>
  );
}
```

#### `useClientTool()` の options

```ts
type UseClientToolOptions<TParameters extends z.ZodTypeAny> = {
  name: string;
  displayName: string;
  description: string;
  summary?: string;
  requireConfirmation?: boolean;
  parameters: TParameters;
  execute: (args: z.output<TParameters>) => Promise<unknown> | unknown;
};
```

- `name`: ツール識別子です。内部では自動で `page_` プレフィックスが付きます
- `displayName`: UI 上で表示するツール名です
- `description`: AI Agent が参照する詳細説明です。何をするツールか、どんな引数を受けるかを明確に書いてください
- `summary`: ユーザー向けの短い要約です
- `requireConfirmation`: `true` の場合、実行前にユーザー確認を要求します
- `parameters`: ツール引数を表す `zod` スキーマです
- `execute`: 検証済み引数を受け取ってクライアント側の処理を実行する関数です

#### 補足

- `name` に `page_` を自分で付けても動きますが、通常は素の名前で問題ありません
- `execute` の返り値は AI Agent に渡されるため、JSON 化しやすい値を返すのがおすすめです
- コンポーネントがアンマウントされるとツール登録は自動で解除されます

## 4. `@super_studio/ecforce-ai-agent-server` のセットアップ

### 4-1. サーバー側で AI Agent セッションを発行する

フロントエンドの `getSession()` から呼ばれる API を用意し、`createClient()` を使って AI Agent サーバーへセッション作成を依頼します。

このとき、サーバー環境に `AI_AGENT_API_KEY` の設定が必要です。

以下は Next.js App Router の Route Handler 例です。

```ts
import { NextResponse } from "next/server";
import { createClient } from "@super_studio/ecforce-ai-agent-server";

const aiClient = createClient();

export async function POST() {
  // ここは自分の認証基盤に置き換えてください
  const currentUser = {
    email: "user@example.com",
    projectId: "project_123",
  };

  const session = await aiClient.internalChat.createSession({
    email: currentUser.email,
    projectId: currentUser.projectId,
  });

  return NextResponse.json(session);
}
```

dev / preview 環境へ接続する場合は、`AI_AGENT_API_ENDPOINT` を設定するか、`createClient({ baseUrl: "..." })` を使って接続先を上書きしてください。

`getSession()` には、この API が返す以下の形式の値をそのまま返せば動作します。

```ts
type AgentSession = {
  token: string;
  expiresAt: string;
};
```

### 4-2. MCP エンドポイントでトークンを検証する

アプリ固有のツールを AI Agent に公開する場合は、MCP エンドポイント側で受け取ったトークンを検証します。

このとき、サーバー環境に `MCP_TOKEN_SECRET` の設定が必要です。

`@super_studio/ecforce-ai-agent-server/mcp-auth` には、MCP トークンを扱うためのユーティリティが含まれています。

```ts
import { getMcpToken } from "@super_studio/ecforce-ai-agent-server/mcp-auth";

export async function GET(req: Request) {
  const mcpToken = getMcpToken(req);

  if (!mcpToken) {
    return new Response("Unauthorized", { status: 401 });
  }

  return new Response("ok");
}
```

トークンの中身を利用したい場合は `decodeMCPToken()` を使います。

```ts
import { decodeMCPToken } from "@super_studio/ecforce-ai-agent-server/mcp-auth";

export async function GET(req: Request) {
  const authHeader = req.headers.get("authorization");
  const token = authHeader?.replace(/^Bearer\s+/i, "");

  if (!token) {
    return new Response("Unauthorized", { status: 401 });
  }

  const payload = await decodeMCPToken(token);

  return Response.json({
    source: payload.source,
    user: payload.user,
  });
}
```