MCP Appsにおける「単一HTML」の技術的背景
「MCP Apps(SEP-1865)」は、従来のテキストや構造化データによる応答を超え、AIチャット画面内にHTMLベースのインタラクティブなUIを直接埋め込むための拡張仕様として、2026年1月26日に正式リリースされました。この仕様において、UI(View)を提供するリソースは最終的に「単一のHTML」として配信される構成が極めて有効です。
LLM(大規模言語モデル)は最新情報や社内の機密データにアクセスできない「データの断絶」という課題を抱えていますが、MCP(Model Context Protocol)はこれをつなぐオープンな標準規格です。ビジネス視点では、接続先ごとに専用APIを個別開発するコストを削減し、認証やアクセス権限の管理を一元化できる大きなメリットがあります。その進化系であるMCP AppsのUI開発において、なぜマルチファイル構成ではなくアセットを1つにまとめた「単一HTML」が求められるのか、その技術的背景と設計判断のトレードオフを解説します。
1. 配信プロトコルとサンドボックス制限
ホスト(AIクライアント)は、サーバーのツール呼び出しレスポンスに含まれる _meta.ui.resourceUri フィールドから ui:// スキームを検出し、対象のUIリソースを取得します。この時、取得されるデータのMIMEタイプは text/html;profile=mcp-app に制限されています。
ホストは取得したHTMLをサンドボックス化された iframe 内にレンダリングします。この際、セキュリティ確保のためにホストのDOM、Cookie、ローカルストレージへのアクセスは完全に遮断されます。もしUIが複数のCSSやJS、画像などの外部ファイルを相対パスで読み込む設計になっていると、この厳格に保護されたサンドボックス環境下において依存アセットを正しく解決・ロードすることが極めて困難になります。
2. セキュリティとデータ窃取の防止
MCP Appsの大きな特徴は、「UI(View)とサーバーは直接通信しない」という設計方針にあります。iframe内のUIはサーバーと直接通信せず、すべての通信を仲介役であるホスト(AIクライアント)経由で行います。これにより、悪意ある操作や外部への意図しないデータ窃取(exfiltration)をホスト側で検閲・ブロックできるようになっています。
アセットがすべてインライン化された単一HTMLであれば、UI表示時における外部への追加アセットリクエストが発生しません。サーバー側があらかじめ宣言したCSP(Content Security Policy)メタデータに基づき、ホストが通信を厳格に制限する上でも、単一HTML構成は非常に親和性が高いのです。
Viteとプラグインによる単一HTMLのビルド構成
ReactやVueなどのコンポーネント指向フレームワークを使用する場合、通常はビルドプロセスにおいてJSやCSSが分割(コードスプリッティング)されます。これを「単一HTML」に強制的にバンドルするために、Viteと vite-plugin-singlefile を組み合わせた開発環境が強力なソリューションとなります。
以下は、このビルド構成を実現するための最小限のセットアップ手順です。
1. 必要なパッケージのインストール
Viteプロジェクトに vite-plugin-singlefile と、公式のフロントエンド用SDKである @modelcontextprotocol/ext-apps を導入します。
npm install -D vite-plugin-singlefile
npm install @modelcontextprotocol/ext-apps
2. vite.config.tsの設定
Viteの設定ファイルにおいて、ビルド時にすべてのJS、CSS、アセットをHTML内にインライン化するようにプラグインを配置します。
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
export default defineConfig({
plugins: [viteSingleFile()],
build: {
// インライン化を確実に行うためのアセットサイズ制限の解除設定
assetsInlineLimit: 100000000,
chunkSizeWarningLimit: 100000000,
},
});
この設定により、npm run build を実行した際、dist/index.html のなかにすべてのスタイルシートと実行スクリプトが埋め込まれた、1つの軽量なHTMLファイルが出力されます。
公式SDKを用いた双方向通信のミニマル実装
単一HTMLとしてビルドされたUIは、ホスト(Agent)を仲介してサーバーと通信を行います。UIとホスト間は、postMessage を介した JSON-RPC 2.0 で双方向通信を実行します。
公式SDKである @modelcontextprotocol/ext-apps を使用することで、低レイヤーの通信処理を記述することなく、直感的なコードでサーバーのツール(callServerTool)を呼び出したり、AI側のコンテキストを動的に更新(app.updateModelContext())したりできます。
1. UIからサーバーツールの呼び出し
以下は、UI側のボタンをクリックした際に、サーバー側のツールを呼び出すミニマルなフロントエンド側の実装例です。
import { createMcpClient } from "@modelcontextprotocol/ext-apps";
// MCPクライアントの初期化
const mcp = createMcpClient();
async function handleAction() {
try {
// サーバー側の特定のツールを呼び出す
const response = await mcp.callServerTool("get-data", {
parameter: "example"
});
// 取得した結果をUIに反映
const resultArea = document.getElementById("result");
if (resultArea) {
resultArea.textContent = JSON.stringify(response);
}
} catch (error) {
console.error("サーバーツールの呼び出しに失敗しました:", error);
}
}
document.getElementById("action-btn")?.addEventListener("click", handleAction);
2. 双方向のコンテキスト更新
ユーザーがUI上で行った操作(例:マップのズームやスクロール、フォームの選択)をAI側に同期させたい場合、app.updateModelContext() を使用します。これにより、AIは「今ユーザーがどのような画面を見ているか」という最新のコンテキストを把握し、的確な次の対話を提供できます。
async function onUserInteract(stateData: any) {
// AI側のコンテキストを動的に更新
await mcp.updateModelContext({
state: stateData,
description: "ユーザーがUI上の特定のデータをマークしました。"
});
}
段階的強化とセキュリティ設計の担保
MCP Appsを導入する上で考慮すべき重要なコンセプトが「段階的強化(Progressive Enhancement)」です。
すべてのAIクライアントが最新のMCP AppsによるUI表示に対応しているわけではありません。UIをサポートしない非対応クライアントで実行された場合でも、システムがクラッシュすることなく、フォールバックとして従来の「テキスト応答」を返す後方互換性が維持されます。そのため、UI構築用の単一HTMLを設計する際は、テキストでの説明情報もあわせてレスポンスに含めるように、バックエンド側を実装することが推奨されます。
また、重要な操作(例えば、Slackへのメッセージ送信や外部ツールへの投稿など)をUIからトリガーする場合は、セキュリティとガバナンスの観点から、実行前にプレビューを表示し、ユーザーによる明示的な同意・確認を必須とするプロセスを設計に組み込むことが不可欠です。