mcpField
I
integrate-fusion-agent

integrate-fusion-agent

github.com/cognitedata開発61.7k 回インストール1 日前に更新
リポジトリ

integrate-fusion-agent について

integrate-fusion-agentは、Cognite FusionアプリにAtlasサイドバーとエージェント機能を組み込むためのスキルです。`@cognite/app-sdk`を使ったホスト接続、コンテキスト付きメッセージ、アプリ固有のリソースやアクションを案内します。

主な機能

  1. 01Fusionホストアプリへの接続
  2. 02状況に応じたAtlasサイドバーの表示
  3. 03アプリのコンテキスト付きメッセージ送信
  4. 04エージェント用リソースとアクションの定義
  5. 05変更アクションに事前承認を要求

活用シーン

  1. 01FusionアプリにAtlasの起動ボタンを追加
  2. 02選択項目のコンテキストをAtlasに送信
  3. 03エージェントにアプリの状態と操作を公開

MCP 対応

`@cognite/app-sdk`を通じてエージェントサーバーを登録し、ホストエージェントにリソースとアクションを提供する方法を説明します。

インストール

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

$npx skills add https://github.com/cognitedata/builder-skills --skill integrate-fusion-agent

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

詳細

Atlas / EOS サイドバーの統合

デフォルトの AI 経路は、@cognite/app-sdk を介したプラットフォームの Atlas サイドバー(EOS / Fusion PAIA)です。useAtlasChat を埋め込んだり、atlas-agent をベンダーとして組み込んだり、サードパーティの LLM API を呼び出したりしないでください。

integrate-atlas-chat を使うのは、ユーザーがアプリ内チャットを明示的に必要としており、かつ connectToHostApp でサイドバーを利用できない場合に限ります(スタンドアロンアプリでは常に拒否されます)。このためのマニフェストフィールドはありません。

必要なものだけを実装します。

  1. 開く — Topbar の Atlas ボタン。アプリ内でのトリガーには sendAgentLayoutMode を使用
  2. メッセージ — コンテキストを注入する sendAgentMessage
  3. サーバー — リソース(アプリの状態)とアクション(ツール)

ステップ 0 — アプリを読む

  • package.json — パッケージマネージャー、@cognite/app-sdk
  • src/App.tsx — 構造、既存の SDK 使用状況

3 つの機能のうちどれが必要か確認します。ユーザーがすでに強く希望しているのでない限り、アプリ内チャットを提案しないでください。


ステップ 1 — インストール

pnpm add @cognite/app-sdk(または npm/yarn)。最低バージョンは 0.3.1 です。


ステップ 2 — ホストに接続する

connectToHostApp は Fusion の外部(スタンドアロンの vite dev)では拒否されます。そのエラーをキャッチし、api が null のときはエージェントのトリガーを非表示にします。

Comlink のプロキシは呼び出し可能です。setApi(proxy) とすると、React はプロキシを更新関数として扱い、Promise を保存してしまいます。必ず setApi(() => resolvedApi) としてください。

// src/hooks/useHostApp.ts
import { useState, useEffect } from 'react';
import { connectToHostApp, type HostAppAPI } from '@cognite/app-sdk';

export function useHostApp(): HostAppAPI | null {
  const [api, setApi] = useState<HostAppAPI | null>(null);

  useEffect(() => {
    connectToHostApp({ applicationName: 'my-app' })
      .then(({ api: resolvedApi }) => setApi(() => resolvedApi))
      .catch(() => { /* outside Fusion — no-op */ });
  }, []);

  return api;
}

ルートで呼び出し、api を下位コンポーネントに渡すか、コンテキスト経由で渡します。typeof proxy.method === 'function' は常に true です。typeof による機能検出はせず、try/catch を使ってください。


ステップ 3 — サイドバーを開く

主な起動方法は Aura Topbar Atlas です(systemActions.atlas.visible: true。use-topbar を参照)。「アシスタントを開く」コントロールを追加しないでください。

sendAgentLayoutMode はコンテキストに応じたトリガー専用です(sidebar | fullscreen | closed)。

await api.sendAgentLayoutMode({ mode: 'sidebar' });

ステップ 4 — メッセージを送信する

sendAgentLayoutMode と組み合わせて使います。アイテムから新しいタスクを開始する場合は newSession: true を指定し、スレッドを続ける場合は省略します。名前、ID、状態はメッセージに含めます。サイドバーで 1 回の応答にまとめ、クエリ結果の各行について N 回の生成を行わないでください。

await api.sendAgentLayoutMode({ mode: 'sidebar' });
await api.sendAgentMessage({
  message: `Analyse the schedule for "${itemName}" and suggest how to reduce total duration.`,
  newSession: true,
});

ステップ 5 — エージェントサーバー

マウント時に登録し、アンマウント時に登録を解除します。ファクトリーはサービスを引数として受け取るようにし、React なしで単体テストできるようにします。

src/features/agent/
  agentActions.ts     — (deps) => Action[]
  agentResources.ts   — (deps) => Resource[]
  useAgentServer.ts   — register / unregister

リソースの read() は { type: 'json', data }(推奨)または { type: 'text', text } を返します。description はドキュメント文字列のように記述します。

// src/features/agent/agentResources.ts
import { createAgentResource } from '@cognite/app-sdk';

export function buildAgentResources(storage: StorageService) {
  return [
    createAgentResource({
      uri: 'my-app://current-state',
      name: 'Current application state',
      description:
        'Items currently visible, their statuses, and active filters. Read before answering questions about what the user is looking at.',
      async read() {
        return [{ type: 'json', data: storage.getAll() }];
      },
    }),
  ];
}

アクション名は snake_case とし、パラメーターには Zod を使い、すべてのフィールドに .describe() を指定します。エージェントは呼び出し前に確認を行いません。状態を変更するアクションは、その旨を description に明記し、事前にユーザーの承認を求める必要があります。

// src/features/agent/agentActions.ts
import { createAgentAction } from '@cognite/app-sdk';
import { z } from 'zod';

export function buildAgentActions(dataService: DataService) {
  return [
    createAgentAction({
      name: 'get_item_details',
      description: 'Full details for an item by ID, including history.',
      parameters: z.object({
        item_id: z.string().describe('The ID of the item to retrieve'),
      }),
      async handler({ item_id }) {
        const item = await dataService.getItem(item_id);
        return { content: [{ type: 'json', data: item }] };
      },
    }),
  ];
}
createAgentAction({
  name: 'update_item_status',
  description:
    'Update item status. Call ONLY when the user has explicitly approved the change.',
  parameters: z.object({
    item_id: z.string().describe('The item to update'),
    status: z.enum(['active', 'closed', 'pending']).describe('The new status'),
  }),
  async handler({ item_id, status }) {
    storage.updateStatus(item_id, status);
    return { content: [{ type: 'json', data: { success: true } }] };
  },
})
// src/features/agent/useAgentServer.ts
import { useEffect } from 'react';
import { createAgentServer, registerAgentServer, type HostAppAPI } from '@cognite/app-sdk';
import { buildAgentActions } from './agentActions';
import { buildAgentResources } from './agentResources';
import { useStorageService } from '../storage/StorageServiceContext';
import { useDataService } from '../data/DataServiceContext';

export function useAgentServer(api: HostAppAPI | null): void {
  const storage = useStorageService();
  const dataService = useDataService();

  useEffect(() => {
    if (!api) return;
    const server = createAgentServer({
      uri: 'my-app', // Fusion namespaces with instance ID
      actions: buildAgentActions(dataService),
      resources: buildAgentResources(storage),
    });
    void registerAgentServer(api, server).catch((err: unknown) => {
      console.warn('[agent] registerAgentServer failed:', err);
    });
    return () => {
      void api.unregisterAgentServer('my-app').catch((err: unknown) => {
        console.warn('[agent] unregisterAgentServer failed:', err);
      });
    };
  }, [api, storage, dataService]);
}

ステップ 6 — 連携する

function App() {
  const api = useHostApp();
  useAgentServer(api);

  return (
    <AppLayout>
      <MainContent onAnalyseItem={async (item) => {
        if (!api) return;
        await api.sendAgentLayoutMode({ mode: 'sidebar' });
        await api.sendAgentMessage({
          message: `Analyse "${item.name}" (id: ${item.id}).`,
          newSession: true,
        });
      }} />
    </AppLayout>
  );
}

ファクトリーを直接テストします。

const [getItemAction] = buildAgentActions({
  getItem: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
});
const result = await getItemAction.handler({ item_id: '1' });
expect(result.content[0].data).toEqual({ id: '1', name: 'Test' });

厳守事項 — クエリ結果に対する LLM 呼び出し

DMS/SDK の行に対して、チャットの生成処理(Atlas の agents/chat、OpenAI/Anthropic、useAtlasChat().send)をマッピングしないでください。sendAgentMessage を 1 回使うか、サイドバーのエージェントが読み取れるリソースを優先します。

アイテムごとの生成が明示的な製品要件である場合(デフォルトでは不要):

ルール上限
デフォルトユーザーが開始したアクション 1 回につき、生成は5 回
上限50 回 — これを超えるコードを生成してはいけません
キャッシュspace:externalId:lastUpdatedTime。キャッシュヒットでは予算を消費しません
バッチN 回の呼び出しではなく、N 個のアイテムを対象とするプロンプトを 1 つ使用
トリガーユーザー操作によるもののみ。レンダリング時、ポーリング時、または上限のないリストでは実行しない
UX上限によって対象セットが切り詰められた場合は、その旨を伝える

禁止事項:items.map((row) => complete(row))、クエリページの結果に対する Promise.all による生成。


チェックリスト

  • Topbar Atlas ランチャー(use-topbar)。アプリ内チャットウィジェットは使わない
  • @cognite/[email protected]+。setApi(() => resolvedApi) を使い、Fusion の外部での拒否をキャッチする
  • api が null のときはトリガーを非表示にする。サーバーの登録と解除には .catch() を付ける
  • リソースの説明に対象とタイミングを記載する。アクション名は snake_case とし、状態を変更するアクションには事前承認を必須とする
  • ファクトリーはサービスを引数として受け取る。行に対する LLM 呼び出しは上限(5 回/最大 50 回)を設け、キャッシュがあれば利用する

コメント