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 应用(本技能) | 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 应用,请自动执行此操作(不要仅停留在解释阶段):
- 运行
/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 — 从零开始搭建一个带有交互式界面的新 MCP 应用
- add-app-to-server — 向现有 MCP 服务器的工具中添加交互式界面
- migrate-oai-app — 将现有 OAI 应用转换为使用 MCP 应用
- convert-web-app — 将 Web 应用转换为 Web 与 MCP 应用的混合应用
安装完成后,调用相应的技能继续操作。
注意:ext-apps 插件位于 external
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/以及一个 React 组件文件)和来自@fluentui/react-components 的 Fluent 导入,则任务未完成,必须在返回结果前予以更正。
不允许纯 HTML 小部件(默认):请勿直接使用静态 HTML 模板和内联 JS 来实现应用程序内容,作为最终的小部件解决方案。 仅允许使用最简化的 HTML 壳文件作为已构建 React 资源的加载器。仅当用户明确请求非 React 原型时,才允许使用纯 HTML 且自包含的小部件。
后台进程:MCP 服务器和 devtunnel 必须作为独立的操作系统进程启动——不得在代理的 shell 会话中运行。isBackground: true、mode: "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 用户登录时进行设备代码确认-g -d)。 如果某个工具未安装,则安装它;如果某个服务需要启动,则启动它。用户期望的是完全自动化。
路径选择(必选 — 编写任何代码前必须执行):在编写任何代码、运行任何命令或做出任何架构决策之前,您必须使用AskUserQuestion询问用户是否需要 OAI 应用或 MCP 应用扩展。
此规则没有例外。最常见的错误思维是认为“根据用户请求,选择已显而易见,因此询问是多余的”。这种想法总是错误的——无论如何都应调用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 运行端到端验证(参见下文“MCP 工具配置规则”)——在继续之前修复所有失败项
- 如有需要,配置代理(参见上文“代理配置”)
- 按以下格式打印项目摘要:
## ✅ — 已就绪
### 小部件
- [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={来自 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:uri://widget/URI.html mimeType:"text/html+skybridge"_meta:包含openai/widgetDomain和openai/widgetCSP(来自环境变量)的 CSP 配置
- ☐ 为
resources/read添加一个处理程序,该处理程序返回此 URI 对应的控件外壳 HTML - ☐ 添加该工具,并将
_meta.openai/outputTemplate指向相同的ui://widget/URI.html - ☐ 验证服务器能力是否在初始化响应中包含
resources: {}
小部件外壳 + 资源相关注意事项:
- 推荐方案(React + Fluent UI):资源 HTML 应为一个简约的壳,其中包含指向 MCP 服务器
/assets/路径下已构建 JS/CSS 资源的链接。 - 仅限例外情况:通过
resources/read获取的自包含 HTML 仅适用于用户明确请求的原型。默认和生产环境路径应为 React + Fluent UI。
React 构建输出的示例外壳:
使用WIDGET_BASE_URL或MCP_SERVER_URL环境变量作为资源 URL 的基址(参见 mcp-server-pattern.md 中的“可配置小部件基址”部分)。
完整的资源和资产提供模式请参阅 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 - 从 Inspector中复制完整的工具定义(包括
名称、描述、inputSchema、_meta、注释、标题) - 将其粘贴到
mcpPlugin.json文件中runtimes[].spec.mcp_tool_description.tools部分(位于RemoteMCPServer运行时spec对象内) - 通过 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 开发隧道配置
└── 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 中渲染小部件。无论使用何种语言,此要求均适用:
- 可流式传输的 HTTP 传输——
/mcp端点处理 POST、GET、DELETE 请求,并支持会话管理 - CORS 标头— 对
/mcp进行源检查,允许m365.cloud.microsoft和*.m365.cloud.microsoft,并需包含必要的 MCP 标头 - 服务器功能—
“initializeresponse” 必须声明resources: {}和tools: {} - MCP 资源— 使用
ui://widget/URI、.html text/html+skybridgeMIME 类型以及 CSP_meta注册小部件 - 工具响应格式— 返回
内容(text) +结构化内容(小部件数据) + 带有openai/outputTemplate的_meta - 小部件提供服务— 通过
/widgets/*.htmlHTTP 路由提供 shell 文件,通过/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) - 确保小部件包的依赖项包含
@fluentui/react-components、react和react-dom - 使用
FluentProvider(webLightTheme/webDarkTheme)和 Fluent令牌进行主题设置 - 通过共享钩子访问数据(例如:
useOpenAiGlobal("toolOutput")) - 提供调试备用方案:当
window.openai不可用时,嵌入模拟数据 - 优雅地处理“未知”值(例如,隐藏操作按钮)
插件架构
有关 mcpPlugin.json 格式的详细信息,请参阅 references/plugin-schema.md。
核心要求:
- 采用
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):
- 首次运行时创建一个命名隧道(或复用现有隧道)
- 在配置的端口上启动隧道托管服务
- 将
MCP_SERVER_URL和MCP_SERVER_DOMAIN写入env/.env.local 文件(仅限首次运行) - 继续托管隧道
快速入门
终端 1 - 启动 MCP 服务器:
cd mcp-server
npm install
npm run dev
终端 2 - 启动 DevTunnel:
npm run tunnel
# 或在 Windows 上:
npm run tunnel:win
首次运行时,待隧道建立后请配置代理(参见“代理配置”规则)。后续运行时,隧道 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”时隐藏邮件/聊天按钮
- 版本号递增:当更改未在 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
复制





首页
