知らないと損かも!AG-UIを入門しよう!

# 前書き

昨年からAI関連のプロトコルが一気に増えてきました。

MCPことModel Context Protocolは、Anthropic社が2024年11月に発表し、今年の2月後半から急速に注目を集めています。

続いてA2A（Agent-to-Agentプロトコル）は、2025年4月9日頃にGoogleが発表し、対応サービスも徐々に増加してきています。

そして最新の動きとして、2025年5月12日には**AG-UI**（Agent-User Interaction Protocol）プロトコルも登場しました、今回はそれをキャッチアップしていきましょう。

![68220ca67d57591307be02ef_MCP_-AG-UI-Diagram_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/2fc2247c-5c27-46f5-a4b3-cc425b467f9f.jpeg)

## AG-UIの何が嬉しい？

AG-UIの最大の魅力は、Agentとユーザーを繋ぐ**UIの標準化**です。
これにより、開発者はAgentのフロントエンド実装に労力を費やさずに済みます。

これまでのAgent開発では、リアルタイムストリーミングやツール連携、状態管理などのフロントエンド実装が大きな負担でした。Streamlitは選択肢の一つでしたが、表現力に限界がありました。

AG-UIなら、LangGraph、Mastra、Amazon Bedrock Agent(対応予定)など異なるバックエンド技術でも同一のUI環境で利用可能です。単一のHTTPエンドポイントとJSONイベントストリームによる、シンプルで強力なプロトコルがこれを実現しています。
また、AG-UIはA2A（Agent-to-Agentプロトコル）やMCP（Model Context Protocol）と競合せず、これらと組み合わせることでより強力なAIシステムが構築できます。

![442547013-215227a5-9698-4d97-a46e-8904b95bfa08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/172b49b9-5237-4664-b5ef-b7756b9df487.png)

## ハンズオン

#### 成果物

AG-UI + Mastra + ReactでMCP搭載のエージェントアプリを構築します
![5A0E65FF-CF47-421E-88BB-BC2942997F7A.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/25800d64-93cd-40a9-9e17-d5b83d03322c.jpeg)

#### Mastraエージェントの初期化

```bash
npx create-mastra@latest
```
初期化プロセスで色々聞かれますが、全部任意で大丈夫です。
```
Need to install the following packages:
create-mastra@0.3.3
Ok to proceed? (y) 

┌  Mastra Create
│
◇  What do you want to name your project?
│  copilotkit-app
│
◇  Project created
│
◇  npm dependencies installed
│
◇  mastra installed
│
◇  Dependencies installed
│
◇  .gitignore added
│
└  Project created successfully

┌  Mastra Init
│
◇  Where should we create the Mastra files? (default: src/)
│  src/
│
◇  Choose components to install:
│  Agents, Workflows
│
◇  Add tools?
│  Yes
│
◇  Select default provider:
│  OpenAI
│
◇  Enter your openai API key?
│  Enter API key
│
◇  Enter your API key:
│  you-key
│
◇  Add example
│  Yes
│
◇  Make your AI IDE into a Mastra expert? (installs Mastra docs MCP server)
│  Windsurf
│
│  
│  Windsurf is already installed, skipping.
│
◇  
│
◇   ───────────────────────────────────────╮
│                                          │
│                                          │
│        Mastra initialized successfully!  │
│                                          │
│                                          │
├──────────────────────────────────────────╯
```

デフォルトではamazon bedrockプロバイダーインストールできないですが、使いたい方は下記の記事をご参照ください

https://qiita.com/Syoitu/items/3835a23f39969eab0111

その他必要のライブラリーをインストール

```bash
npm install @mastra/mcp@latest @copilotkit/runtime @mastra/client-js
```

#### エージェントの実装

```typescript:src/mastra/agents/index.ts
import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { LibSQLStore } from '@mastra/libsql';
import { MCPClient } from '@mastra/mcp';

const mcp = new MCPClient({
  servers: {
    tavily: {
      command: "npx",
      args: ["-y", "tavily-mcp@0.1.4"],
      env: {
        TAVILY_API_KEY: "you-key",
      },
    },
  },
});

export const searchAgent = new Agent({
  name: 'Search Agent',
  instructions: `
      You are a helpful search assistant that provides search results.

      Your primary function is to help users get search results. When responding:
      - Always ask for a topic if none is provided
      - If the topic name isn’t in English, please translate it
      - If giving a topic with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York")
      - Keep responses concise but informative

      Use the searchTool to fetch current search data.
      日本語で返信してください。
`,
  model: openai('gpt-4o'),
  tools: await mcp.getTools(),
  memory: new Memory({
    storage: new LibSQLStore({
      url: 'file:../mastra.db',
    }),
    options: {
      lastMessages: 10,
      semanticRecall: false,
      threads: {
        generateTitle: false,
      },
    },
  }),
});
  
```

mastraのメインファイルを修正します。

```typescript:src/mastra/index.ts
import { Mastra } from "@mastra/core/mastra";
import { createLogger } from "@mastra/core/logger";
import { LibSQLStore } from "@mastra/libsql";
import { registerApiRoute } from "@mastra/core/server";
import {
  CopilotRuntime,
  copilotRuntimeNodeHttpEndpoint,
  ExperimentalEmptyAdapter,
} from "@copilotkit/runtime";
import { MastraClient } from "@mastra/client-js";
import { searchAgent } from "./agents";
 
const serviceAdapter = new ExperimentalEmptyAdapter();
 
export const mastra = new Mastra({
  agents: { weatherAgent, searchAgent },
  storage: new LibSQLStore({
    url: ":memory:",
  }),
  logger: createLogger({
    name: "Mastra",
    level: "info",
  }),
  server: {
    apiRoutes: [
      registerApiRoute("/copilotkit", {
        method: 'POST',
        handler: async (c) => {
          // N.B. Current integration leverages MastraClient to fetch AGUI.
          // Future versions will support fetching AGUI from mastra context.
          const client = new MastraClient({
            baseUrl: "http://localhost:4111",
          });
 
          const runtime = new CopilotRuntime({
            agents: await client.getAGUI({ resourceId: "searchAgent" }),
          });
 
          const handler = copilotRuntimeNodeHttpEndpoint({
            endpoint: "/copilotkit",
            runtime,
            serviceAdapter,
          });
 
          return handler.handle(c.req.raw, {});
        },
      }),
    ],
  },
});
```

#### クライアントの実装

Viteを使ってReactのプロジェクトを初期化します。

```
% npm create vite@latest

> npx
> create-vite

│
◇  Project name:
│  client
│
◆  Select a framework:
│  ○ Vanilla
│  ○ Vue
│  ● React
│  ○ Preact
│  ○ Lit
│  ○ Svelte
│  ○ Solid
│  ○ Qwik
│  ○ Angular
│  ○ Marko
│  ○ Others
└
```
依存関係をインストールします。

```
npm install @copilotkit/react-core @copilotkit/react-ui
```

クライアントを修正する

```typescript:src/client/App.tsx
import { CopilotChat } from "@copilotkit/react-ui";
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/styles.css";

function App() {

  return (
      <CopilotKit
        runtimeUrl="http://localhost:4111/copilotkit"
        agent="searchAgent"
      >
        <CopilotChat
          labels={{
            title: "Your Assistant",
            initial: "Hi! 👋 How can I assist you today?",
          }}
        />
      </CopilotKit>
    );
}

export default App
```

このままではエラーが出るので、Mastraサーバーにcorsの設定を追加します。

```diff_typescript:src/mastra/index.ts
import { Mastra } from "@mastra/core/mastra";
import { createLogger } from "@mastra/core/logger";
import { LibSQLStore } from "@mastra/libsql";
import { registerApiRoute } from "@mastra/core/server";
import {
  CopilotRuntime,
  copilotRuntimeNodeHttpEndpoint,
  ExperimentalEmptyAdapter,
} from "@copilotkit/runtime";
import { MastraClient } from "@mastra/client-js";
import { searchAgent } from "./agents";
 
const serviceAdapter = new ExperimentalEmptyAdapter();
 
export const mastra = new Mastra({
  agents: { weatherAgent, searchAgent },
  storage: new LibSQLStore({
    url: ":memory:",
  }),
  logger: createLogger({
    name: "Mastra",
    level: "info",
  }),
  server: {
+    cors: {
+      origin: ["http://localhost:5173"], //クライアントのPORTに合わせて
+      allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
+      allowHeaders: ['Content-Type', 'Authorization', 'x-copilotkit-runtime-client-gql-version'],
+      credentials: true,
+    },
    apiRoutes: [
      registerApiRoute("/copilotkit", {
        method: 'POST',
        handler: async (c) => {
          // N.B. Current integration leverages MastraClient to fetch AGUI.
          // Future versions will support fetching AGUI from mastra context.
          const client = new MastraClient({
            baseUrl: "http://localhost:4111",
          });
 
          const runtime = new CopilotRuntime({
            agents: await client.getAGUI({ resourceId: "searchAgent" }),
          });
 
          const handler = copilotRuntimeNodeHttpEndpoint({
            endpoint: "/copilotkit",
            runtime,
            serviceAdapter,
          });
 
          return handler.handle(c.req.raw, {});
        },
      }),
    ],
  },
});
```
クライアントとMastraサーバーの両方を立ち上げれば、機能していることが確認できます。

![F69DE409-4867-4F19-8EBF-75F7E343270B.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/7ca4eebb-d8c8-4309-94a3-721584793592.jpeg)

# その他使い方

AG-UIを実装するためのcopilotkitというSDKも公開されており、
ユースケースに合わせて好きなクライアントコンポーネントを選んで利用できます。

https://docs.copilotkit.ai/guides/custom-look-and-feel/built-in-ui-components

現状、Amazon Bedrock Agentがまだ対応していないのは残念ですが、`LangGraph`と`Mastra`が同じクライアントで動作できるだけでも大きなメリットと言えるでしょう。

![009234A1-1A0E-4EB1-AE70-2D64F34EC362.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/a7c105f6-e874-4f29-a5a5-d32c6ef460e4.jpeg)


### CopilotPopup

例えばサービスの右下に配置するチャットボットは、`CopilotPopup`を使えば一瞬で実装できます。

```diff_typescript:client/src/App.tsx
import { CopilotChat, CopilotPopup } from "@copilotkit/react-ui";
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/styles.css";

function App() {

  return (
      <CopilotKit
        runtimeUrl="http://localhost:4111/copilotkit"
        agent="searchAgent"
      >
        <CopilotChat
          labels={{
            title: "Your Assistant",
            initial: "Hi! 👋 How can I assist you today?",
          }}
        />
+        <CopilotPopup
+          instructions={"You are assisting the user as best as you can. Answer in the best way possible given the data you have."}
+          labels={{
+            title: "Popup Assistant",
+            initial: "Need any help?",
+          }}
+        />
      </CopilotKit>
    );
}

export default App
```

![B6F4F16C-D8A5-4467-9161-254A66EBD6D7.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/99910b96-e6fd-447c-ba0b-fa0cedbec286.jpeg)

### tailwindcssと一緒に使える

SDKに構築済みのコンポネートのデザインを修正する際に、tailwindcssも使えます。

```bash
client % npm install tailwindcss @tailwindcss/vite
```

```diff_typescript:vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'

// https://vite.dev/config/
export default defineConfig({
+  plugins: [react(), tailwindcss()],
})
```

```diff_css:index.css
+ @import "tailwindcss";
```

```typescript:client/src/App.tsx
import {type HeaderProps, useChatContext, CopilotSidebar } from "@copilotkit/react-ui";
import { BookOpenIcon } from "@heroicons/react/24/outline";
import { CopilotKit } from "@copilotkit/react-core";
import "@copilotkit/react-ui/styles.css";

function Header({}: HeaderProps) {
  const { setOpen, icons, labels } = useChatContext();
 
  return (
    <div className="flex justify-between items-center p-4 bg-blue-500 text-white">
      <div className="w-24">
        <a href="/">
          <BookOpenIcon className="w-6 h-6 bg-white" />
        </a>
      </div>
      <div className="text-lg">{labels.title}</div>
      <div className="w-24 flex justify-end">
        <button onClick={() => setOpen(false)} aria-label="Close" type="button">
          {icons.headerCloseIcon}
        </button>
      </div>
    </div>
  );
};

function App() {

  return (
      <CopilotKit
      runtimeUrl="http://localhost:4111/copilotkit"
      agent="searchAgent">
        <CopilotSidebar Header={Header} />
      </CopilotKit>
    );
}

export default App
```

![E1B31A6C-9B09-4534-8DDE-D96FEAB2CF67.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/810f2b4e-97be-45f4-8e9a-a3e5ba1a8986.jpeg)

# 最後に

実際にクライアントからエージェントを呼び出す際、MCPサーバーを使用すると何回か失敗することがありました。原因はまだ特定できていませんが、今後の発展に期待したいと思います。

また、AG-UIとA2Aの発展次第では、クライアントとサーバーの実装を明確に分離することで、よりスケーラブルなシステム構築が可能になるかもしれません。

# 参考資料

https://docs.copilotkit.ai/guides/custom-look-and-feel/built-in-ui-components

https://github.com/ag-ui-protocol/ag-ui