ミツモアの Agentic AI Lab でAIエージェント関連の開発を担当している井藤です。
ミツモアでは、現場向け業務SaaS「プロワン」にAIエージェントを組み込む取り組みを進めています。その中で、AIがブラウザ上のUIを直接操作できる仕組み「クライアントサイドツール」を設計・実装しました。
本記事では、クライアントサイドツールの背景・アーキテクチャ・実装方法を、見積もり明細の操作デモを題材に紹介します。
背景:AIエージェントに「手足」を与える
LLM単体ではテキストを生成することしかできませんが、「ツール」を渡すことで、AIは外部のシステムを操作できるようになります。ツールは「AIが呼び出せる関数」のようなもので、これによってAIはデータ検索、API呼び出し、ファイル操作などを行える「エージェント」になります。
ミツモアでも、サーバーサイドのデータ操作にはMCP(Model Context Protocol)ツールを使っています。MCPツールはサーバー上で実行され、DBやAPIなどに直接アクセスします。
しかし、業務SaaSでAIが実際に「仕事」をするには、データの取得や更新だけでなく、ユーザーが普段行っている画面操作もできる必要があります。MCPツールだけでは解決できない課題がありました。
- フォームへの入力や画面遷移など、ブラウザ上のUI操作ができない
- サーバーサイドでデータを直接書き換えると、フロントエンドのバリデーションやカスケード更新(あるフィールドの変更に連動して他のフィールドも更新される処理)をバイパスしてしまい、データの不整合が起きうる
- ユーザーが見ている画面の状態(どのフォームが開いているか、どんな選択肢があるか)をサーバーからは把握できない
そこで、ブラウザ上で実行される「クライアントサイドツール」 という仕組みを作りました。
MCPツール: UseAI Server → ProOne Server(DB / OpenSearch) クライアントサイドツール: UseAI Server → ブラウザ(DOM操作 / フォーム操作)
MCPツールがサーバーサイドの手足なら、クライアントサイドツールはブラウザサイドの手足です。この2つを組み合わせることで、AIエージェントはサーバーのデータにもブラウザのUIにもアクセスできるようになります。
use-ai とは
クライアントサイドツールの仕組みは、ミツモアが公開しているライブラリ use-ai によって実現されています。ここでは要点だけ紹介します。
use-aiは、Reactアプリケーションにチャット型AIエージェントを統合するためのフレームワークで、クライアント(@meetsmore-oss/use-ai-client)とサーバー(@meetsmore-oss/use-ai-server)の2パッケージで構成されています。主な特徴は以下の通りです。
useAI()フックで、コンポーネントからAIにツールとコンテキスト(状態)を宣言的に登録できるUseAIProviderがマウント中のコンポーネントのツール・コンテキストを自動集約し、Socket.IO経由でAIサーバーに送信する- AIがツールを呼ぶと、ブラウザ側のハンドラがそのまま実行される
- サーバーサイドMCP・ファイルアップロード・Langfuse連携・チャットUI・レート制限なども組み込み済み
UseAIProvider(接続管理、認証、チャットUI、FileTransformer)
└── useAI({ id: 'estimate-form', tools, prompt }) ← 見積もりヘッダー
└── useAI({ id: 'line-table', tools, prompt }) ← 明細テーブル
今レンダリングされているコンポーネントの登録だけが集約されるため、ページ遷移やコンポーネントのアンマウント時には自動的に登録解除されます。AIが見えるツールは常に「ユーザーが今見ているページで実際に操作できるもの」だけです。
use-aiの全体像(アーキテクチャ、機能一覧、AG-UIプロトコル対応など)については、以下の記事で詳しく解説されています。
👉 use-ai: Unlocking AI power for any app 💪🏼(英語)
本記事ではuse-ai自体の深掘りはここまでにして、クライアントサイドツールの設計と実装にフォーカスします。
デモ:自然言語で見積もり明細を操作
デモ動画: AIにチャットで指示して、既存の見積もり明細行の一部の単価を2倍に変更しています。

通信の仕組み
ブラウザとAIサーバー間の通信はSocket.IOで行われます。

一般的なAIエージェントではツール実行はサーバーサイドで完結しますが、クライアントサイドツールではAIサーバーからブラウザに「このツールを実行して」とリクエストが飛び、ブラウザが結果を返すという往復が発生します。この往復をSocket.IOのリアルタイム通信で実現しています。
実装方法:3ステップでツールを登録する
クライアントサイドツールの実装は、ツール定義・コンテキスト構築・useAI呼び出しの3ステップです。
ステップ1:defineToolでツールを定義する
defineToolで「AIが呼べるツール」をひとつ定義します。引数は、説明文(AIがいつ使うか判断するためのテキスト)、Zodスキーマ(パラメータの型)、ハンドラ(ブラウザ上で実行される関数)の3つです。
import { defineTool, z } from '@meetsmore-oss/use-ai-client'
const changeFieldTool = defineTool(
// AIが読む説明文
'Updates an estimate field.',
// パラメータのスキーマ
z.object({
field: z.enum(['title', 'taxType', 'note']),
value: z.string(),
}),
// ブラウザ上で実行されるハンドラ
(params) => {
setValue(params.field, params.value) // react-hook-form等の既存関数をそのまま呼ぶ
return { success: true, message: `Updated ${params.field}` }
},
)
ポイントは、ハンドラの中で既存のコンポーネントの操作関数をそのまま呼べることです。react-hook-formのsetValueでもDOM操作でもAPIコールでも、ブラウザ上で動くものなら何でも書けます。
ステップ2:コンテキストを組み立てる
AIに「今この画面がどういう状態か」を伝えるテキストを作ります。
const context = `
## Estimate Form
Current values: ${JSON.stringify({ title, taxType })}
Available tags: ${JSON.stringify(tags.map(t => t.name))}
`
フォームの値が変わるとコンテキストも更新されるため、AIは常に最新の状態を把握できます。選択肢(タグ一覧など)を含めると、AIが有効な値を選びやすくなります。
ステップ3:useAIで登録する
ツールとコンテキストをuseAI()に渡すと、そのコンポーネントがマウントされている間だけAIからツールが使えるようになります。
useAI({
id: 'estimate-form',
tools: { changeField: changeFieldTool, save: saveTool },
prompt: context,
})
これだけで、AIチャットから estimate-form_changeField や estimate-form_save が呼べるようになります。コンポーネントがアンマウントされると自動的に登録解除されるので、AIが見えるツールは常に「ユーザーが今操作できるもの」だけに保たれます。
実装で気をつけたこと
破壊的な操作にはユーザー確認を入れる
defineTool の第4引数で annotations を設定すると、AIがそのツールを実行する前にユーザーの承認ダイアログが表示されます。保存や削除のような操作には必ず設定しています。
const saveTool = defineTool(
'Saves the current estimate.',
z.object({}),
async () => {
await save()
return { success: true }
},
{ annotations: { destructiveHint: true } },
)
コンテキストは必要最小限にする
データをそのままコンテキストに流し込むとトークン使用量が膨張し、AIの精度も落ちます。例えばタグ情報はidとnameだけに絞るなど、AIが判断に必要な情報だけを渡すようにしています。ツール数も同様で、多すぎるとAIが適切なツールを選びにくくなるため、コンポーネントごとに必要最小限に留めています。
Zodスキーマでパラメータを制約する
AIは想定外の値を渡してくることがあります。z.string()だけでは緩すぎるので、z.enum()で有効な値を限定したり、z.string().max()で長さを制限したりしています。スキーマを厳しくするほど、AIの操作精度が上がります。
まとめ
本記事では、業務SaaS「プロワン」にAIエージェントを統合するにあたって設計・実装した「クライアントサイドツール」を、見積もり明細の操作デモを題材に紹介しました。
- MCPツール(サーバーサイド)とクライアントサイドツール(ブラウザサイド)の組み合わせで、AIがデータもUIも操作できるようにした
- use-aiの
useAI()フックでツールを宣言的に登録し、コンポーネントのライフサイクルと連動した自動スコープ管理を実現した - defineToolでツール定義、コンテキスト構築でAIに状態を伝え、useAIで登録する、という3ステップで実装できる
- 破壊的操作のユーザー確認、コンテキストの最小化、Zodスキーマによるパラメータ制約など、実装で気をつけたポイントも紹介した
今後の記事では、AIをコードからプログラマティックに呼び出す仕組み(chat.sendMessage + FileTransformer)を使って、OCRで読み取ったPDFから見積もり明細を自動入力するデモを紹介します。
本記事で紹介したクライアントサイドツールの仕組みは、ミツモアが公開しているライブラリ use-ai で実現しています。ライセンスは Business Source License 1.1 です。ぜひ使ってみてください! 👉 GitHub: meetsmore/use-ai
ミツモアで一緒に働きませんか?
ミツモアでは、生成AIを活用して圧倒的な生産性を生み出し、日本のGDPを向上させるという目標に向けて、一緒に働く仲間を募集しています。今回ご紹介したように、プロダクトへのAIエージェント組み込みを積極的に進めています。
少しでも興味をお持ちの方は、カジュアル面談からでも大歓迎です。ぜひお気軽にご応募ください!