選項
首頁首頁 Skill 網頁開發 ui-widget-developer

ui-widget-developer

microsoft/skills microsoft/skills

使用 React 和 Fluent UI 為 Copilot Chat 建立具備互動式小工具的 MCP 伺服器,同時支援 OpenAI Apps SDK 和 MCP Apps Extension 兩項標準。

...展開全部
0
更新時間 2026-09-18

Copilot MCP 伺服器開發

遙測標記

在執行任何atkCLI 指令之前,請設定工作階段環境變數,以便所有 CLI 呼叫皆被標記為由技能發起:

export ATK_CLI_SKILL=true

請在工作階段開始時執行此指令一次。之後在同一終端機中執行的所有atk指令都會繼承此設定。

🔀 路徑選擇

繼續之前,請使用 AskUserQuestion 詢問使用者希望選擇哪條路徑。M365 Copilot 小工具的渲染支援這兩種路徑請說明兩者的優缺點,並讓使用者自行選擇:

OAI 應用程式(此技能) MCP 應用程式擴充功能(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 應用程式,請自動執行此步驟(不要僅停留在說明階段):

  1. 執行/plugin marketplace add modelcontextprotocol/ext-apps
  2. 執行/plugin install mcp-apps@mcp-apps
  3. 確認外掛程式已就緒,接著根據使用者意圖呼叫正確的 ext-apps 技能

若當前環境中無法使用外掛程式指令,請提供以下確切指令,並請使用者執行一次,隨後繼續呼叫所選的 ext-apps 技能。

參考指令:

要建立 MCP 應用程式,請從市集安裝 ext-apps 外掛程式:

1. /plugin marketplace add modelcontextprotocol/ext-apps
2. /plugin install mcp-apps@mcp-apps

接著使用該外掛程式中的其中一項技能:
- create-mcp-app      — 從頭開始建立具備互動式使用者介面的新 MCP 應用程式
- add-app-to-server   — 將互動式使用者介面新增至現有 MCP 伺服器的工具中
- migrate-oai-app     — 將現有的 OAI 應用程式轉換為使用 MCP 應用程式
- convert-web-app     — 將網頁應用程式轉為混合型網頁 + MCP 應用程式

安裝完成後,請呼叫相關技能以繼續操作。

注意:ext-apps 外掛程式位於外部modelcontextprotocol/ext-apps市集 — 它不屬於此外掛程式集合的一部分。

安裝後的交接映射:

  • 從頭建立新的 MCP 應用程式 →create-mcp-app
  • 將應用程式介面新增至現有 MCP 伺服器 →add-app-to-server
  • 遷移現有 OAI 應用程式 →migrate-oai-app
  • 轉換現有網頁應用程式 →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-appadd-app-to-server—— 請參閱上方的「路徑選擇」
從頭開始(無代理程式、無 MCP 伺服器) 完整的 OAI 應用程式設定 首先將代理程式骨架建置工作委派給declarative-agent-developer,隨後返回此處進行 MCP 伺服器與小工具的設定
現有 M365 代理程式,新 MCP 伺服器 MCP 伺服器 + 小工具 + mcpPlugin.json 從實作開始
現有 MCP 伺服器,新增 Copilot 小工具 在現有伺服器中新增小工具支援 從 Copilot 小工具協定開始
語言選擇(非 TypeScript) 協定要求 請參閱 Copilot 小工具協定以了解需實作的內容,並參考 MCP 伺服器模式(TypeScript)

🚨 關鍵執行規則 🚨

Fluent UI 強制規範(必備):Widget 實作必須使用 React + Fluent UI 元件。在撰寫任何 Widget 程式碼之前,開發者必須閱讀並遵循:

  • references/widget-patterns.md
  • references/best-practices.md FLUENT UI 套件要求(必備):小工具專案在實作前必須包含 Fluent UI 依賴項。至少須在小工具套件的依賴項中安裝並保留以下項目:
  • @fluentui/react-components
  • react
  • react-dom

若缺少上述任何套件,請在繼續進行小工具程式碼生成之前自動安裝它們。

若生成的 Widget 未包含 React 入口檔案(例如widgets/src//main.tsx以及一個 React 元件檔案),且未從@fluentui/react-components 導入 Fluent 元件,則該任務屬未完成狀態,必須在回傳結果前予以修正。

禁止純 HTML 小部件(預設):請勿直接使用靜態 HTML 範本和內嵌 JS 來實現應用程式內容,作為最終的小部件解決方案。 僅允許使用最簡化的 HTML 外殼檔案作為已建置 React 資產的載入器。僅包含原始/自包含 HTML 的元件,僅在使用者明確要求非 React 原型時才允許使用。

背景進程:MCP 伺服器和 devtunnel 必須作為獨立的作業系統進程啟動 — 不得在代理程式的 shell 會話中執行。isBackground: truemode: "async" 以及Start-Job皆在代理程式的 shell 會話中執行,並將在訊息傳遞之間被終止。 唯一可靠的方法是啟動一個分離的作業系統進程。

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 小工具程式碼、伺服器邏輯)無需重新配置代理程式——執行或部署伺服器時會自動套用變更。

何時需要進行配置:

  1. manifest.json中更新版本號(遞增修補版本,例如1.0.01.0.1
  2. 部署代理程式:
    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 中進行測試前,先驗證小工具的渲染效果。

完成後自動部署(必填 — 請勿跳過):程式碼編寫完成後,請自動執行後續步驟,無需等待使用者:

  1. 在背景中啟動 MCP 伺服器 + devtunnel(參照上文「背景程序」)
  2. 使用 MCP Inspector 執行端到端驗證(參照下文「MCP 工具配置規則」)—— 在繼續之前修復所有失敗項目
  3. 如有需要,請配置代理程式(參照上文「代理程式配置」)
  4. 以以下格式列印專案摘要:
## ✅ — 已就緒

### 小工具
- [widget-name.html](http://localhost:/widgets/widget-name.html)
- [小工具名稱2.html](http://localhost:/widgets/widget-name2.html)

### 端點
- MCP 伺服器:http://localhost:/mcp
- 透過隧道連線的 MCP:https:///mcp

### 在 Copilot 中測試
本地端:      https://m365.cloud.microsoft/chat/?titleId={M365_TITLE_ID 來自 env/.env.local}
其他環境:{SHARE_LINK 來自 env/.env.{environment}}

代理程式專案授權:此技能用於建置 MCP 伺服器與小工具,而非宣告式代理程式專案。 若使用者的請求涉及建立或配置宣告式代理程式本身(骨架、m365agents.ymlm365agents.local.ymldeclarativeAgent.json、清單生命週期),請轉交給declarative-agent-developer技能。

MCP 資源註冊:每個小工具都必須對應一個 MCP 資源。若無資源,Copilot 將無法透過 MCP 協定取得小工具外殼,導致小工具無法渲染。

針對每個新小工具,請完成以下檢查清單:

  1. ☐ 在widgets/ 目錄下建立小工具外殼 HTML 檔案,並在widgets/src/目錄下建立 React 小工具入口檔案/(參見 widget-patterns.md)
  2. ☐ 定義一個ui://widget/.htmlURI 常數
  3. ☐ 在resources陣列中新增一個Resource條目,內容如下:
    • uriui://widget/.htmlURI
    • mimeType:"text/html+skybridge"
    • _meta:包含openai/widgetDomainopenai/widgetCSP(來自環境變數)的 CSP 設定
  4. ☐ 為resources/read新增一個處理常式,該處理常式會針對此 URI 返回小工具外殼的 HTML
  5. ☐ 新增工具,並將_meta.openai/outputTemplate指向相同的ui://widget/.htmlURI
  6. ☐ 驗證伺服器功能是否在初始化回應中包含resources: {}

小工具外殼與資產考量:

  • 首選方案(React + Fluent UI):資源 HTML 應為一個簡約的外殼,並連結至由 MCP 伺服器/assets/路徑所提供的預先建置 JS/CSS 資產。
  • 僅限例外情況:透過resources/read提供的自包含 HTML 僅適用於使用者明確請求的原型。預設及生產環境的路徑應採用 React + Fluent UI。

React 建置輸出的外殼範例:


  
  

  

請使用WIDGET_BASE_URLMCP_SERVER_URL環境變數作為資產 URL 的基底(參閱 mcp-server-pattern.md 中的「可配置的小組件基底 URL」章節)。

完整的資源與資產提供模式請參閱 mcp-server-pattern.md。

⚠️ MCP 工具配置規則 ⚠️

切勿在mcpPlugin.json 中手動撰寫工具定義。請務必使用 MCP Inspector 從正在運行的 MCP 伺服器取得完整的工具定義。

工具命名規範:工具名稱必須符合^[A-Za-z0-9_]+$模式(僅限字母、數字和底線)。切勿在工具名稱中使用連字號 (-),請改用底線(例如:render_profile而非render-profile)。

必備工作流程:

  1. 啟動 MCP 伺服器(在背景執行)
  2. 使用 MCP Inspector取得最新的工具定義:
    npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --transport http --method tools/list
    
    
  3. 從 Inspector複製完整的工具定義(包含名稱描述inputSchema_meta註解標題
  4. 貼入mcpPlugin.json檔案中的 runtimes[].spec.mcp_tool_description.tools區段(位於RemoteMCPServer執行環境的spec物件內)
  5. 透過 devtunnel執行端到端驗證— 呼叫每個工具,並確認回應中包含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)    (可串流的 HTTP)                  (window.openai.toolOutput)
     │
     └── 功能模組(例如「人員」等)提供資料以傳遞給 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 開發隧道設定
│   └── 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 小工具協定

您的 MCP 伺服器必須實作這些協定要求,才能在 Copilot Chat 中渲染小工具。此要求適用於所有程式語言:

  1. 可串流的 HTTP 傳輸/mcp端點需處理 POST、GET、DELETE 請求並具備會話管理功能
  2. CORS 標頭— 在/mcp進行來源驗證,允許m365.cloud.microsoft*.m365.cloud.microsoft,並需包含必要的 MCP 標頭
  3. 伺服器功能初始化回應必須宣告resources: {}tools: {}
  4. MCP 資源— 需透過ui://widget/.htmlURI、text/html+skybridgeMIME 類型以及 CSP_meta來註冊小工具
  5. 工具回應格式— 回傳內容(text) +structuredContent(小工具資料) + 帶有openai/outputTemplate_meta
  6. 小工具提供— 針對 shell 檔案使用/widgets/*.html的 HTTP 路徑,針對預先建置的套件使用/assets/*,兩者均需進行來源驗證的 CORS

有關完整協定細節、JSON 結構及現有 MCP 伺服器的適配檢查清單,請參閱 references/copilot-widget-protocol.md。

實作

MCP 伺服器模式(TypeScript 參考指南)

完整實作請參閱 references/mcp-server-pattern.md。

若使用其他程式語言,請透過您所用語言的 MCP SDK 來實作《Copilot Widget 協定》中所述的要求。有關 SDK 套件,請參閱「語言 SDK 參考」表格。

核心要求:

  • /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[] };

  // 將資料標準化 — 對缺失欄位填入「未知」
  const title = args.title || "預設標題";
  const items = (args.items || []).map(item => ({
    name: item.name || "未知",
    value: item.value || "未知",
  }));

  // 建立小工具的結構化內容
  const structuredContent = { title, items };
  // ...
});

小工具模式

完整範例請參閱 references/widget-patterns.md。

核心需求:

  • 使用 React + Fluent UI 元件 (@fluentui/react-components)
  • 確保 widget 套件的依賴項包含@fluentui/react-componentsreactreact-dom
  • 使用FluentProviderwebLightTheme/webDarkTheme)及 Fluent代碼進行主題設定
  • 透過共用鉤子存取資料(例如:useOpenAiGlobal("toolOutput"))
  • 備用方案:當window.openai不可用時,嵌入模擬資料
  • 妥善處理「未知」值(例如:隱藏操作按鈕)

外掛程式架構

請參閱 references/plugin-schema.md 以了解 mcpPlugin.json 的格式。

核心需求:

  • 搭配RemoteMCPServer執行環境的 Schemav2.4
  • `run_for_functions` 陣列須與工具名稱相符
  • 工具定義中的_meta用於小工具綁定
  • inputSchema— 為提高靈活性,將屬性設為可選,並在描述中說明預設值

DevTunnels 設定

僅限本地測試。DevTunnels適用於您的電腦上的開發與測試在更廣泛地分享代理程式之前,請將 MCP 伺服器和小工具資產部署至託管環境(例如 Azure App Service、Azure Static Web Apps 或其他託管服務提供者),並據此更新代理程式清單的 URL。

DevTunnels透過命名隧道將您的 localhost MCP 伺服器對外公開給 M365 Copilot,以確保 URL 穩定。請參閱 references/devtunnels.md 以取得設定腳本、命令參考及疑難排解資訊。

設定腳本(npm run tunnel/npm run tunnel:win):

  1. 首次執行時建立命名隧道(或重複使用現有隧道)
  2. 在設定的埠號上啟動隧道託管服務
  3. MCP_SERVER_URLMCP_SERVER_DOMAIN寫入env/.env.local(僅限首次執行時)
  4. 繼續託管隧道

快速入門

終端機 1 - 啟動 MCP 伺服器:

cd mcp-server
npm install
npm run dev

終端機 2 — 啟動 DevTunnel:

npm run tunnel
# 或在 Windows 上:
npm run tunnel:win

首次執行時,請在隧道建立後進行一次代理程式配置(參閱「代理程式配置」規則)。後續執行時,隧道 URL 將保持穩定——除非代理程式清單有所變更,否則無需重新配置。

開發工作流程

  1. 啟動 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
  2. 啟動 devtunnel(首次執行時會建立命名隧道,後續執行時會重複使用):

    npm run tunnel
    
  3. 配置 + 測試— 請參閱「AGENT PROVISIONING」規則以了解何時需要執行此步驟;若 Copilot 未反映變更,請在 manifest.json 中更新版本號

最佳實務

詳見 references/best-practices.md 以獲取詳細指引。

重點:

  1. 渲染工具:接受資料作為輸入,勿在內部擷取資料
  2. 操作指示:首先指示代理程式使用功能,然後將資料傳遞給 MCP 工具
  3. 主題:使用FluentProvider+ Fluent標記來支援深色/淺色模式
  4. 除錯模式:為本地小工具測試包含備用資料
  5. 部分資料:對缺失欄位使用「未知」預設值進行處理
  6. 動作按鈕:當資料為「未知」時,隱藏電子郵件/聊天按鈕
  7. 版本更新:當變更未反映在 Copilot 中時,請將 manifest 版本號提升
在 GitHub 上查看
---
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

複製 複製
快速設定: 將技能資料夾複製到 .claude/skills/ Claude 會自動偵測並使用該技能
儲存庫 microsoft/skills

相關技能

github-code-search
更新時間 2026-06-29
drizzle-orm
更新時間 2026-06-29
clickhouse-io
更新時間 2026-06-29
prisma-client-api
更新時間 2026-06-29
OR