MCP Apps: AIチャット内でインタラクティブUIが動作する

MCP Apps: AIチャット内でインタラクティブUIが動作する

MCP AppsがAIエージェントUXをどう変えるか、sandboxed iframeとJSON-RPC双方向通信アーキテクチャから実践実装コードまで、Engineering Manager視点で完全解説します。

2026年1月26日、Model Context Protocolチームが静かに発表した機能一つが、AIエージェントUXのパラダイムを変えています。それがMCP Appsです。AIがテキスト応答の代わりに、AIチャット画面の中で直接動作するインタラクティブなダッシュボード、フォーム、データビジュアライゼーションを提供できるようになりました。

Engineering Manager目線でこれが何故重要かを一言で言うと:これまでAIエージェントが「データを見せれば」ユーザーはそのテキストを読み、再び手動で操作する必要がありました。MCP Appsはそのギャップを埋めます。

MCP Appsとは何か

MCP Appsは、MCP(Model Context Protocol)の最初の公式拡張(extension)で、ツール呼び出し(tool call)の応答としてインタラクティブなHTML UIを返せるようにするプロトコルです。

既存のMCPツールはテキスト、画像、構造化データを返していました。MCP Appsを使うと、同じツール呼び出しが次のものを返せます:

  • クリッカブルな地域別売上マップ
  • リアルタイム更新のシステム監視ダッシュボード
  • 全オプションを一覧できるデプロイ設定フォーム
  • PDFビューアー、3Dモデルビューアー、楽譜レンダラー

そしてこのUIがチャット画面の中で、会話コンテキストの中で動作します。

なぜ普通のWebアプリリンクと違うのか

「リンクを渡せばいいのでは?」と思うかもしれません。MCP Appsが別のWebアプリと根本的に異なる理由は4つあります。

1. コンテキスト保持

UIが会話の中に存在します。ユーザーがタブを切り替えたり、どのチャットでそのダッシュボードを見たかを思い出す必要がありません。会話の流れにUIが自然に溶け込んでいます。

2. 双方向データフロー

MCP AppはMCPサーバーのすべてのツールを呼び出せ、ホストは新しい結果をアプリにプッシュできます。別のWebアプリなら独自のAPI、認証、状態管理が必要ですが、MCP Appsは既存のMCPパターンをそのまま活用します。

3. ホスト機能との統合

アプリがホストに作業を委任できます。「このミーティングをスケジュールに追加して」というリクエストをアプリがホストに送ると、ホストがユーザーがすでに連携しているカレンダー統合を通じて処理します。すべての外部統合をアプリが直接実装する必要がありません。

4. セキュリティ保証

MCP Appsはsandboxed iframeの中で実行されます。親ページへのアクセス、クッキーの盗取、コンテナからの脱出ができません。ホストがサーバー開発者を完全に信頼しなくても、サードパーティアプリを安全にレンダリングできます。MCPエコシステム全体のセキュリティ脅威とハードニング方法についてはMCPセキュリティ危機 — 60日間で30件のCVE、エンタープライズハードニングガイドを参照してください。

動作原理:アーキテクチャ詳細

MCP Appsは2つのMCPプリミティブを組み合わせます:UIリソースを宣言するツール(tool)と、そのデータをインタラクティブHTMLとしてレンダリングするUIリソースです。

sequenceDiagram
    participant ユーザー
    participant エージェント/ホスト
    participant App as MCP App iframe
    participant Server as MCPサーバー

    ユーザー->>エージェント/ホスト: 「売上分析を見せて」
    Note over ユーザー,App: チャット内にインタラクティブアプリをレンダリング
    エージェント/ホスト->>Server: tools/call (show_analytics)
    Server-->>エージェント/ホスト: ツール結果 + UIリソース参照
    エージェント/ホスト-->>App: ツール結果をアプリにプッシュ
    ユーザー->>App: 地域クリック、フィルター操作
    App->>エージェント/ホスト: tools/call リクエスト (postMessage経由)
    エージェント/ホスト->>Server: ツール呼び出し転送
    Server-->>エージェント/ホスト: 新鮮なデータ
    エージェント/ホスト-->>App: データ更新
    Note over ユーザー,App: アプリが新データで更新
    App-->>エージェント/ホスト: コンテキスト更新

ステップ別の動作フロー

Step 1: UIプリロード

ツール説明(tool description)に _meta.ui.resourceUri フィールドが含まれています。このフィールドは ui:// リソースを指します。ホストはツールが呼ばれる前にこのリソースをプリロードでき、ストリーミング入力などの機能が可能になります。

Step 2: リソースフェッチ

ホストがサーバーからUIリソースを取得します。このリソースはHTMLページで、通常JavaScriptとCSSがバンドルされています。

Step 3: Sandboxedレンダリング

ホストが会話の中でsandboxed iframeとしてHTMLをレンダリングします。sandboxがアプリの親ページへのアクセスを制限します。

Step 4: 双方向通信

アプリとホストは ui/ メソッド名プレフィックスを持つJSON-RPCプロトコルで通信します。アプリはツール呼び出しリクエスト、メッセージ送信、モデルコンテキスト更新、ホストからのデータ受信が可能です。

実践実装:MCP Appサーバーを作る

実際にMCP Appサーバーを実装してみましょう。シンプルな売上分析ダッシュボードの例です。

1. 依存関係のインストール

npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps express

2. UI宣言付きMCPサーバー

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "analytics-dashboard",
  version: "1.0.0",
});

// UIリソースを宣言するツール定義
server.tool(
  "show_sales_dashboard",
  "地域別売上データをインタラクティブダッシュボードで表示します",
  {
    region: {
      type: "string",
      description: "分析する地域 (all, kr, jp, us, cn)",
      default: "all",
    },
    period: {
      type: "string",
      description: "分析期間 (7d, 30d, 90d)",
      default: "30d",
    },
  },
  // _meta.ui: MCP Appsの核心 — UIリソース参照の宣言
  {
    _meta: {
      ui: {
        resourceUri: "ui://analytics-dashboard/sales",
      },
    },
  },
  async ({ region, period }) => {
    // 実際のデータ取得
    const salesData = await fetchSalesData(region, period);

    return {
      content: [
        {
          type: "text",
          text: `${region}地域の直近${period}売上データをロードしました。`,
        },
        {
          type: "resource",
          resource: {
            uri: "ui://analytics-dashboard/sales",
            mimeType: "text/html",
          },
        },
      ],
      // UIアプリに初期データを渡す
      _meta: {
        ui: {
          resourceUri: "ui://analytics-dashboard/sales",
          initialData: salesData,
        },
      },
    };
  }
);

// UIリソースハンドラー
server.resource("ui://analytics-dashboard/sales", async () => {
  const htmlContent = generateDashboardHTML();
  return {
    contents: [
      {
        uri: "ui://analytics-dashboard/sales",
        mimeType: "text/html",
        text: htmlContent,
      },
    ],
  };
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main();

3. MCP App UI実装(React例)

// dashboard-app/src/App.tsx
import { useEffect, useState } from "react";
import { App as McpApp, useToolCall, useHostData } from "@modelcontextprotocol/ext-apps";

interface SalesData {
  regions: { name: string; revenue: number; growth: number }[];
  total: number;
  period: string;
}

function SalesDashboard() {
  const [data, setData] = useState<SalesData | null>(null);
  const [selectedRegion, setSelectedRegion] = useState<string>("all");

  // ホストから初期データを受信
  const hostData = useHostData<SalesData>();

  // ツール呼び出しhook — ユーザーインタラクション時にサーバーに新データ要求
  const { call: fetchRegionData, loading } = useToolCall("show_sales_dashboard");

  useEffect(() => {
    if (hostData) {
      setData(hostData);
    }
  }, [hostData]);

  const handleRegionClick = async (region: string) => {
    setSelectedRegion(region);
    // UIから直接MCPツールを呼び出し — 追加LLMターンなし!
    const result = await fetchRegionData({ region, period: "30d" });
    if (result?.data) {
      setData(result.data as SalesData);
    }
  };

  if (!data) return <div className="loading">データ読み込み中...</div>;

  return (
    <div className="dashboard">
      <h2>売上現況ダッシュボード</h2>
      <div className="region-filters">
        {["all", "kr", "jp", "us", "cn"].map((region) => (
          <button
            key={region}
            className={selectedRegion === region ? "active" : ""}
            onClick={() => handleRegionClick(region)}
            disabled={loading}
          >
            {region.toUpperCase()}
          </button>
        ))}
      </div>
      <div className="chart-area">
        {data.regions.map((r) => (
          <div key={r.name} className="region-bar">
            <span className="label">{r.name}</span>
            <div
              className="bar"
              style={{ width: `${(r.revenue / data.total) * 100}%` }}
            />
            <span className="value">
              ¥{r.revenue.toLocaleString()}
              <span className={r.growth > 0 ? "up" : "down"}>
                {r.growth > 0 ? "▲" : "▼"}{Math.abs(r.growth)}%
              </span>
            </span>
          </div>
        ))}
      </div>
      <div className="summary">
        合計: ¥{data.total.toLocaleString()} | 期間: {data.period}
      </div>
    </div>
  );
}

// McpAppで包んでホストとの通信を有効化
export default function App() {
  return (
    <McpApp>
      <SalesDashboard />
    </McpApp>
  );
}

4. セキュリティ設定(CSPと権限)

// ツール宣言時にセキュリティポリシーを明示
{
  _meta: {
    ui: {
      resourceUri: "ui://analytics-dashboard/sales",
      permissions: [], // 追加権限なし(基本sandboxのみ)
      csp: {
        // 外部リソース許可ドメインを明示
        "script-src": ["'self'", "https://cdn.jsdelivr.net"],
        "connect-src": ["'self'", "https://api.yourcompany.com"],
        "style-src": ["'self'", "'unsafe-inline'"],
      },
    },
  },
}

現在のサポートクライアント

2026年3月時点でMCP Appsをサポートするクライアントは以下の通りです:

クライアントサポート状況備考
Claude (claude.ai)✅ 対応Web + Desktop
Claude Desktop✅ 対応v3.5以降
VS Code Copilot✅ 対応InsidersからStableに移行済み
Goose (Block)✅ 対応
Postman✅ 対応APIテストに活用
MCPJam✅ 対応
ChatGPT⏳ 未定公式発表なし
Cursor⏳ 未定ロードマップ検討中

VS Codeでは /mcp チャットコマンドでサーバーの有効化/無効化、OAuth認証管理が可能です。ブラウザでMCPサーバーを直接実行する方式についてはWebMCP: Chrome 146でブラウザがAIエージェントのツールサーバーになるを参照してください。

Engineering Manager視点での実務適用

MCP Appsを導入する際にEMが判断すべきポイントをまとめました。

MCP Appsが適しているケース

複雑なデータ探索が繰り返される場合です。チームメンバーがAIに「今月の障害状況をまとめて」と聞くたびにテキスト応答を読み、改めてダッシュボードを開いて確認するなら、MCP Appsでダッシュボードをチャット内に埋め込めます。

多段階の設定/承認ワークフローも適しています。インフラデプロイ設定、コスト承認、コードレビュートリアージのような作業は、「一つずつ質問する会話」方式よりも全オプションを一覧で見せるフォームの方がはるかに効率的です。

リアルタイム監視も強みです。チャットで質問を投げ、その場でライブメトリクスダッシュボードが表示される体験は、従来の方式とは根本的に異なります。

導入時の注意点

バンドルサイズ管理:UIリソースはチャットでロードされるため、初期ロードパフォーマンスが重要です。React全バンドルよりPreactやvanilla JSの使用が推奨されます。

CSP(Content Security Policy)設定:外部スクリプト、APIエンドポイントを明示的に宣言する必要があります。セキュリティチームと協議して許可ドメインリストを管理してください。

Fallback設計:MCP Appsをサポートしないクライアントでもツールが有用なテキスト応答を返すよう、必ずフォールバックを設計してください。

ユーザー同意フロー:UIからツールを呼び出す際、ホストがユーザーの同意を求めます。このUXを自然に設計する必要があります。

クライアント実装(独自ホスト構築時)

独自のAIクライアントを構築中のチームには2つの選択肢があります:

# 選択肢1: @mcp-ui/client パッケージ(Reactコンポーネント提供)
npm install @mcp-ui/client

# 選択肢2: App Bridge直接実装
# SDKのApp Bridgeモジュールを活用
# - sandboxed iframeレンダリング
# - メッセージパッシング
# - ツール呼び出しプロキシ
# - セキュリティポリシー適用

ユースケースギャラリー

公式リポジトリにある実際の例を見ると、可能性の範囲を実感できます。

  • map-server:CesiumJSグローブ — 「アジアの物流状況を見せて」→ 3D地球儀がチャット内に
  • cohort-heatmap-server:コホートヒートマップ — ユーザー維持率分析ダッシュボード
  • pdf-server:PDFビューアー — 契約書をチャット内で直接レビュー
  • system-monitor-server:リアルタイムシステムメトリクス監視
  • scenario-modeler-server:ビジネスシナリオモデリングツール
  • budget-allocator-server:予算配分シミュレーター

すべての例はReact、Vue、Svelte、Preact、Solid、vanilla JSバージョンが提供されています。

まとめ

MCP AppsはAIエージェントインターフェースの根本的な限界を解決します。テキストのみでコミュニケーションしていたAIが、生きたUIを会話の中で直接起動できるようになりました。

Engineering Manager視点でこの技術の価値は明確です。チームメンバーがAIに質問し、その場でインタラクティブツールを受け取って作業を完了するワークフローが実現します。別のダッシュボードタブや別のツール切り替えなしに。

今すぐすべてのMCPサーバーにUIを追加する必要はありません。しかし、チームで最もよく使うツール一つにMCP Appsを適用してみることから始めてください。その経験が今後のAIワークフロー設計の方向性を変えてくれるでしょう。AIエージェントに活用できる標準化されたスキルシステムについてはAnthropic Agent Skills標準:AIエージェント能力の拡張も合わせてお読みください。

参考資料

他の言語で読む

この記事は役に立ちましたか?

より良いコンテンツを作成するための力になります。コーヒー一杯で応援してください。

著者について

jw

Kim Jangwook

AI/LLM専門フルスタック開発者

10年以上のWeb開発経験を活かし、AIエージェントシステム、LLMアプリケーション、自動化ソリューションを構築しています。Claude Code、MCP、RAGシステムの実践的な知見を共有します。

ブログリストへ