MCP Apps 完全ガイド:MCP のインタラクティブ UI の仕組み(2026)
MCP Apps は、MCP サーバーがインタラクティブな UI を ui:// リソースとして配信し、Claude・ChatGPT・VS Code・Goose のサンドボックス iframe で描画する仕組みです。仕様の現状、SDK、最小サンプルを解説します。
MCP Apps は、MCP サーバーがプレーンテキストの代わりにインタラクティブな UI(グラフ、フォーム、地図、ダッシュボード)を返せるようにする、Model Context Protocol の公式拡張です。ツールはメタデータで HTML ページを含む ui:// リソースを指し、ホスト(Claude、ChatGPT、VS Code、Goose など)がそのページをサンドボックス化された iframe で描画します。ページは JSON-RPC でホストとやり取りします。一度書けば、この拡張に対応したすべてのホストで動きます。
このガイドでは、仕様の現状、MCP Apps を描画できるクライアント、公式 SDK に基づく最小の動作サンプル、そして MCP Apps と OpenAI Apps SDK・MCP-UI の関係を解説します。
現状のまとめ(2026 年 10 月)
| 項目 | 現在の状態 |
|---|---|
| 仕様 | SEP-1865「MCP Apps: Interactive User Interfaces for MCP」、Extensions トラック |
| ステータス | Stable(安定版)、バージョン 2026-01-26。draft 版も開発中 |
| 発表 | 2026 年 1 月 26 日、初の公式 MCP 拡張として発表 |
| リポジトリ | modelcontextprotocol/ext-apps |
| SDK | @modelcontextprotocol/ext-apps(サブパス /server・/react・/app-bridge)、v2.x |
| MIME タイプ | text/html;profile=mcp-app |
| 紐付けキー | ツールの _meta.ui.resourceUri |
この SEP は 2025 年 11 月 21 日に初めて公開されました。著者には MCP-UI の開発者と、Anthropic・OpenAI のメンバーが含まれています。
MCP Apps の仕組み
ext-apps の README は流れを 4 つのステップで説明しています。
- ツール定義:ツールが HTML インターフェースを含む
ui://リソースを宣言します。 - ツール呼び出し:モデルはいつもどおりサーバー上のツールを呼び出します。
- ホストが描画:ホストがリソースを取得し、サンドボックス化された iframe に表示します。
- 双方向通信:ホストは通知でツールの入力と結果を UI に渡し、UI はホストを経由して別のツールを呼び出せます。
つまり UI はもう一つの MCP リソースにすぎません。だから MCP Apps は既存のプロトコルにそのまま収まります。ツールとリソースの違いを確認したい場合は MCP のリソースとツールの違い を参照してください。
プロトコルの構成要素
- UI リソース:URI が
ui://で、MIME タイプがtext/html;profile=mcp-appのリソースです。_meta.uiでcsp(許可するオリジン)、permissions、domain、prefersBorderを宣言できます。 - ツールとの紐付け:ツールの
_meta.ui.resourceUriで描画するリソースを指定します。_meta.ui.visibilityは["model", "app"](デフォルト)か、UI からは呼べるがモデルには見えない補助ツール向けの["app"]を指定できます。 - ハンドシェイク:View が
ui/initializeを送ってhostContext(テーマ、表示モード、ロケールなど)を受け取り、続けてui/notifications/initializedを送ります。 - データの受け取り:
ui/notifications/tool-inputとui/notifications/tool-resultが呼び出しの引数と結果を届けます。 - UI からのアクション:View は
tools/call、ui/message(チャットにメッセージを投稿)、ui/open-link、ui/update-model-contextを送れます。 - 表示モード:
inline(デフォルト)、fullscreen、pip(ピクチャーインピクチャー)。ホストが 3 つすべてに対応しているとは限りません。
セキュリティモデル
すべてはサンドボックス化された iframe 内で動き、CSP はデフォルトですべてを拒否します。リソースが ui.csp を宣言しない場合、仕様によりホストは connect-src 'none' を使うので、UI は外部 API を一切呼べません。外部オリジンを許可するには、connectDomains(fetch/WebSocket)、resourceDomains(スクリプト、画像、フォント、スタイル)、frameDomains(ネストした iframe)、baseUriDomains に列挙します。ホストはルールをさらに厳しくできますが、宣言されていないドメインを許可してはいけません。発表記事によると、UI とホスト間のメッセージはすべて監査可能な JSON-RPC で、UI が始めたツール呼び出しにユーザーの承認を求めることもできます。より広い脅威モデルについては MCP セキュリティのベストプラクティス を参照してください。
MCP Apps に対応しているクライアント
公式ドキュメントで確認できたホストだけを挙げます。
| ホスト | 対応状況 | 備考 |
|---|---|---|
| Claude(Web とデスクトップ) | 対応(発表当日から) | Claude ドキュメント:App を表示する前に許可を求めます(Allow / Always allow)。ui.domain を設定するリモート App は、Claude 独自のハッシュ形式 *.claudemcpcontent.com を使う必要があります |
| ChatGPT | 対応 | OpenAI の変更履歴によると、2026-02-22 から ChatGPT は「fully compatible with the MCP Apps spec」です。Add UI to your MCP server を参照 |
| VS Code(GitHub Copilot エージェント) | 対応 | 2026-01-26 に発表。まず Insiders、その後 Stable に提供 |
| Goose | 対応 | 公式発表で挙げられたローンチ時のホストの一つ |
| Postman、MCPJam、mcp-use Inspector、Alpic Playground | 掲載あり | ext-apps の README で対応ホストとして記載 |
個々の機能への対応はホストによってまだ違いがあり、特に表示モードとホスト独自の機能で差が出ます。MCP Apps に対応していないクライアントでも作業を終えられるよう、ツールは引き続き有用なテキストを返すようにしましょう。対応状況の一覧は modelcontextprotocol.io/clients にあり、MCP クライアントは当サイトの /clients でも探せます。
最小の MCP App を作る
以下のコードは公式の quickstart サンプル(ext-apps 2.x、Node.js 20+)からそのまま取ったものです。get-time というツールで、UI にはサーバー時刻と更新ボタンが表示されます。サーバーを作ったことがなければ、まず MCP サーバーの作り方 をご覧ください。
1. パッケージをインストール
npm install -S @modelcontextprotocol/ext-apps \
@modelcontextprotocol/client@^2.0.0 \
@modelcontextprotocol/server@^2.0.0 \
@modelcontextprotocol/node@^2.0.0 \
@modelcontextprotocol/express@^2.0.0 \
zod@^4.2.0
npm install -D typescript vite vite-plugin-singlefile tsx
ホストは UI を 1 つの HTML リソースとして読み込むため、quickstart では Vite と vite-plugin-singlefile を使って JavaScript をインラインにバンドルします。
2. ツールと UI リソースを登録する(server.ts)
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
registerAppResource,
registerAppTool,
RESOURCE_MIME_TYPE,
} from '@modelcontextprotocol/ext-apps/server';
import { McpServer } from '@modelcontextprotocol/server';
import { z } from 'zod';
const DIST_DIR = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'dist'
);
export function createServer(): McpServer {
const server = new McpServer({
name: 'Quickstart MCP App Server',
version: '1.0.0',
});
// Tool + resource, tied together by the resource URI.
const resourceUri = 'ui://get-time/mcp-app.html';
registerAppTool(
server,
'get-time',
{
title: 'Get Time',
description: 'Returns the current server time.',
inputSchema: z.object({}),
_meta: { ui: { resourceUri } }, // links this tool to its UI
},
async () => {
const time = new Date().toISOString();
return { content: [{ type: 'text', text: time }] };
}
);
registerAppResource(
server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => {
const html = await fs.readFile(
path.join(DIST_DIR, 'mcp-app.html'),
'utf-8'
);
return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
};
}
);
return server;
}
RESOURCE_MIME_TYPE は text/html;profile=mcp-app を表す定数です。文字列を手書きせずにこちらを使いましょう。ヘルパーの registerAppTool と registerAppResource はホストごとに必要なメタデータも生成してくれます。Claude のドキュメントがこれらを推奨しているのはそのためです。
3. View を作る(mcp-app.html + src/mcp-app.ts)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Get Time App</title>
</head>
<body>
<p>
<strong>Server Time:</strong> <code id="server-time">Loading...</code>
</p>
<button id="get-time-btn">Get Server Time</button>
<script type="module" src="/src/mcp-app.ts"></script>
</body>
</html>
import { App } from '@modelcontextprotocol/ext-apps';
const serverTimeEl = document.getElementById('server-time')!;
const getTimeBtn = document.getElementById('get-time-btn')!;
const app = new App({ name: 'Get Time App', version: '1.0.0' });
// Register handlers BEFORE connect() so you don't miss the first result.
app.ontoolresult = (result) => {
const time = result.content?.find((c) => c.type === 'text')?.text;
serverTimeEl.textContent = time ?? '[ERROR]';
};
getTimeBtn.addEventListener('click', async () => {
const result = await app.callServerTool({ name: 'get-time', arguments: {} });
const time = result.content?.find((c) => c.type === 'text')?.text;
serverTimeEl.textContent = time ?? '[ERROR]';
});
app.connect();
main.ts(ポート 3001 の /mcp で Streamable HTTP、または --stdio で stdio)、vite.config.ts、package.json のスクリプトは quickstart フォルダ からコピーしてください。React を使う場合は、@modelcontextprotocol/ext-apps/react の useApp と useHostStyles が使えます。
4. テストする
- basic-host:
ext-appsをクローンし、npm install && npm startを実行してhttp://localhost:8080を開きます。このリファレンスホストはデフォルトでhttp://localhost:3001/mcpに接続します。 - Claude Desktop:Settings → Developer → Edit Config で stdio の設定を追加します。まず公開済みのサンプルを試すなら次のとおりです。
{
"mcpServers": {
"map": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-map", "--stdio"]
}
}
}
- ChatGPT:接続できるのはリモートの HTTPS サーバーだけです。サーバーをデプロイするかトンネルを張ってから、カスタム MCP サーバーとして追加します。手順は ChatGPT MCP 設定ガイド と MCP サーバーのデプロイ を参照してください。
- デバッグ:描画を調べる前に、MCP Inspector でツールの
_metaとリソースが正しく返るか確認しましょう。
AI コーディングエージェントを使っていますか?リポジトリには Agent Skills(create-mcp-app、add-app-to-server、migrate-oai-app、convert-web-app)が同梱されています。Claude Code では /plugin marketplace add modelcontextprotocol/ext-apps を実行してから /plugin install mcp-apps@mcp-apps でインストールできます。
MCP Apps・OpenAI Apps SDK・MCP-UI の比較
3 つは同じ系譜にあります。発表記事によると、MCP Apps は MCP-UI と OpenAI Apps SDK を土台にしています。
| MCP-UI | OpenAI Apps SDK(ChatGPT) | MCP Apps | |
|---|---|---|---|
| 何か | MCP 上の UI を切り開いたコミュニティプロジェクト | ChatGPT 向けアプリを作る OpenAI の仕組み。現在は Plugins のドキュメントにまとめられている | 公式でベンダー中立の MCP 拡張 |
| 現状 | @mcp-ui/server と @mcp-ui/client は現在 MCP Apps 標準を実装 | ChatGPT は MCP Apps を実装。window.openai が ChatGPT 専用の機能を追加 | 安定版仕様 2026-01-26 |
| ツールと UI の紐付け | 埋め込み UI リソース | _meta["openai/outputTemplate"](エイリアスとして存続) | _meta.ui.resourceUri |
| 動作環境 | MCP-UI ホスト、および MCP Apps ホスト | ChatGPT | Claude、ChatGPT、VS Code、Goose など |
実際の開発では次のようになります。
- 新規プロジェクト:MCP Apps(
ui.resourceUri、text/html;profile=mcp-app、Appクラス)を使いましょう。OpenAI の ドキュメントも共通の名前を使うよう求めており、window.openaiは仕様にない機能にだけ使うよう案内しています。該当するのはrequestCheckout、ファイルのアップロードと選択、requestModal、widgetStateです。ホスト名で分岐せず、機能検出で使えるか判断してください。 - 既存の ChatGPT アプリ:従来の
window.openai.toolInput・toolOutput・callTool・sendFollowUpMessageはエイリアスとして引き続き動きます。migrate-oai-appスキルと 移行ガイド が、それぞれをontoolinput・ontoolresult・callServerTool・ui/messageに対応づけてくれます。 - ホストを自作する場合:MCP-UI のクライアント SDK(
AppRenderer)は機能のそろった MCP Apps レンダラーです。@modelcontextprotocol/ext-apps/app-bridgeで View を自前で埋め込むこともできます。
よくあるトラブル
- テキストは出るが UI が出ない:ツールに
_meta.ui.resourceUriがあるか、リソースが MIMEtext/html;profile=mcp-appで登録されているか、ホストが MCP Apps に対応しているかを確認します。 - iframe が真っ白、またはリクエストが失敗する:原因はほぼ CSP です。バンドルが読み込んだり呼び出したりするオリジンは、開発時の localhost も含めてすべて
resourceDomainsかconnectDomainsに列挙する必要があります。漏れたオリジンは何も表示されずに失敗します。違反はブラウザのコンソールで確認できます。 - 最初の結果が表示されない:
ontoolresultとontoolinputはapp.connect()の前に設定します。 - デプロイ後も UI が古いまま:OpenAI のドキュメントは、リソース URI をキャッシュキーとして扱うよう勧めています。互換性のない変更をしたら新しい URI を公開し、参照しているツールを更新します。
- Claude が
ui.domainを拒否する:Claude で使う値は、サーバー URL の SHA-256 の先頭 32 桁の 16 進数に.claudemcpcontent.comを付けたものです。詳しくは Claude のドキュメントを参照してください。
次のステップ
MCP Apps でツールの結果はクリックして操作できる画面になりますが、その裏のツール自体が良くなければ意味がありません。MCP サーバーディレクトリ で既存のサーバーがツールをどう設計しているかを見たり、おすすめの MCP サーバー からヒントを得たりしてください。App を公開したら、mcp.so に登録してほかの人に見つけてもらいましょう。
よくある質問
MCP Apps とは何ですか?
- MCP Apps は Model Context Protocol の公式拡張(SEP-1865)で、MCP サーバーがインタラクティブな UI を配信できるようにします。ツールが ui:// の HTML リソースを指し、ホストがそれをサンドボックス iframe で描画して JSON-RPC でやり取りします。
MCP Apps の仕様は安定版ですか?
- はい。MCP Apps の仕様はバージョン 2026-01-26 で安定版となり、2026 年 1 月 26 日に初の公式 MCP 拡張として発表されました。draft 版は modelcontextprotocol/ext-apps リポジトリで引き続き更新されています。
MCP Apps に対応しているクライアントは?
- Claude(Web とデスクトップ)、ChatGPT、GitHub Copilot を使う VS Code、Goose が対応しており、Postman や MCPJam などでも描画できます。フルスクリーンやピクチャーインピクチャーなどの機能対応はホストごとに異なります。
MCP Apps と OpenAI Apps SDK の違いは?
- OpenAI Apps SDK は ChatGPT 独自のアプリ UI の仕組みで、MCP Apps はそれを一部土台にしたオープン標準です。現在の ChatGPT は MCP Apps を実装し、window.openai の名前をエイリアスとして残しつつ、決済やファイルアップロードなど ChatGPT 専用の機能を追加しています。