generate-mcp-app-ui について
チャット内でツール出力を視覚化する、単一ファイルの MCP App ウィジェットを生成するスキルです。サンプル JSON と表示要件が必要で、セルフコンテインド形式に対応し、明示的な許可があれば CDN ライブラリも使えます。
主な機能
- 01用途を絞った HTML ウィジェットの生成
- 02ツールのテスト JSON の利用
- 03データに応じた表示形式の選択
- 04セルフコンテインド形式と CDN 形式
- 05callServerTool によるツール接続
活用シーン
- 01ツール結果のチャートや地図での表示
- 02ツール出力を使った比較表の作成
- 03ツールデータを再取得する対話型ウィジェットの作成
MCP 対応
MCP Apps 向けのウィジェットを生成します。ツール名が指定された場合、`callServerTool` を通じて MCP ツールに接続できます。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill generate-mcp-app-uiインストール後、Claude Code で/generate-mcp-app-uiを入力して読み込まれたか確認します。
詳細
トリガー: mcp app, mcp widget, generate widget, create widget, build widget, widget for tool, visual for tool
キーワード: mcp apps, widget, html widget, tool visualization, fluent ui, ext-apps
別名: /generate-mcp-app-ui, /mcp-app, /widget
参考資料:
- MCP Apps API と技術パターン:mcp-apps-reference.md
- ビジュアルデザインの既定値とテーマトークン:design-guidelines.md
- 自己完結型ソーステンプレート:self-contained-widget.template.html
あなたは MCP App ウィジェットのジェネレーターです。チャットの会話内でツールの出力を視覚的に表示する、目的を絞った単機能のウィジェットを作成します。
ユーザーから必要な情報
-
希望するビジュアルの説明(「グラフとして表示する」「比較表を表示する」「これらを地図上に表示する」など)
-
ツールのテスト出力 - ツールをテストして得られた実際の JSON です。通常の構造化ペイロード、
content、structuredContent、metaを含む作成時のエンベロープ、またはcontent、structuredContent、_metaを含む実行時の結果の場合があります。 -
公開 CDN URL の使用が許可されているかどうか。 ユーザーがまだ方針を示していない場合は、次のように尋ねます。
このウィジェットで、実行時ライブラリや可視化ライブラリを公開 CDN から読み込んでもよいですか?
次の選択肢をこの順序で提示します。
- CDN なし/自己完結型(推奨) - MCP Apps ランタイムを埋め込み、リモートスクリプト、モジュール、スタイル、フォント、画像、地図タイル、その他の実行時アセットを使用しない HTML ファイルを 1 つ生成します。
- CDN の使用を許可 - CDN ベースのより小さな出力を維持し、明確な利点がある場合は外部の可視化ライブラリを使用します。
CDN モードを暗黙に選択しないでください。ユーザーが希望を指定しない場合は、自己完結型モードを使用します。
ユーザーがツールのテスト出力もスキーマも提示していない場合は、生成前に必ず質問してください。データの形状を推測してはいけません。推測したスキーマでは、実際のツールに接続したときにウィジェットが動作しなくなります。
次のように尋ねてください。
ツールで動作するウィジェットを生成するには、ツールが返すデータを確認する必要があります。ツールをテストして、出力された JSON をここに貼り付けてもらえますか?ツールの出力形式は JSON に設定してください。
ツールのテスト JSON は必ず必要です。設計を始める前に、次のように正規化してください。
- 予約済みの結果キーを含まない通常のオブジェクトは、
structuredContentを表します。 content、structuredContent、metaのいずれかを含むオブジェクトは、作成時の結果エンベロープです。metaを実行時の_metaに名前変更します。_metaを含むオブジェクトは、すでに実行時の MCP 結果形式になっています。
content と structuredContent はモデルから参照できます。_meta はウィジェット専用で、モデルには不要でもウィジェットが必要とするデータや表示上のヒントを格納する適切な場所です。ウィジェットでは、これらのチャネルを任意に組み合わせて使用できます。視覚的な価値がない限り、会話用の content を UI に重複して表示しないでください。
ユーザーがツール名も提示した場合は、callServerTool を設定してウィジェットからツールを対話的に呼び出せるようにします(更新ボタンなど)。ツール名が指定されていない場合、ウィジェットはデータを読み取り専用で表示します。callServerTool の例については、samples/weather-refresh-widget.html を参照してください。
ウィジェットについて考える
ウィジェットは会話内のカードであり、単独で動作するアプリではありません。次の原則を念頭に置いてください。
- 会話が入力です。 ユーザーはすでにチャットでリクエストを入力しています。ツールがそれを処理し、ウィジェットが結果を視覚的に表示します。チャットでユーザーが伝えた内容と重複する検索バーやテキスト入力欄は追加しないでください。
- LLM のテキスト応答が説明です。 ウィジェットの下に表示されるモデルのテキストが、詳しいリストや説明を提供します。ウィジェットは、テキストだけでは伝えられない視覚的な要素(地図、グラフ、画像、インタラクティブな要素)を提供します。LLM のテキストですでに説明されている内容を、ウィジェットで再び列挙しないでください。
- 基本はコンパクトに。 視覚的な価値を重視してください。ツールが項目のリストを返す場合は、テキストを再び列挙するより、地図、グラフ、カード形式のレイアウトのほうが有用かどうかを検討してください。
- 表示は1つに。 タブ、ページ移動、戻るボタンは使用しないでください。ユーザーが別のものを求める場合は、チャットで依頼します。
- データに適したビジュアルを選んでください。 座標には地図、数値や傾向のデータにはグラフ、構造化されたレコードにはカード、比較にはテーブル、イベントにはタイムラインを使用します。特定のビジュアル形式を常に使うのは避けてください。
生成方法
-
MCP Apps API、配信モード、ライブラリ、技術的なパターンについては、mcp-apps-reference.mdを読んでください。
-
ビジュアルデザインの基本設定については、design-guidelines.mdを読んでください。
-
テスト出力を実行時の
{ content, structuredContent, _meta }形式に正規化し、要求されたビジュアルに関連するすべてのチャネルを確認してください。 -
数値、ブール値、またはオプションのフィールドを読み取る際は、型安全なチェックを使用してください。mcp-apps-reference.md の「データ型の安全性」を参照してください。実行時の型がサンプルと一致するとは限りません。
-
データを最も適切に表現するビジュアルを選んでください。
-
選択した配信モードのテンプレートを使って、HTML ファイルを1つ生成してください。
-
ファイルを
./mcp-app-widget.html(または./travel-map.htmlのような説明的な名前)に書き込んでください。 -
セルフホストモードでは、完了を報告する前に決定的インライン化スクリプトを実行してください。
node "${PLUGIN_ROOT}/scripts/inline-self-contained-runtime.js" --input "./mcp-app-widget.html"このスクリプトは実行時マーカーをその場で置き換え、下書きに外部のランタイム/リソース URL が残っている場合は失敗します。
-
ファイルの場所と、プレビューするためにブラウザーで開く方法をユーザーに伝えてください。
CDN モードのテンプレート
ユーザーがパブリック CDN の利用を許可した場合に限り、このテンプレートを使用してください。ウィジェットのロジックはすべて単一の <script type="module"> ブロックに記述してください。CDN から MCP Apps の App クラスを使用してください。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/@fluentui/web-components@beta/dist/web-components.min.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
padding: 24px;
font-family: var(--fontFamilyBase, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
font-size: 14px;
line-height: 1.5;
background: var(--colorNeutralBackground1, #fff);
color: var(--colorNeutralForeground1, #242424);
overflow-x: hidden;
}
</style>
</head>
<body>
<div id="widget-root"></div>
<script type="module">
// IMPORTANT: App is a NAMED export — use { App } with curly braces
// WRONG: import App from '...' (default import — App will be undefined)
// RIGHT: import { App } from '...'
import { App } from 'https://cdn.jsdelivr.net/npm/@modelcontextprotocol/ext-apps/+esm';
import { webLightTheme, webDarkTheme } from 'https://cdn.jsdelivr.net/npm/@fluentui/tokens/+esm';
// --- Theme ---
function applyTheme(theme) {
const tokens = theme === 'dark' ? webDarkTheme : webLightTheme;
const root = document.documentElement;
for (const [token, value] of Object.entries(tokens)) {
root.style.setProperty('--' + token, value);
}
document.body.style.background = theme === 'dark'
? 'var(--colorNeutralBackground1, #292929)'
: 'var(--colorNeutralBackground1, #fff)';
document.body.style.color = theme === 'dark'
? 'var(--colorNeutralForeground1, #e0e0e0)'
: 'var(--colorNeutralForeground1, #242424)';
}
// Keep all result channels so the widget can intentionally use model-visible
// content/structuredContent and widget-private _meta.
function normalizeToolResult(result) {
return {
content: result?.content ?? [],
structuredContent: result?.structuredContent ?? {},
_meta: result?._meta ?? {}
};
}
function hasToolResultData(result) {
return result.content.length > 0
|| Object.keys(result.structuredContent).length > 0
|| Object.keys(result._meta).length > 0;
}
// --- Your render functions go here ---
function renderLoading() { /* ... */ }
function renderData(toolResult) {
// Read toolResult.content, toolResult.structuredContent, and/or toolResult._meta.
}
function renderError(message) { /* ... */ }
// --- Show loading immediately ---
renderLoading();
// --- MCP Apps setup ---
const app = new App({ name: "widget", version: "1.0.0" });
app.ontoolresult = (result) => {
const toolResult = normalizeToolResult(result);
if (hasToolResultData(toolResult)) {
renderData(toolResult);
} else {
renderError('No data received.');
}
};
app.onhostcontextchanged = (ctx) => {
if (ctx.theme) { applyTheme(ctx.theme); }
};
app.onteardown = () => ({});
await app.connect();
// Apply initial theme from host
const hostCtx = app.getHostContext();
if (hostCtx?.theme) { applyTheme(hostCtx.theme); }
</script>
</body>
</html>
セルフホストモードのテンプレート
self-contained-widget.template.htmlから始めてください。
最終的なインライン化の手順まで、この正確なマーカーを <head> 内に残してください。
アプリケーションモジュールは、埋め込みランタイムから依存関係を取得します。
const { App, webLightTheme, webDarkTheme } = globalThis.McpAppsRuntime;
このモードでは:
import宣言、リモートの<script src>、スタイルシート、フォント、画像、 iframe、メディア、マップタイル、ワーカー、またはリテラルなネットワークfetch呼び出しを追加しないでください。- インライン CSS でスタイルを設定したセマンティック HTML コントロールを使用してください。
<fluent-*>要素は出力しないでください。 Fluent Web Components は意図的にバンドルされていません。 - バンドル済みの Fluent テーマトークンを使って、ライト/ダークの色を設定してください。
- グラフ、タイムライン、図、地図はインライン SVG、Canvas、または CSS で実装してください。 地理的なプロットでは、座標とインラインの概略図/ベースの輪郭を使えますが、 タイルを取得してはいけません。
<script type="module">のアプリケーションブロックはちょうど1つにしてください。インライナーがその前に ランタイムをクラシックなインラインスクリプトとして追加します。- ウィジェットを編集し終えたら、インライナーを実行してください。縮小済みランタイムを手動でファイルにコピーしたり、最終出力にマーカーを残したりしないでください。
改良
ユーザーから既存のウィジェットの変更(「もっとカラフルにして」「グラフを追加して」「地図を大きくして」など)を依頼された場合:
-
既存の HTML ファイルを読み込みます
-
依頼された変更のみを行います
-
依頼がない限り、ウィジェットの構成を変更したり、新機能を追加したり、機能を削除したりしないでください
-
既存の CDN ポリシーを維持します。自己完結型のウィジェットの場合は、以下のコマンドで用意した一時コピーを編集し、編集後にインライナーを再実行します。圧縮済みのランタイムを手動で編集しないでください。
node "${PLUGIN_ROOT}/scripts/inline-self-contained-runtime.js" --prepare --input "./widget.html" --output "./widget.draft.html" -
更新したファイルを書き込みます
出力ルール
<!DOCTYPE html>で始まる、完全な単一ファイルの HTML ページを出力します- HTML をファイルに書き込みます。チャットに表示するだけではいけません
- 自己完結型モードでは、最終ファイルに埋め込み済みのランタイムを含め、
MCP_APPS_SELF_CONTAINED_RUNTIMEを含めず、リモートのランタイムアセットを読み込まないでください。 - CDN モードでは、ユーザーが明示的に許可した後に限り、公開 CDN リソースを使用します。
ontoolresultとcallServerToolの各経路で、すべての結果チャネルを維持します。ウィジェット専用のメタデータはランタイムのresult._metaから読み取り、result.metaは使用しないでください。- ファイルの保存場所をユーザーに伝えます
- 変更を希望する場合はチャットで内容を伝えればよいことをユーザーに知らせます:「変更をご希望の場合は、チャットで内容をお伝えください(例:「地図を大きくして」「グラフを追加して」「カードレイアウトを使って」)。」
- 作成する CSS とウィジェットのロジックはすべてインラインにします。選択した提供モードで許可されているリソースのみ、外部リソースとして使用できます。