create-mcp-app について
create-mcp-appは、MCP対応ホスト内で動作するインタラクティブなUIを構築するためのスキルです。MCPツールとHTMLリソースの組み合わせ方を案内し、フレームワーク例、実装手順、ローカルテスト方法を提供します。
主な機能
- 01MCPツールとHTMLリソースの組み合わせ
- 02ReactなどのUIフレームワーク向けガイド
- 03既存サーバーへのアプリ追加例
- 04ホストコンテキスト、ストリーミング、ビュー状態のパターン
- 05リファレンスホストを使ったローカルテスト
活用シーン
- 01MCPサーバーにインタラクティブなUIを追加
- 02ReactやVanilla JavaScriptでMCP Appを作成
- 03UIのライフサイクルとホストコンテキストを実装
- 04basic-hostでアプリをローカルテスト
MCP 対応
MCPツールがデータを返し、HTMLリソースがMCP対応ホスト向けのUIを提供するMCP Appの構築を案内します。MCPサーバーへのツールとリソースの登録方法も扱います。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ modelcontextprotocol/ ext-apps --skill create-mcp-appインストール後、Claude Code で/create-mcp-appを入力して読み込まれたか確認します。
詳細
MCP App の作成
Claude Desktop などの MCP 対応ホスト内で実行される、インタラクティブな UI を構築します。MCP App は、MCP ツールと HTML リソースを組み合わせて、リッチでインタラクティブなコンテンツを表示します。
基本概念:ツール + リソース
すべての MCP App には、次の 2 つの部分を関連付ける必要があります。
- ツール - LLM/ホストから呼び出され、データを返します
- リソース - データを表示するバンドル済み HTML UI を提供します
ツールの _meta.ui.resourceUri は、リソースの URI を参照します。
ホストがツールを呼び出す → ホストがリソースの UI をレンダリングする → サーバーが結果を返す → UI が結果を受け取る。
クイックスタートの選択ガイド
フレームワークの選択
| フレームワーク | SDK のサポート | 最適な用途 |
|---|---|---|
| React | useApp フックを提供 | React に慣れているチーム |
| Vanilla JS | ライフサイクルを手動で管理 | シンプルなアプリ、ビルドの複雑さを避けたい場合 |
| Vue/Svelte/Preact/Solid | ライフサイクルを手動で管理 | 特定のフレームワークを使いたい場合 |
プロジェクトの状況
既存の MCP サーバーに追加する場合:
- SDK から
registerAppTool、registerAppResourceをインポートします _meta.ui.resourceUriを指定してツール登録を追加します- バンドル済み HTML を提供するリソース登録を追加します
新しい MCP サーバーを作成する場合:
- トランスポート(stdio または HTTP)を使ってサーバーをセットアップします
- ツールとリソースを登録します
vite-plugin-singlefileを使ってビルドシステムを設定します
参考コードの取得
動作するサンプルと API ドキュメントを確認するには、SDK リポジトリをクローンします。
git clone --branch "v$(npm view @modelcontextprotocol/ext-apps version)" --depth 1 https://github.com/modelcontextprotocol/ext-apps.git /tmp/mcp-ext-apps
フレームワークのテンプレート
/tmp/mcp-ext-apps/examples/basic-server-{framework}/ を参考にして、内容を学び、応用します。
| テンプレート | 主なファイル |
|---|---|
basic-server-vanillajs/ | server.ts、src/mcp-app.ts、mcp-app.html |
basic-server-react/ | server.ts、src/mcp-app.tsx(useApp フックを使用) |
basic-server-vue/ | server.ts、src/App.vue |
basic-server-svelte/ | server.ts、src/App.svelte |
basic-server-preact/ | server.ts、src/mcp-app.tsx |
basic-server-solid/ | server.ts、src/mcp-app.tsx |
各テンプレートには、次のものが含まれています。
registerAppToolとregisterAppResourceを含むserver.ts- HTTP および stdio トランスポートのセットアップを含む
main.tsエントリーポイント - ライフサイクルハンドラーを含むクライアント側アプリ(例:
src/mcp-app.ts、src/mcp-app.tsx) - グローバルスタイルとホストスタイル変数のフォールバックを含む
src/global.css vite-plugin-singlefileを使用するvite.config.tsnpm runスクリプトと必要な依存関係を含むpackage.jsonnode_modules/とdist/を除外する.gitignore
API リファレンス(ソースファイル)
/tmp/mcp-ext-apps/src/ の JSDoc ドキュメントを直接参照します。
| ファイル | 内容 |
|---|---|
src/app.ts | App クラス、ハンドラー(ontoolinput、ontoolresult、onhostcontextchanged、onteardown など)、ライフサイクル |
src/server/index.ts | registerAppTool、registerAppResource、ヘルパー関数 |
src/spec.types.ts | すべての型定義:McpUiHostContext、McpUiStyleVariableKey(CSS 変数名)、McpUiResourceCsp(CSP 設定)など |
src/styles.ts | applyDocumentTheme、applyHostStyleVariables、applyHostFonts |
src/react/useApp.tsx | React アプリ向けの useApp フック |
高度なパターン
詳しい手順については、/tmp/mcp-ext-apps/docs/patterns.md を参照してください。
- App 専用ツール —
visibility: ["app"]を使って、ツールをモデルから隠します - ポーリング — リアルタイムダッシュボード、インターバル管理
- 分割レスポンス — 大きなファイル、ページネーション、base64 エンコーディング
- エラー処理 —
isErrorを使って、失敗をモデルに通知します - バイナリーリソース —
resources/read、blob フィールドを介した音声/動画など - ネットワークリクエスト — アセット、fetch、CSP、
_meta.ui.csp、CORS、_meta.ui.domain - ホストコンテキスト — テーマ、スタイル、フォント、セーフエリアのインセット
- 全画面モード —
requestDisplayMode、表示モードの変更 - モデルコンテキスト —
updateModelContext、sendMessageを使って、モデルに最新情報を伝えます - ビューの状態 —
viewUUID、localStorage、状態の復元 - 表示状態に応じた一時停止 — IntersectionObserver を使って、アニメーション/WebGL を一時停止します
- ストリーミング入力 —
ontoolinputpartial、段階的なレンダリング
参照ホスト実装
/tmp/mcp-ext-apps/examples/basic-host/ は、MCP Apps に対応したホストの実装例を示しています。Claude Desktop のような実際のホストは、より高度な機能を備えています。basic-host はローカルでのテストとプロトコルの理解に使用し、ホストの動作を保証するものとして扱わないでください。
重要な実装上の注意事項
依存関係の追加
依存関係を追加する際は、バージョン番号を手動で記述せず、必ず npm install を使用してください。
npm install @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 express cors
npm install -D typescript vite vite-plugin-singlefile concurrently cross-env @types/node @types/express @types/cors
ext-apps 2.x では、分割されたベース MCP SDK パッケージ(@modelcontextprotocol/client、
server、node、express)の ^2.0.0 が必要です。@modelcontextprotocol/core は
推移的に導入されます。レガシー版の @modelcontextprotocol/sdk v1 パッケージを追加したり、
未公開のローカルパッケージで代用したりしないでください。
TypeScript サーバーの実行
ユーザーから別の指定がない限り、TypeScript サーバーファイルの実行には tsx を使用してください。例:
npm install -D tsx
npm pkg set scripts.dev="cross-env NODE_ENV=development concurrently 'cross-env INPUT=mcp-app.html vite build --watch' 'tsx --watch main.ts'"
メモ
SDK の例では
bunを使用していますが、幅広い互換性を確保するため、生成されるプロジェクトではtsxをデフォルトにしてください。
ハンドラーの登録順序
app.connect() を呼び出す前に、すべてのハンドラーを登録してください。
const app = new App({ name: "My App", version: "1.0.0" });
// Register handlers first
app.ontoolinput = (params) => { /* handle input */ };
app.ontoolresult = (result) => { /* handle result */ };
app.onhostcontextchanged = (ctx) => { /* handle context */ };
app.onteardown = async () => { return {}; };
// etc.
// Then connect
await app.connect();
よくある避けるべき間違い
- テキストのフォールバックがない - UI に対応していないホスト向けに、必ず
content配列を指定してください - CSP の設定がない - MCP Apps の HTML は、同一オリジンのサーバーがない状態で MCP リソースとして提供されます。
localhost宛てであっても、すべてのネットワークリクエストに CSP 設定が必要です - CSP または CORS の設定が誤った _meta オブジェクトにある -
_meta.ui.cspと_meta.ui.domainは、registerAppResource()の設定オブジェクトではなく、registerAppResource()の読み取りコールバックが返すcontents[]オブジェクトに指定してください app.connect()の後にハンドラーを登録する -app.connect()を呼び出す前に、すべてのハンドラーを登録してください- 大きな入力にストリーミングを使っていない - 入力の生成中に進捗を表示するには、
ontoolinputpartialを使用してください
テスト
basic-host の使用
basic-host の例を使って、MCP Apps をローカルでテストします。
# Terminal 1: Build and run your server
npm run build && npm run serve
# Terminal 2: Run basic-host (from cloned repo)
cd /tmp/mcp-ext-apps/examples/basic-host
npm install
SERVERS='["http://localhost:3001/mcp"]' npm run start
# Open http://localhost:8080
SERVERS には、サーバー URL の JSON 配列を設定してください(デフォルト:http://localhost:3001/mcp)。
sendLog によるデバッグ
(iframe の開発者コンソールだけでなく)ホストアプリケーションにデバッグログを送信します。
await app.sendLog({ level: "info", data: "Debug message" });
await app.sendLog({ level: "error", data: { error: err.message } });