ui-widget-developer
microsoft/skills
ReactとFluent UIを使用して、インタラクティブなウィジェットを備えたCopilot Chat用のMCPサーバーを構築し、OpenAI Apps SDKとMCP Apps Extensionの両方の標準に対応します。
...すべて拡張しますCopilot MCP サーバーの開発
テレメトリのタグ付け
atkCLI コマンドを実行する前に、すべての CLI 呼び出しがスキルによって開始されたものとしてタグ付けされるよう、セッション環境変数を設定してください:
export ATK_CLI_SKILL=true
セッションの開始時にこれを一度実行してください。同じターミナルでのその後のすべてのatkコマンドは、この設定を継承します。
🔀 経路の選択
続行する前に、AskUserQuestion を使用して、ユーザーにどのパスを選択したいかを尋ねてください。M365 Copilot ウィジェットのレンダリングでは、両方のパスがサポートされています。それぞれのメリット・デメリットを提示し、ユーザーに選択させてください:
| OAI Apps(このスキル) | MCP Apps Extension(modelcontextprotocol/ext-apps) |
|
|---|---|---|
| 標準 | OpenAI固有 | 公式MCP標準 |
| 動作環境 | ChatGPT および M365 Copilot | M365 Copilot、ChatGPT、VSCode など |
| 成熟度 | 実戦検証済み、本番環境対応 | 新たな公式標準、拡大するエコシステム |
| 設計 | OpenAI Apps SDK | MCP Appsプロトコル(クロスプラットフォーム) |
| 選択すべきタイミング | 既存のOAIアプリへの投資 | オープン標準を優先し、可能な限り幅広いクライアントサポートを望む場合 |
質問: 「OAIアプリ(OpenAI Apps SDK — 実戦実績あり、ChatGPTおよびM365 Copilotで動作)とMCPアプリ(新しい公式標準 — M365 Copilot、ChatGPT、VSCodeなどで動作)のどちらを作成しますか?」
- OAIアプリ→ 以下の手順に進んでください。このスキルで必要な機能はすべて網羅されています。
- MCPアプリ→
modelcontextprotocol/ext-appsプラグインをインストールし(下記参照)、そのプラグインから適切なスキルを使用してください。
MCPアプリ:ext-appsプラグインのインストール
ユーザーがMCPアプリを選択した場合は、自動的にこの操作を行ってください(説明のみにとどめないでください):
/plugin marketplace add modelcontextprotocol/ext-appsを実行/plugin install mcp-apps@mcp-appsを実行- プラグインが利用可能であることを確認し、ユーザーの意図に基づいて適切な ext-apps スキルを呼び出します
現在の環境でプラグインコマンドが利用できない場合は、以下の正確なコマンドを提示し、ユーザーに一度実行するよう依頼してから、選択された ext-apps スキルを呼び出して処理を続行してください。
参照コマンド:
MCPアプリを構築するには、マーケットプレイスからext-appsプラグインをインストールしてください:
1. /plugin marketplace add modelcontextprotocol/ext-apps
2. /plugin install mcp-apps@mcp-apps
その後、そのプラグインに含まれる以下のスキルのいずれかを使用します:
- create-mcp-app — 対話型 UI を持つ新しい MCP アプリをゼロから構築する
- add-app-to-server — 既存の MCP サーバーのツールに対話型 UI を追加する
- migrate-oai-app — 既存の OAI アプリを MCP アプリに対応するように変換します
- convert-web-app — Web アプリを、Web と MCP アプリを組み合わせたハイブリッドアプリに変換します
インストール後、該当するスキルを実行して続行してください。
注:ext-apps プラグインは、外部の
modelcontextprotocol/ext-appsマーケットプレイスに存在します。このプラグインコレクションの一部ではありません。
インストール後のマッピング設定:
- ゼロから新しいMCPアプリを作成 →
create-mcp-app - 既存のMCPサーバーにアプリUIを追加 →
add-app-to-server - 既存のOAIアプリを移行 →
migrate-oai-app - 既存のWebアプリを変換 →
convert-web-app
📛 プロジェクトの検出 📛
このスキルは、Microsoft 365 Copilot Chat 向けの OAI アプリまたはウィジェットレンダリング機能を備えた MCP サーバーを構築する際にトリガーされます。MCP サーバーは、MCP プロトコルをサポートする任意の言語(TypeScript、Python、C# など)で記述できます。 エージェントプロジェクトとMCPサーバーは、同じリポジトリ内、別々のフォルダー、あるいはまったく別のプロジェクトに配置することができます。
シナリオのルーティング
| 開始点 | 必要なもの | パス |
|---|---|---|
| MCP Apps 標準を推奨 | クロスプラットフォームのウィジェット対応(M365 Copilot、ChatGPT、VSCode など) | modelcontextprotocol/ext-apps をインストールし、create-mcp-appまたはadd-app-to-serverを使用してください — 上記の「進め方」を参照 |
| ゼロからの構築(エージェントなし、MCPサーバーなし) | OAIアプリの完全なセットアップ | まずdeclarative-agent-developerにエージェントのスケルトン生成を委任し、その後ここに戻って MCP サーバーとウィジェットの設定を行う |
| 既存の M365 エージェント、新しい MCP サーバー | MCPサーバー+ウィジェット+mcpPlugin.json | 実装から開始 |
| 既存のMCPサーバーにCopilotウィジェットを追加 | 既存のサーバーにウィジェットサポートを追加 | Copilotウィジェットプロトコルから開始 |
| 言語の選択(TypeScript以外) | プロトコルの要件 | 実装すべき内容については「Copilotウィジェットプロトコル」を参照し、「MCPサーバーパターン(TypeScript)」を参考としてください |
🚨 重要な実行ルール 🚨
FLUENT UI の適用(必須):ウィジェットの実装では、React および Fluent UI コンポーネントを使用しなければなりません。ウィジェットのコードを記述する前に、エージェントは必ず以下を読み、それに従わなければなりません:
references/widget-patterns.mdreferences/best-practices.mdFLUENT UI パッケージ要件(必須):ウィジェットプロジェクトは、実装前に Fluent UI の依存関係を含める必要があります。少なくとも、以下のものをウィジェットパッケージの依存関係としてインストールし、維持してください:@fluentui/react-componentsreactreact-dom
これらのパッケージのいずれかが欠けている場合は、ウィジェットのコード生成を続行する前に、自動的にインストールしてください。
生成されたウィジェットに、Reactのエントリファイル(例:widgets/src/コンポーネントファイル)と、@fluentui/react-componentsからのFluentインポートが含まれていない場合、タスクは不完全であり、結果を返す前に修正する必要があります。
生の HTML のみのウィジェットは使用不可(デフォルト):最終的なウィジェットソリューションとして、静的な HTML テンプレートやインライン JS を使用してアプリコンテンツを直接実装してはなりません。 最小限のシェルHTMLファイルは、ビルド済みReactアセットのローダーとしてのみ許可されます。生の、または自己完結型のHTMLのみのウィジェットは、ユーザーが明示的に非Reactプロトタイプを要求した場合にのみ許可されます。
バックグラウンドプロセス:MCP サーバーおよび devtunnel は、エージェントのシェルセッション内で実行するのではなく、独立した OS プロセスとして起動する必要があります。isBackground: true、mode: "async"、およびStart-Jobはすべてエージェントのシェルセッション内で実行され、メッセージの間に終了されます。 唯一信頼できる方法は、分離された OS プロセスを起動することです。
Windows —Start-Process -WindowStyle Hidden を使用:
# devtunnel を起動
$t = Start-Process -FilePath "devtunnel" `
-ArgumentList "host","","-a" `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput "tunnel.log" -RedirectStandardError "tunnel-err.log"
# MCP サーバーの起動 — cmd.exe /c を使用して作業ディレクトリを設定し、PATH を継承
$s = Start-Process -FilePath "cmd.exe" `
-ArgumentList "/c","cd /d &&" `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput "server.log" -RedirectStandardError "server-err.log"
# 後で停止できるようにPIDを保存
"$($t.Id),$($s.Id)" | Out-File pids.txt
Write-Host "トンネルPID $($t.Id)、サーバーPID $($s.Id) を起動しました"
停止するには:Stop-Process -Id (Get-Content pids.txt).Split(',')またはStop-Process -Id 。
Linux/Mac —& 付きでnohupを使用:
nohup devtunnel host > tunnel.log 2>tunnel-err.log &
echo "tunnel:$!" >> pids.txt
nohup > server.log 2>server-err.log &
echo "server:$!" >> pids.txt
停止するには:kill $(grep -oP '\d+' pids.txt) を実行します。
起動後、ログをtailして両方のプロセスが正常に動作していることを確認してから、次の手順に進んでください:
# Windows
Start-Sleep 3; Get-Content tunnel.log, server.log
# Linux/Mac
sleep 3 && tail tunnel.log server.log
完全自動化:ユーザーに手動でコマンドを実行するよう指示してはいけません。ツールのインストール、認証、サービスの起動など、すべてを自動的に行います。ユーザーにインタラクティブな入力を求めるのは、本当に必要な場合(devtunnel の `user login -g -d` 時のデバイスコードの確認など)に限ります。 ツールがインストールされていない場合はインストールし、サービスを開始する必要がある場合は開始してください。ユーザーは完全な自動化を期待しています。
パス選択(必須 — コードを書く前に必ず実施):コードを記述したり、コマンドを実行したり、アーキテクチャ上の決定を行ったりする前に、AskUserQuestionを使用して、ユーザーに OAI Apps または MCP Apps Extension のどちらを使用したいかを尋ねる必要があります。
このルールに例外はありません。最も一般的な失敗パターンは、「ユーザーのリクエストから明らかだから、確認するのは冗長だ」という理屈です。この理屈は常に誤りです。どのような場合でもAskUserQuestionを呼び出してください。 ユーザーが「ウィジェットを使ってMCPサーバーを構築してください」と言ったとしても、これはこの質問に対する答えではありません。ユーザーがこのスキルの名前を呼んで呼び出したとしても、それは答えではありません。この質問に対する明確な回答のみが有効です。尋ねるべき正確な質問については、上記の「パス選択」を参照してください。
エージェントのプロビジョニング:再プロビジョニングが必要となるのは、エージェントのマニフェストが変更された場合のみです(例:mcpPlugin.json のツール定義、MCP サーバーの URL、declarativeAgent.json、instruction.txt)。 MCPサーバーのコード変更(ツールの実装、Reactウィジェットのコード、サーバーロジック)については、エージェントの再プロビジョニングは必要ありません。サーバーの実行またはデプロイを行うと、変更は自動的に反映されます。
プロビジョニングが必要な場合:
manifest.json内のバージョンを更新します(パッチバージョンをインクリメントします。例:1.0.0→1.0.1)- エージェントをデプロイします:
npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local
ウィジェットのテスト用リンク:MCP サーバーが実行中の状態でユーザーに結果を返す際は、ユーザーがローカルでテストできるよう、必ずすべてのウィジェットへのリンクを含める必要があります。形式:
🧪 ウィジェットをローカルでテスト:
- http://localhost:3001/widgets/widget-name.html
- http://localhost:3001/widgets/another-widget.html
mcp-server/widgets/ディレクトリ(または同等のウィジェットフォルダ)内のすべての.htmlファイルを一覧表示してください。これにより、ユーザーは Copilot でテストを行う前に、ウィジェットのレンダリングを確認しやすくなります。
完了時の自動デプロイ(必須 — スキップしないでください):コーディングが完了したら、ユーザーを待たずに自動的に処理を進めます:
- バックグラウンドでMCPサーバーとdevtunnelを起動する(上記の「バックグラウンドプロセス」に従う)
- MCP Inspector を使用して E2E 検証を実行する(以下の「MCP ツールの設定ルール」に従う) — 続行する前に、失敗箇所を修正する
- 必要に応じてエージェントをプロビジョニングする(上記の「エージェントのプロビジョニング」に従う)
- 以下の形式でプロジェクトの概要を出力する:
## ✅ — 準備完了
### ウィジェット
- [widget-name.html](http://localhost:/widgets/widget-name.html)
- [widget-name2.html](http://localhost:/widgets/widget-name2.html)
### エンドポイント
- MCP サーバー: http://localhost:/mcp
- トンネル経由の MCP: https:///mcp
### Copilot でのテスト
ローカル: https://m365.cloud.microsoft/chat/?titleId={env/.env.local 内の M365_TITLE_ID}
その他の環境: {env/.env.{environment} 内の SHARE_LINK}
エージェントプロジェクトの委任:このスキルはMCPサーバーとウィジェットを構築するものであり、宣言型エージェントプロジェクトを構築するものではありません。 ユーザーのリクエストに、宣言型エージェント自体の作成や設定(スケルトン生成、m365agents.yml、m365agents.local.yml、declarativeAgent.json、マニフェストのライフサイクル)が含まれる場合は、declarative-agent-developerスキルに委譲してください。
MCPリソースの登録:すべてのウィジェットには、対応するMCPリソースが必ず存在する必要があります。リソースがない場合、CopilotはMCPプロトコルを通じてウィジェットのシェルを取得できず、ウィジェットはレンダリングされません。
新しいウィジェットを作成するたびに、以下のチェックリストを完了してください:
- ☐
widgets/ディレクトリ内にウィジェットシェルの HTML ファイルを作成し、widgets/src/ディレクトリ内のに React ウィジェットのエントリを作成してください(widget-patterns.md を参照)。/ - ☐
ui://widget/という URI 定数を定義する.html - ☐
resources配列に、以下の内容でResourceエントリを追加してください:uri:ui://widget/URI.html mimeType:"text/html+skybridge"_meta:openai/widgetDomainおよびopenai/widgetCSPを含む CSP 設定(環境から取得)
- ☐ この URI に対応するウィジェットシェル HTML を返す
resources/read用のハンドラを追加する - ☐
_meta.openai/outputTemplateを同じui://widget/URI を指すように設定して、ツールを追加する.html - ☐ サーバーの機能に
resources: {}が含まれていることを、initialize レスポンスで確認する
ウィジェットシェルとアセットに関する考慮事項:
- 推奨(React + Fluent UI):リソース HTML は、MCP サーバーの
/assets/ルートから提供されるビルド済み JS/CSS アセットへのリンクを含む、最小限のシェルであるべきです。 - 例外のみ:
resources/read経由の自己完結型 HTML は、ユーザーが明示的に要求したプロトタイプにのみ使用されます。デフォルトおよび本番環境でのアプローチは React + Fluent UI です。
React ビルド出力のシェル例:
アセット URL のベースには、WIDGET_BASE_URLまたはMCP_SERVER_URL環境変数を使用してください(mcp-server-pattern.md の「Configurable Widget Base URL」セクションを参照)。
リソースおよびアセットの提供パターンに関する詳細は、mcp-server-pattern.mdを参照してください。
⚠️ MCP ツール設定のルール ⚠️
mcpPlugin.json にツール定義を手動で記述してはいけません。常に MCP Inspector を使用して、実行中の MCP サーバーから完全なツール定義を取得してください。
ツールの命名規則:ツール名は、必ず^[A-Za-z0-9_]+$のパターンに一致する必要があります(英字、数字、アンダースコアのみ)。ツール名にハイフン(-)を使用しないでください。代わりにアンダースコアを使用してください(例:render_profile、render-profile ではない)。
必須のワークフロー:
- MCPサーバーを(バックグラウンドで)起動する
- MCP Inspector を使用して最新のツール定義を取得します:
npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --transport http --method tools/list - インスペクタからツール定義のすべて(
名前、説明、inputSchema、_meta、注釈、タイトルを含む)をコピーする -
mcpPlugin.json内のruntimes[].spec.mcp_tool_description.tools(RemoteMCPServerランタイムのspecオブジェクト内)に貼り付ける - devtunnel 経由でE2E 検証を実行します。各ツールを呼び出し、レスポンスに
structuredContentおよび_meta.openai/widgetAccessible: trueが含まれていることを確認します:
また、npx @modelcontextprotocol/[email protected] --cli https:///mcp --transport http --method tools/call --tool-name GET https:///health が {"status":"ok"}を返すことも確認してください。プロビジョニングの前に、失敗があれば修正してください。
MCP Inspectorには、サーバーからの正確なツールスキーマが表示されます。これを完全にコピーしてください。これらの定義を手動で記述したり変更したりしないでください。これにより、mcpPlugin.jsonがMCPサーバーと同期された状態が維持されます。
Microsoft 365 Copilot Chat と統合し、リッチでインタラクティブなウィジェットを表示する MCP サーバーを構築します。
アーキテクチャ
M365 Copilot ──▶ mcpPlugin.json ──▶ MCP サーバー ──▶ structuredContent ──▶ React + Fluent UI ウィジェット
│ (RemoteMCPServer) (Streamable HTTP) (window.openai.toolOutput)
│
└── 機能(People など)が、MCP ツールに渡すデータを提供します
プロジェクト構造
プロジェクト構造の例。必須要件ではありませんが、MCPサーバーとウィジェットの開発を整理するための一般的なパターンです:
project/
├── appPackage/
│ ├── manifest.json # チームマニフェスト(デプロイ時にバージョンを更新)
│ ├── declarativeAgent.json # エージェントの設定 + 機能
│ ├── mcpPlugin.json # _meta を含むツール定義
│ └── instruction.txt # エージェントの動作指示
├── mcp-server/
│ ├── src/index.ts # Streamable HTTP 対応のサーバー
│ ├── widgets/ # ウィジェットシェル + Reactソース
│ │ ├── my-widget.html # resources/readによって返される最小限のシェル
│ │ └── src/my-widget/ # React + Fluent UIソース
│ ├── assets/ # /assets で配信されるビルド済みウィジェットバンドル
│ └── package.json
├── scripts/
│ ├── setup-devtunnel.sh # Linux/Mac 用の devtunnel セットアップ
│ └── setup-devtunnel.ps1 # Windows用devtunnelの設定
└── env/.env.local # MCP_SERVER_URL、MCP_SERVER_DOMAIN
言語に関する注記:これは TypeScript プロジェクトの構成例です。 Pythonの場合は、mcp-server/src/index.tsをPythonのエントリポイント(例:server.py)に置き換えてください。C#の場合は、標準的な.NETプロジェクト構造を使用してください。appPackage/、widgets/、scripts/、およびenv/ディレクトリは言語に依存しません。
Copilot ウィジェットプロトコル
Copilot Chat でウィジェットをレンダリングするには、MCP サーバーが以下のプロトコル要件を実装している必要があります。これは言語に関係なく適用されます:
- ストリーム可能な HTTP トランスポート—
/mcpエンドポイントで、セッション管理を伴う POST、GET、DELETE を処理すること - CORSヘッダー—
/mcpでのオリジンチェックにより、m365.cloud.microsoftおよび*.m365.cloud.microsoftを許可し、必要な MCP ヘッダーを指定 - サーバー機能—
initializeレスポンスでresources: {}およびtools: {}を宣言する必要があります - MCPリソース—
ui://widget/URI、.html text/html+skybridgeMIMEタイプ、およびCSP_metaを使用してウィジェットを登録する - ツールのレスポンス形式—
コンテンツ(text) +structuredContent(ウィジェットデータ) +openai/outputTemplateを含む_metaを返す - ウィジェットの提供— シェルファイル用の
/widgets/*.htmlおよびビルド済みバンドル用の/assets/*という HTTP ルート。いずれも origin-checking CORS を適用
プロトコルの詳細、JSON シェイプ、および既存の MCP サーバー向けの適応チェックリストについては、references/copilot-widget-protocol.md を参照してください。
実装
MCP サーバーパターン (TypeScript リファレンス)
完全な実装については、references/mcp-server-pattern.md を参照してください。
その他の言語については、各言語の MCP SDK を使用して、Copilot Widget Protocol に記載された要件を実装してください。SDK パッケージについては、「Language SDK References」の表を参照してください。
主な要件:
/mcpに対して Streamable HTTP トランスポートを公開するopenai/outputTemplateを使用してstructuredContent+_metaを返す- HTTPエンドポイント経由でウィジェットを提供する
- クロスオリジンリクエストに対するCORSを処理する
- 部分的なデータに対して適切に処理を行う(欠落しているフィールドには「Unknown」を記入する)
ツールのレスポンス形式:
return {
content: [{ type: "text", text: "Summary" }],
structuredContent: { /* ウィジェットデータ */ },
_meta: { "openai/outputTemplate": "ui://widget/name.html", "openai/widgetAccessible": true }
};
部分データの処理
欠落しているフィールドに対処するため、入力データは常に正規化してください:
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
const args = request.params.arguments as { title?: string; items?: Partial- [] };
// データを正規化 - 欠落しているフィールドには「Unknown」を代入
const title = args.title || "Default Title";
const items = (args.items || []).map(item => ({
name: item.name || "Unknown",
value: item.value || "Unknown",
}));
// ウィジェット用のstructuredContentを構築
const structuredContent = { title, items };
// ...
});
ウィジェットパターン
完全な例については、references/widget-patterns.md を参照してください。
主な要件:
- React および Fluent UI コンポーネント (
@fluentui/react-components) を使用すること - ウィジェットパッケージの依存関係に
@fluentui/react-components、react、およびreact-domが含まれていることを確認する FluentProvider(webLightTheme/webDarkTheme) および Fluentトークンを使用してテーマを設定する- 共有フックを介してデータにアクセスする(例:
useOpenAiGlobal("toolOutput")) - デバッグ用フォールバック:
window.openaiが利用できない場合に埋め込まれた模擬データを使用する - 「Unknown」値に対して適切な処理を行う(例:アクションボタンを非表示にする)
プラグインのスキーマ
mcpPlugin.json のフォーマットについては、references/plugin-schema.md を参照してください。
コア要件:
RemoteMCPServerランタイムを含むスキーマv2.4- ツール名と一致する
run_for_functions配列 - ウィジェットバインディングのためのツール定義内の
`_meta` inputSchema- 柔軟性を高めるためプロパティをオプションとし、デフォルト値は説明文で記述する
DevTunnels のセットアップ
ローカルでのテストのみ。DevTunnelsは、ローカルマシン上での開発およびテストを目的としています。エージェントをより広範囲に共有する前に、MCPサーバーとウィジェットアセットの両方をホスティング環境(Azure App Service、Azure Static Web Apps、またはその他のホスティングプロバイダーなど)にデプロイし、それに応じてエージェントマニフェストのURLを更新してください。
DevTunnels は、安定した URL を得るために名前付きトンネルを使用して、ローカルホスト上の MCP サーバーを M365 Copilot に公開します。セットアップスクリプト、コマンドリファレンス、およびトラブルシューティングについては、references/devtunnels.md を参照してください。
セットアップスクリプト(npm run tunnel/npm run tunnel:win):
- 初回実行時に名前付きトンネルを作成します(または既存のものを再利用します)
- 設定されたポートでトンネルのホスティングを開始します
env/.env.local ファイルをMCP_SERVER_URLおよびMCP_SERVER_DOMAINで更新します(初回実行時のみ)- トンネルのホスティングを継続します
クイックスタート
ターミナル 1 - MCP サーバーの起動:
cd mcp-server
npm install
npm run dev
ターミナル 2 - DevTunnel の起動:
npm run tunnel
# または Windows の場合:
npm run tunnel:win
初回実行時は、トンネルが確立されたらエージェントをプロビジョニングしてください(「AGENT PROVISIONING」ルールを参照)。2回目以降の実行では、トンネルURLは固定されます。エージェントのマニフェストが変更されない限り、再プロビジョニングは必要ありません。
開発ワークフロー
MCPサーバーを起動します(ホットリロード機能付きの開発モード):
- TypeScript:
cd mcp-server && npm install && npm run dev - Python:
cd mcp-server && pip install -r requirements.txt && python server.py - C#:
cd mcp-server && dotnet run
- TypeScript:
devtunnel を起動します(初回実行時に名前付きトンネルを作成し、それ以降の実行ではそれを再利用します):
npm run tunnelプロビジョニング + テスト— これが必要なタイミングについては「AGENT PROVISIONING」ルールを参照してください。Copilotに変更が反映されない場合は、manifest.jsonの
バージョンを更新してください
ベストプラクティス
詳細なガイダンスについては、references/best-practices.md を参照してください。
重要なポイント:
- レンダリングツール:データを入力として受け入れる。内部でデータを取得しない
- 指示:まずエージェントに機能の使用を指示し、その後データをMCPツールに渡す
- テーマ:ダークモード/ライトモードの対応には、
FluentProviderと Fluentトークンを使用してください - デバッグモード:ローカルでのウィジェットテスト用にフォールバックデータを含める
- 部分的なデータ:「Unknown」のデフォルト値で欠落フィールドを処理する
- アクションボタン:データが「Unknown」の場合は、メール/チャットボタンを非表示にする
- バージョンアップ:変更がCopilotに反映されない場合は、マニフェストのバージョンを上げる
---
name: ui-widget-developer
description: Build MCP servers for Copilot Chat with interactive widgets using React and Fluent UI, supporting both OpenAI Apps SDK and MCP Apps Extension standards.
---
# Copilot MCP Server Development
## Telemetry Tagging
Before running any `atk` CLI commands, set the session environment variable so all CLI invocations are tagged as skill-initiated:
```bash
export ATK_CLI_SKILL=true
```
Run this once at the start of the session. All subsequent `atk` commands in the same terminal will inherit it.
## 🔀 PATH SELECTION
**Before proceeding, ask the user which path they want to take using AskUserQuestion.** Both are supported for M365 Copilot widget rendering. Present the tradeoffs and let them choose:
| | **OAI Apps** (this skill) | **MCP Apps Extension** (`modelcontextprotocol/ext-apps`) |
|---|---|---|
| **Standard** | OpenAI-specific | Official MCP standard |
| **Works in** | ChatGPT + M365 Copilot | M365 Copilot, ChatGPT, VSCode, and more |
| **Maturity** | Battle-tested, production-ready | New official standard, growing ecosystem |
| **Design** | OpenAI Apps SDK | MCP Apps protocol (cross-platform) |
| **When to choose** | Existing OAI app investment | Prefer the open standard, want broadest client support |
**Ask:** _"Would you like to build an OAI app (OpenAI Apps SDK — battle-tested, works in ChatGPT and M365 Copilot) or an MCP app (new official standard — works in M365 Copilot, ChatGPT, VSCode, and more)?"_
- **OAI apps** → Continue below. This skill covers everything you need.
- **MCP apps** → Install the `modelcontextprotocol/ext-apps` plugin (see below), then use the appropriate skill from that plugin.
### MCP Apps: Install ext-apps Plugin
If the user chooses MCP Apps, do this automatically (do not stop at explanation-only):
1. Run `/plugin marketplace add modelcontextprotocol/ext-apps`
2. Run `/plugin install mcp-apps@mcp-apps`
3. Confirm the plugin is available, then invoke the correct ext-apps skill based on user intent
If plugin commands are unavailable in the current environment, provide the exact commands below and ask the user to run them once, then continue by invoking the selected ext-apps skill.
Reference commands:
```
To build an MCP App, install the ext-apps plugin from the marketplace:
1. /plugin marketplace add modelcontextprotocol/ext-apps
2. /plugin install mcp-apps@mcp-apps
Then use one of these skills from that plugin:
- create-mcp-app — Scaffold a new MCP App with interactive UI from scratch
- add-app-to-server — Add interactive UI to an existing MCP server's tools
- migrate-oai-app — Convert an existing OAI app to use MCP Apps
- convert-web-app — Turn a web app into a hybrid web + MCP App
After installing, invoke the relevant skill to continue.
```
> **Note:** The ext-apps plugin lives in the external `modelcontextprotocol/ext-apps` marketplace — it is not part of this plugin collection.
**Handoff mapping after install:**
- New MCP app from scratch → `create-mcp-app`
- Add app UI to existing MCP server → `add-app-to-server`
- Migrate existing OAI app → `migrate-oai-app`
- Convert an existing web app → `convert-web-app`
---
## 📛 PROJECT DETECTION 📛
This skill triggers when building MCP servers with OAI app or widget rendering for Microsoft 365 Copilot Chat. The MCP server can be written in any language that supports the MCP protocol (TypeScript, Python, C#, etc.). The agent project and MCP server may live in the same repo, separate folders, or entirely different projects.
## Scenario Routing
| Starting Point | What You Need | Path |
|---------------|---------------|------|
| **Prefer MCP Apps standard** | Cross-platform widget support (M365 Copilot, ChatGPT, VSCode, and more) | Install `modelcontextprotocol/ext-apps`, then use `create-mcp-app` or `add-app-to-server` — see [Path Selection](#-path-selection) above |
| **From scratch** (no agent, no MCP server) | Full OAI app setup | Delegate agent scaffolding to `declarative-agent-developer` first, then return here for MCP server + widgets |
| **Existing M365 agent, new MCP server** | MCP server + widgets + mcpPlugin.json | Start at [Implementation](#implementation) |
| **Existing MCP server, add Copilot widgets** | Widget support added to existing server | Start at [Copilot Widget Protocol](references/copilot-widget-protocol.md#adaptation-checklist-existing-mcp-server) |
| **Language choice** (non-TypeScript) | Protocol requirements | See [Copilot Widget Protocol](references/copilot-widget-protocol.md) for what to implement, [MCP Server Pattern (TypeScript)](references/mcp-server-pattern.md) as a reference |
---
## 🚨 CRITICAL EXECUTION RULES 🚨
**FLUENT UI ENFORCEMENT (REQUIRED):** Widget implementations MUST use React + Fluent UI components. Before writing any widget code, the agent MUST read and follow:
- `references/widget-patterns.md`
- `references/best-practices.md`
**FLUENT UI PACKAGE REQUIREMENT (REQUIRED):** The widget project MUST include Fluent UI dependencies before implementation. At minimum, install and keep these in the widget package dependencies:
- `@fluentui/react-components`
- `react`
- `react-dom`
If any of these packages are missing, install them automatically before continuing with widget code generation.
If the generated widget does not include React entry files (for example `widgets/src/<widget-name>/main.tsx` and a React component file) and Fluent imports from `@fluentui/react-components`, the task is incomplete and MUST be corrected before returning results.
**NO RAW HTML-ONLY WIDGETS (DEFAULT):** Do not implement app content directly with static HTML templates and inline JS as the final widget solution. A minimal shell HTML file is allowed only as a loader for built React assets. Raw/self-contained HTML-only widgets are allowed only when the user explicitly requests a non-React prototype.
**BACKGROUND PROCESSES:** MCP server and devtunnel MUST be spawned as independent OS processes — NOT run inside the agent's shell session. `isBackground: true`, `mode: "async"`, and `Start-Job` all run inside the agent's shell session and will be killed between messages. The only reliable approach is to spawn a detached OS process.
**Windows — use `Start-Process -WindowStyle Hidden`:**
```powershell
# Start devtunnel
$t = Start-Process -FilePath "devtunnel" `
-ArgumentList "host","<tunnel-name>","-a" `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput "tunnel.log" -RedirectStandardError "tunnel-err.log"
# Start MCP server — use cmd.exe /c to set the working directory and inherit PATH
$s = Start-Process -FilePath "cmd.exe" `
-ArgumentList "/c","cd /d <abs-path-to-mcp-server> && <start-command>" `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput "server.log" -RedirectStandardError "server-err.log"
# Save PIDs so they can be stopped later
"$($t.Id),$($s.Id)" | Out-File pids.txt
Write-Host "Started tunnel PID $($t.Id), server PID $($s.Id)"
```
To stop: `Stop-Process -Id (Get-Content pids.txt).Split(',')` or `Stop-Process -Id <pid>`.
**Linux/Mac — use `nohup` with `&`:**
```bash
nohup devtunnel host <tunnel-name> > tunnel.log 2>tunnel-err.log &
echo "tunnel:$!" >> pids.txt
nohup <start-command> > server.log 2>server-err.log &
echo "server:$!" >> pids.txt
```
To stop: `kill $(grep -oP '\d+' pids.txt)`.
After starting, tail the logs to confirm both processes are up before proceeding:
```powershell
# Windows
Start-Sleep 3; Get-Content tunnel.log, server.log
```
```bash
# Linux/Mac
sleep 3 && tail tunnel.log server.log
```
**FULL AUTOMATION:** Never tell the user to run commands manually. Install tools, authenticate, start services — do everything automatically. Only ask the user for interactive input that truly requires them (like device code confirmation during `devtunnel user login -g -d`). If a tool isn't installed, install it. If a service needs starting, start it. The user expects full automation.
**PATH SELECTION (REQUIRED — STOP BEFORE ANY CODE):** You MUST use `AskUserQuestion` to ask the user whether they want OAI Apps or MCP Apps Extension before writing any code, running any commands, or making any architectural decisions.
**There is no exception to this rule.** The most common failure mode is reasoning "the user's request makes it obvious, so asking is redundant." This reasoning is always wrong — invoke `AskUserQuestion` regardless. A user saying "build an MCP server with widgets" is NOT an answer to this question. A user invoking this skill by name is NOT an answer. Only an explicit answer to the question counts. See [PATH SELECTION](#-path-selection) above for the exact question to ask.
**AGENT PROVISIONING:** Re-provisioning is only required when the **agent manifest** changes (e.g., mcpPlugin.json tool definitions, MCP server URL, declarativeAgent.json, instruction.txt). MCP server code changes (tool implementations, React widget code, server logic) do **NOT** require re-provisioning the agent — running or deploying the server picks up changes automatically.
When provisioning is needed:
1. **Bump the version** in `manifest.json` (increment the patch version, e.g., `1.0.0` → `1.0.1`)
2. **Deploy the agent:**
```bash
npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local
```
**WIDGET TESTING LINKS:** Every time you return to the user with a result while the MCP server is running, you MUST include links to ALL widgets so they can test them locally. Format:
```
🧪 Test widgets locally:
- http://localhost:3001/widgets/widget-name.html
- http://localhost:3001/widgets/another-widget.html
```
List every `.html` file in the `mcp-server/widgets/` directory (or equivalent widget folder). This helps users verify widget rendering before testing in Copilot.
**AUTO-DEPLOY ON COMPLETION (REQUIRED — DO NOT SKIP):** When coding is complete, proceed automatically without waiting for the user:
1. Start MCP server + devtunnel in the background (per BACKGROUND PROCESSES above)
2. Run E2E verification with MCP Inspector (per MCP TOOL CONFIGURATION RULE below) — fix any failures before continuing
3. Provision the agent if needed (per AGENT PROVISIONING above)
4. Print a project summary in this format:
```
## ✅ <Project Name> — Ready
### Widgets
- [widget-name.html](http://localhost:<PORT>/widgets/widget-name.html)
- [widget-name2.html](http://localhost:<PORT>/widgets/widget-name2.html)
### Endpoints
- MCP server: http://localhost:<PORT>/mcp
- MCP via tunnel: https://<tunnel-url>/mcp
### Test in Copilot
Local: https://m365.cloud.microsoft/chat/?titleId={M365_TITLE_ID from env/.env.local}
Other envs: {SHARE_LINK from env/.env.{environment}}
```
**AGENT PROJECT DELEGATION:** This skill builds MCP servers and widgets, NOT declarative agent projects. If the user's request involves creating or configuring the declarative agent itself (scaffolding, `m365agents.yml`, `m365agents.local.yml`, `declarativeAgent.json`, manifest lifecycle), delegate to the `declarative-agent-developer` skill.
**MCP RESOURCE REGISTRATION:** Every widget MUST have a matching MCP resource. Without resources, Copilot cannot fetch widget shells through the MCP protocol and widgets will not render.
For each new widget, complete this checklist:
1. ☐ Create a widget shell HTML file in `widgets/` and a React widget entry under `widgets/src/<widget-name>/` (see widget-patterns.md)
2. ☐ Define a `ui://widget/<name>.html` URI constant
3. ☐ Add a `Resource` entry to the `resources` array with:
- `uri`: the `ui://widget/<name>.html` URI
- `mimeType`: `"text/html+skybridge"`
- `_meta`: CSP config with `openai/widgetDomain` and `openai/widgetCSP` (from environment)
4. ☐ Add a handler for `resources/read` that returns the widget shell HTML for this URI
5. ☐ Add the tool with `_meta.openai/outputTemplate` pointing to the same `ui://widget/<name>.html` URI
6. ☐ Verify the server capabilities include `resources: {}` in the initialize response
**Widget shell + asset considerations:**
- **Preferred (React + Fluent UI)**: Resource HTML should be a minimal shell that links to built JS/CSS assets served from the MCP server's `/assets/` route.
- **Exception only**: Self-contained HTML via `resources/read` is for explicit user-requested prototypes only. Default and production path is React + Fluent UI.
Example shell for React build output:
```html
<!doctype html><html><head>
<script type="module" src="${serverUrl}/assets/my-widget.js"></script>
<link rel="stylesheet" href="${serverUrl}/assets/my-widget.css">
</head><body>
<div id="widget-root"></div>
</body></html>
```
Use the `WIDGET_BASE_URL` or `MCP_SERVER_URL` environment variable for the asset URL base (see mcp-server-pattern.md "Configurable Widget Base URL" section).
See [mcp-server-pattern.md](references/mcp-server-pattern.md) for the complete resource and asset serving patterns.
---
## ⚠️ MCP TOOL CONFIGURATION RULE ⚠️
**NEVER manually write tool definitions in `mcpPlugin.json`.** Always use MCP Inspector to get the complete tool definitions from the running MCP server.
**TOOL NAMING CONVENTION:** Tool names MUST match the pattern `^[A-Za-z0-9_]+$` (letters, numbers, and underscores only). **NEVER use hyphens (-) in tool names.** Use underscores instead (e.g., `render_profile` not `render-profile`).
**MANDATORY WORKFLOW:**
1. **Start the MCP server** (in background)
2. **Use MCP Inspector** to get the latest tool definitions:
```bash
npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --transport http --method tools/list
```
3. **Copy the COMPLETE tool definition** from the inspector (including `name`, `description`, `inputSchema`, `_meta`, `annotations`, `title`)
4. **Paste into `mcpPlugin.json`** under `runtimes[].spec.mcp_tool_description.tools` (inside the `RemoteMCPServer` runtime's `spec` object)
5. **Run E2E verification** through the devtunnel — call each tool and confirm the response contains `structuredContent` and `_meta.openai/widgetAccessible: true`:
```bash
npx @modelcontextprotocol/[email protected] --cli https://<tunnel-url>/mcp --transport http --method tools/call --tool-name <tool_name>
```
Also verify `GET https://<tunnel-url>/health` returns `{"status":"ok"}`. Fix any failures before provisioning.
The MCP Inspector shows the exact tool schema from your server. Copy it completely — do not manually write or modify these definitions. This ensures `mcpPlugin.json` stays in sync with the MCP server.
---
Build MCP servers that integrate with Microsoft 365 Copilot Chat and render rich interactive widgets.
## Architecture
```
M365 Copilot ──▶ mcpPlugin.json ──▶ MCP Server ──▶ structuredContent ──▶ React + Fluent UI Widget
│ (RemoteMCPServer) (Streamable HTTP) (window.openai.toolOutput)
│
└── Capabilities (People, etc.) provide data to pass to MCP tools
```
## Project Structure
Example project structure, not a hard requirement but a common pattern for organizing MCP server + widget development:
```
project/
├── appPackage/
│ ├── manifest.json # Teams manifest (bump version on deploy)
│ ├── declarativeAgent.json # Agent config + capabilities
│ ├── mcpPlugin.json # Tool definitions with _meta
│ └── instruction.txt # Agent behavior instructions
├── mcp-server/
│ ├── src/index.ts # Server with Streamable HTTP
│ ├── widgets/ # Widget shells + React source
│ │ ├── my-widget.html # Minimal shell returned by resources/read
│ │ └── src/my-widget/ # React + Fluent UI source
│ ├── assets/ # Built widget bundles served at /assets
│ └── package.json
├── scripts/
│ ├── setup-devtunnel.sh # Linux/Mac devtunnel setup
│ └── setup-devtunnel.ps1 # Windows devtunnel setup
└── env/.env.local # MCP_SERVER_URL, MCP_SERVER_DOMAIN
```
**Language note**: This shows a TypeScript project layout. For Python, replace `mcp-server/src/index.ts` with your Python entry point (e.g., `server.py`). For C#, use a standard .NET project structure. The `appPackage/`, `widgets/`, `scripts/`, and `env/` directories are language-agnostic.
## Copilot Widget Protocol
Your MCP server must implement these protocol requirements to render widgets in Copilot Chat. This applies regardless of language:
1. **Streamable HTTP transport** — `/mcp` endpoint handling POST, GET, DELETE with session management
2. **CORS headers** — Origin-checking on `/mcp` allowing `m365.cloud.microsoft` and `*.m365.cloud.microsoft`, with required MCP headers
3. **Server capabilities** — `initialize` response must declare `resources: {}` and `tools: {}`
4. **MCP resources** — Register widgets with `ui://widget/<name>.html` URIs, `text/html+skybridge` mime type, and CSP `_meta`
5. **Tool response format** — Return `content` (text) + `structuredContent` (widget data) + `_meta` with `openai/outputTemplate`
6. **Widget serving** — HTTP route at `/widgets/*.html` for shell files and `/assets/*` for built bundles, both with origin-checking CORS
For full protocol details, JSON shapes, and an adaptation checklist for existing MCP servers, see [references/copilot-widget-protocol.md](references/copilot-widget-protocol.md).
## Implementation
### MCP Server Pattern (TypeScript Reference)
See [references/mcp-server-pattern.md](references/mcp-server-pattern.md) for complete implementation.
> For other languages, implement the requirements described in [Copilot Widget Protocol](references/copilot-widget-protocol.md) using your language's MCP SDK. See the [Language SDK References](references/copilot-widget-protocol.md#language-sdk-references) table for SDK packages.
Core requirements:
- Expose Streamable HTTP transport on `/mcp`
- Return `structuredContent` + `_meta` with `openai/outputTemplate`
- Serve widgets via HTTP endpoint
- Handle CORS for cross-origin requests
- Handle partial data gracefully (fill in "Unknown" for missing fields)
Tool response format:
```typescript
return {
content: [{ type: "text", text: "Summary" }],
structuredContent: { /* widget data */ },
_meta: { "openai/outputTemplate": "ui://widget/name.html", "openai/widgetAccessible": true }
};
```
### Handling Partial Data
Always normalize input data to handle missing fields:
```typescript
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
const args = request.params.arguments as { title?: string; items?: Partial<Item>[] };
// Normalize data - fill in "Unknown" for missing fields
const title = args.title || "Default Title";
const items = (args.items || []).map(item => ({
name: item.name || "Unknown",
value: item.value || "Unknown",
}));
// Build structuredContent for widget
const structuredContent = { title, items };
// ...
});
```
### Widget Pattern
See [references/widget-patterns.md](references/widget-patterns.md) for complete examples.
Core requirements:
- Use React + Fluent UI components (`@fluentui/react-components`)
- Ensure widget package dependencies include `@fluentui/react-components`, `react`, and `react-dom`
- Theme with `FluentProvider` (`webLightTheme`/`webDarkTheme`) and Fluent `tokens`
- Access data through shared hooks (e.g., `useOpenAiGlobal("toolOutput")`)
- Debug fallback: embedded mock data when `window.openai` unavailable
- Handle "Unknown" values gracefully (e.g., hide action buttons)
### Plugin Schema
See [references/plugin-schema.md](references/plugin-schema.md) for mcpPlugin.json format.
Core requirements:
- Schema `v2.4` with `RemoteMCPServer` runtime
- `run_for_functions` array matching tool names
- `_meta` in tool definitions for widget binding
- `inputSchema` - make properties optional for flexibility, describe defaults in descriptions
## DevTunnels Setup
> **Local testing only.** DevTunnels are for development and testing on your machine. Before sharing the agent more broadly, deploy both the MCP server and widget assets to a hosted environment (e.g., Azure App Service, Azure Static Web Apps, or another hosting provider) and update the agent manifest URLs accordingly.
DevTunnels expose your localhost MCP server to M365 Copilot using **named tunnels** for stable URLs. See [references/devtunnels.md](references/devtunnels.md) for setup scripts, command reference, and troubleshooting.
The setup script (`npm run tunnel` / `npm run tunnel:win`):
1. Creates a named tunnel on first run (or reuses the existing one)
2. Starts hosting the tunnel on the configured port
3. Updates `env/.env.local` with `MCP_SERVER_URL` and `MCP_SERVER_DOMAIN` (first run only)
4. Continues hosting the tunnel
### Quick Start
**Terminal 1 - Start MCP Server:**
```bash
cd mcp-server
npm install
npm run dev
```
**Terminal 2 - Start DevTunnel:**
```bash
npm run tunnel
# Or on Windows:
npm run tunnel:win
```
On first run, provision the agent once the tunnel is up (see AGENT PROVISIONING rule). On subsequent runs the tunnel URL is stable — no re-provisioning needed unless the agent manifest changes.
## Development Workflow
1. **Start the MCP server** (dev mode with hot reload):
- TypeScript: `cd mcp-server && npm install && npm run dev`
- Python: `cd mcp-server && pip install -r requirements.txt && python server.py`
- C#: `cd mcp-server && dotnet run`
2. **Start the devtunnel** (creates named tunnel on first run, reuses on subsequent runs):
```bash
npm run tunnel
```
3. **Provision + test** — see AGENT PROVISIONING rule for when this is needed; bump `version` in manifest.json if Copilot doesn't reflect changes
## Best Practices
See [references/best-practices.md](references/best-practices.md) for detailed guidance.
Key points:
1. **Rendering tools**: Accept data as input, don't fetch internally
2. **Instructions**: Tell agent to use capabilities FIRST, then pass data to MCP tools
3. **Themes**: Use `FluentProvider` + Fluent `tokens` for dark/light support
4. **Debug mode**: Include fallback data for local widget testing
5. **Partial data**: Handle missing fields with "Unknown" defaults
6. **Action buttons**: Hide email/chat buttons when data is "Unknown"
7. **Version bumping**: Bump manifest version when changes aren't reflected in Copilot
すべてのファイル
0件のファイルui-widget-developerをインストール
スキルファイルをダウンロードし、.claude/skills/ ディレクトリに解凍してください。
ZIPをダウンロードリポジトリをクローンし、スキルファイルをプロジェクトにコピーしてください。
git clone https://github.com/microsoft/skills/tree/main/.github/plugins/microsoft-365-agents-toolkit/skills/ui-widget-developer # Copy SKILL.md to your .claude/skills/ directory
コピー





家
