ui-widget-developer
microsoft/skills
Crea servidores MCP para Copilot Chat con widgets interactivos utilizando React y Fluent UI, compatibles tanto con el SDK de OpenAI Apps como con los estándares de MCP Apps Extension.
...Expandir todoDesarrollo del servidor Copilot MCP
Etiquetado de telemetría
Antes de ejecutar cualquier comando de la CLI de ATK, configura la variable de entorno de la sesión para que todas las invocaciones de la CLI se etiqueten como iniciadas por una habilidad:
export ATK_CLI_SKILL=true
Ejecuta esto una vez al inicio de la sesión. Todos los comandos atk posteriores en el mismo terminal lo heredarán.
🔀 SELECCIÓN DE RUTA
Antes de continuar, pregunta al usuario qué ruta desea seguir utilizando AskUserQuestion. Ambas opciones son compatibles con la visualización del widget de M365 Copilot. Presenta las ventajas e inconvenientes y deja que elija:
| Aplicaciones OAI (esta skill) | Extensión de aplicaciones MCP (modelcontextprotocol/ext-apps) |
|
|---|---|---|
| Estándar | Específico de OpenAI | Estándar oficial de MCP |
| Funciona en | ChatGPT + M365 Copilot | M365 Copilot, ChatGPT, VSCode y más |
| Madurez | Probado en la práctica, listo para producción | Nuevo estándar oficial, ecosistema en expansión |
| Diseño | SDK de OpenAI Apps | Protocolo MCP Apps (multiplataforma) |
| Cuándo elegirlo | Inversión existente en aplicaciones OAI | Si se prefiere el estándar abierto y se busca la mayor compatibilidad con clientes |
Pregunta: «¿Te gustaría crear una aplicación OAI (SDK de OpenAI Apps — probada en la práctica, funciona en ChatGPT y M365 Copilot) o una aplicación MCP (nuevo estándar oficial — funciona en M365 Copilot, ChatGPT, VSCode y más)?»
- Aplicaciones OAI → Continúa a continuación. Esta habilidad cubre todo lo que necesitas.
- Aplicaciones MCP → Instala el complemento
modelcontextprotocol/ext-apps(ver más abajo) y, a continuación, utiliza la habilidad adecuada de ese complemento.
Aplicaciones MCP: Instala el complemento ext-apps
Si el usuario elige las aplicaciones MCP, hazlo automáticamente (no te limites a dar una explicación):
- Ejecuta
/plugin marketplace add modelcontextprotocol/ext-apps - Ejecuta
/plugin install mcp-apps@mcp-apps - Confirma que el complemento está disponible y, a continuación, invoca la habilidad ext-apps correcta en función de la intención del usuario
Si los comandos del complemento no están disponibles en el entorno actual, facilite los comandos exactos que se indican a continuación y pida al usuario que los ejecute una vez; a continuación, continúe invocando la skill de ext-apps seleccionada.
Comandos de referencia:
Para crear una aplicación MCP, instala el complemento «ext-apps» desde el marketplace:
1. /plugin marketplace add modelcontextprotocol/ext-apps
2. /plugin install mcp-apps@mcp-apps
A continuación, utiliza una de estas habilidades de ese complemento:
- create-mcp-app — Genera desde cero una nueva aplicación MCP con interfaz de usuario interactiva
- add-app-to-server — Añade una interfaz de usuario interactiva a las herramientas de un servidor MCP existente
- migrate-oai-app — Convierte una aplicación OAI existente para que utilice aplicaciones MCP
- convert-web-app — Convierte una aplicación web en una aplicación híbrida web + MCP
Tras la instalación, ejecuta la función correspondiente para continuar.
Nota: El complemento «ext-apps» se encuentra en el marketplace «external
modelcontextprotocol/ext-apps»; no forma parte de esta colección de complementos.
Asignación de traspasos tras la instalación:
- Nueva aplicación MCP desde cero →
create-mcp-app - Añadir la interfaz de usuario de una aplicación a un servidor MCP existente →
add-app-to-server - Migrar una aplicación OAI existente →
migrate-oai-app - Convertir una aplicación web existente →
convert-web-app
📛 DETECCIÓN DE PROYECTOS 📛
Esta habilidad se activa al crear servidores MCP con una aplicación OAI o renderización de widgets para Microsoft 365 Copilot Chat. El servidor MCP puede estar escrito en cualquier lenguaje que admita el protocolo MCP (TypeScript, Python, C#, etc.). El proyecto del agente y el servidor MCP pueden encontrarse en el mismo repositorio, en carpetas separadas o en proyectos totalmente distintos.
Enrutamiento de escenarios
| Punto de partida | Lo que necesitas | Ruta |
|---|---|---|
| Se recomienda utilizar MCP Apps estándar | Compatibilidad con widgets multiplataforma (M365 Copilot, ChatGPT, VSCode y más) | Instala modelcontextprotocol/ext-apps y, a continuación, utiliza create-mcp-app o add-app-to-server; consulta la sección «Selección de ruta» más arriba |
| Desde cero (sin agente, sin servidor MCP) | Configuración completa de la aplicación OAI | Delega primero la creación del esqueleto del agente a declarative-agent-developer y, a continuación, vuelve aquí para el servidor MCP y los widgets |
| Agente M365 existente, nuevo servidor MCP | Servidor MCP + widgets + mcpPlugin.json | Empezar por la implementación |
| Servidor MCP existente: añadir widgets de Copilot | Se ha añadido compatibilidad con widgets al servidor existente | Empezar por el protocolo de widgets de Copilot |
| Elección del lenguaje (que no sea TypeScript) | Requisitos del protocolo | Consulta el Protocolo de widgets de Copilot para saber qué hay que implementar, y el patrón de servidor MCP (TypeScript) como referencia |
🚨 NORMAS DE EJECUCIÓN CRÍTICAS 🚨
APLICACIÓN DE FLUENT UI (OBLIGATORIO): Las implementaciones de widgets DEBEN utilizar React + componentes de Fluent UI. Antes de escribir cualquier código de widget, el agente DEBE leer y seguir:
references/widget-patterns.mdreferences/best-practices.mdREQUISITO DEL PAQUETE DE FLUENT UI (OBLIGATORIO): El proyecto del widget DEBE incluir las dependencias de Fluent UI antes de la implementación. Como mínimo, instala y mantén estos componentes en las dependencias del paquete del widget:@fluentui/react-componentsreactreact-dom
Si falta alguno de estos paquetes, instálalo automáticamente antes de continuar con la generación del código del widget.
Si el widget generado no incluye archivos de entrada de React (por ejemplo , widgets/src/ y un archivo de componente de React) ni las importaciones de Fluent desde @fluentui/react-components, la tarea está incompleta y DEBE corregirse antes de devolver resultados.
NO SE ADMITEN WIDGETS SOLO EN HTML SIN FORMATO (POR DEFAULT): No implementes el contenido de la aplicación directamente con plantillas HTML estáticas y JavaScript en línea como solución final del widget. Se permite un archivo HTML mínimo de estructura básica únicamente como cargador de los recursos de React compilados. Los widgets que contengan únicamente HTML sin procesar o autónomos solo se permiten cuando el usuario solicite explícitamente un prototipo que no sea de React.
PROCESOS EN SEGUNDO PLANO: El servidor MCP y devtunnel DEBEN ejecutarse como procesos independientes del sistema operativo —NO deben ejecutarse dentro de la sesión de shell del agente—. isBackground: true, mode: «async» y Start-Job se ejecutan todos dentro de la sesión de shell del agente y se cerrarán entre mensajes. El único método fiable es iniciar un proceso independiente del sistema operativo.
Windows: utiliza Start-Process -WindowStyle Hidden:
# Iniciar devtunnel
$t = Start-Process -FilePath "devtunnel" `
-ArgumentList "host","","-a" `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput "tunnel.log" -RedirectStandardError "tunnel-err.log"
# Iniciar el servidor MCP: utiliza cmd.exe /c para establecer el directorio de trabajo y heredar la variable PATH
$s = Start-Process -FilePath "cmd.exe" `
-ArgumentList "/c", "cd /d && " `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput "server.log" -RedirectStandardError "server-err.log"
# Guardar los PID para poder detenerlos más tarde
"$($t.Id),$($s.Id)" | Out-File pids.txt
Write-Host "PID del túnel iniciado: $($t.Id), PID del servidor: $($s.Id)"
Para detenerlos: Stop-Process -Id (Get-Content pids.txt).Split(',') o Stop-Process -Id .
Linux/Mac: utiliza nohup con &:
nohup devtunnel host > tunnel.log 2>tunnel-err.log &
echo "túnel:$!" >> pids.txt
nohup > server.log 2>server-err.log &
echo "servidor:$!" >> pids.txt
Para detenerlos: kill $(grep -oP '\d+' pids.txt).
Una vez iniciados, revisa los registros con «tail» para confirmar que ambos procesos están en marcha antes de continuar:
# Windows
Start-Sleep 3; Get-Content tunnel.log, server.log
# Linux/Mac
sleep 3 && tail tunnel.log server.log
AUTOMATIZACIÓN TOTAL: Nunca pidas al usuario que ejecute comandos manualmente. Instala herramientas, autentifica, inicia servicios… hazlo todo automáticamente. Solo solicita al usuario una intervención interactiva cuando sea realmente necesario (como la confirmación del código del dispositivo durante el inicio de sesión de devtunnel con -g -d). Si una herramienta no está instalada, instálala. Si hay que iniciar un servicio, inícialo. El usuario espera una automatización total.
SELECCIÓN DE LA RUTA (OBLIGATORIO — DETÉNGETE ANTES DE ESCRIBIR NINGÚN CÓDIGO): DEBES utilizar AskUserQuestion para preguntar al usuario si desea la extensión OAI Apps o MCP Apps antes de escribir cualquier código, ejecutar cualquier comando o tomar cualquier decisión arquitectónica.
No hay excepciones a esta regla. El error más común es razonar que «la solicitud del usuario lo deja claro, por lo que preguntar es redundante». Este razonamiento siempre es erróneo: invoca AskUserQuestion en cualquier caso. Que un usuario diga «crea un servidor MCP con widgets» NO es una respuesta a esta pregunta. Que un usuario invoque esta habilidad por su nombre NO es una respuesta. Solo cuenta una respuesta explícita a la pregunta. Consulta la sección SELECCIÓN DE RUTA más arriba para conocer la pregunta exacta que debes formular.
APROVISIONAMIENTO DEL AGENTE: Solo es necesario volver a aprovisionar el agente cuando cambia su manifiesto (por ejemplo, definiciones de herramientas en mcpPlugin.json, URL del servidor MCP, declarativeAgent.json, instruction.txt). Los cambios en el código del servidor MCP (implementaciones de herramientas, código de widgets de React, lógica del servidor) NO requieren volver a aprovisionar el agente: al ejecutar o implementar el servidor, este detecta los cambios automáticamente.
Cuándo es necesario el aprovisionamiento:
- Actualiza la versión en
manifest.json(incrementa la versión de parche, p. ej.,1.0.0→1.0.1) - Implementa el agente:
npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local
ENLACES PARA PROBAR LOS WIDGETS: Cada vez que envíes un resultado al usuario mientras el servidor MCP esté en funcionamiento, DEBES incluir enlaces a TODOS los widgets para que puedan probarlos localmente. Formato:
🧪 Probar widgets localmente:
- http://localhost:3001/widgets/widget-name.html
- http://localhost:3001/widgets/another-widget.html
Enumera todos los archivos .html del directorio mcp-server/widgets/ (o la carpeta de widgets equivalente). Esto ayuda a los usuarios a verificar la visualización de los widgets antes de probarlos en Copilot.
IMPLANTACIÓN AUTOMÁTICA AL FINALIZAR (OBLIGATORIO — NO OMITIR): Cuando se haya completado la codificación, continuar automáticamente sin esperar al usuario:
- Inicia el servidor MCP y devtunnel en segundo plano (según los «PROCESOS EN SEGUNDO PLANO» anteriores)
- Ejecuta la verificación de extremo a extremo con MCP Inspector (según la «REGLA DE CONFIGURACIÓN DE HERRAMIENTAS MCP» más abajo); corrige cualquier fallo antes de continuar
- Aprovisiona el agente si es necesario (según «APROVISIONAMIENTO DEL AGENTE» más arriba)
- Imprime un resumen del proyecto en este formato:
## ✅ — Listo
### Widgets
- [nombre-del-widget.html](http://localhost:/widgets/nombre-del-widget.html)
- [nombre-del-widget2.html](http://localhost:/widgets/nombre-del-widget2.html)
### Puntos de conexión
- Servidor MCP: http://localhost:/mcp
- MCP a través de túnel: https:///mcp
### Prueba en Copilot
Local: https://m365.cloud.microsoft/chat/?titleId={M365_TITLE_ID de env/.env.local}
Otros entornos: {SHARE_LINK de env/.env.{entorno}}
DELEGACIÓN DE PROYECTOS DE AGENTE: Esta habilidad crea servidores y widgets de MCP, NO proyectos de agente declarativos. Si la solicitud del usuario implica la creación o configuración del propio agente declarativo (estructura básica, m365agents.yml, m365agents.local.yml, declarativeAgent.json, ciclo de vida del manifiesto), delega en la habilidad «declarative-agent-developer ».
REGISTRO DE RECURSOS DE MCP: Cada widget DEBE tener un recurso de MCP correspondiente. Sin recursos, Copilot no puede recuperar las plantillas de los widgets a través del protocolo MCP y los widgets no se mostrarán.
Para cada nuevo widget, sigue esta lista de comprobación:
- ☐ Crea un archivo HTML de plantilla de widget en
widgets/y una entrada de widget React enwidgets/src/(consulta widget-patterns.md)/ - ☐ Define una constante URI
ui://widget/.html - ☐ Añade una entrada
de recursoal arrayde recursoscon:uri: la URIui://widget/.html mimeType:«text/html+skybridge»_meta: configuración de CSP conopenai/widgetDomainyopenai/widgetCSP(del entorno)
- ☐ Añade un controlador para
resources/readque devuelva el código HTML del shell del widget para esta URI - ☐ Añade la herramienta con
_meta.openai/outputTemplateapuntando a la misma URIui://widget/.html - ☐ Verificar que las capacidades del servidor incluyan
resources: {}en la respuesta de inicialización
Consideraciones sobre el shell del widget y los recursos:
- Opción preferida (React + Fluent UI): el HTML del recurso debe ser una estructura mínima que enlace con los activos JS/CSS compilados servidos desde la ruta
/assets/del servidor MCP. - Solo como excepción: el HTML autónomo a través de
resources/readse utiliza únicamente para prototipos solicitados explícitamente por el usuario. La ruta predeterminada y de producción es React + Fluent UI.
Ejemplo de estructura para la salida de compilación de React:
Utiliza la variable de entorno WIDGET_BASE_URL o MCP_SERVER_URL como base de la URL del recurso (consulta la sección «Configurable Widget Base URL» en mcp-server-pattern.md).
Consulta mcp-server-pattern.md para ver los patrones completos de servicio de recursos y activos.
⚠️ NORMA DE CONFIGURACIÓN DE HERRAMIENTAS DE MCP ⚠️
NUNCA escribas manualmente las definiciones de las herramientas en mcpPlugin.json. Utiliza siempre MCP Inspector para obtener las definiciones completas de las herramientas del servidor MCP en ejecución.
CONVENCIÓN DE DENOMINACIÓN DE HERRAMIENTAS: Los nombres de las herramientas DEBEN seguir el patrón ^[A-Za-z0-9_]+$ (solo letras, números y guiones bajos). NUNCA utilices guiones (-) en los nombres de las herramientas. Utiliza guiones bajos en su lugar (por ejemplo, render_profile en lugar de render-profile).
FLUJO DE TRABAJO OBLIGATORIO:
- Inicia el servidor MCP (en segundo plano)
- Utilice MCP Inspector para obtener las últimas definiciones de herramientas:
npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --transport http --method tools/list - Copia la definición COMPLETA de la herramienta desde el inspector (incluyendo
nombre,descripción,inputSchema,_meta,anotacionesytítulo) - Pégala en
mcpPlugin.json, enruntimes[].spec.mcp_tool_description.tools(dentro del objetospecdel entorno de ejecuciónRemoteMCPServer) - Ejecuta la verificación E2E a través del devtunnel: llama a cada herramienta y confirma que la respuesta contiene
`structuredContent` y`_meta.openai/widgetAccessible: true`:
Comprueba también quenpx @modelcontextprotocol/[email protected] --cli https:///mcp --transport http --method tools/call --tool-name la solicitud GET https://devuelva/health {"status":"ok"}. Corrige cualquier error antes del aprovisionamiento.
El MCP Inspector muestra el esquema exacto de las herramientas de su servidor. Cópielo íntegramente; no escriba ni modifique manualmente estas definiciones. Esto garantiza que mcpPlugin.json se mantenga sincronizado con el servidor MCP.
Crea servidores MCP que se integren con Microsoft 365 Copilot Chat y muestren widgets interactivos enriquecidos.
Arquitectura
M365 Copilot ──▶ mcpPlugin.json ──▶ Servidor MCP ──▶ structuredContent ──▶ Widget de React + Fluent UI
│ (RemoteMCPServer) (Streamable HTTP) (window.openai.toolOutput)
│
└── Las capacidades (Personas, etc.) proporcionan datos para transmitirlos a las herramientas MCP
Estructura del proyecto
Ejemplo de estructura de proyecto; no es un requisito imprescindible, sino un patrón habitual para organizar el desarrollo del servidor MCP y los widgets:
project/
├── appPackage/
│ ├── manifest.json # Manifiesto de Teams (actualizar la versión al desplegar)
│ ├── declarativeAgent.json # Configuración del agente + capacidades
│ ├── mcpPlugin.json # Definiciones de herramientas con _meta
│ └── instruction.txt # Instrucciones de comportamiento del agente
├── mcp-server/
│ ├── src/index.ts # Servidor con Streamable HTTP
│ ├── widgets/ # Plantillas de widgets + código fuente de React
│ │ ├── my-widget.html # Plantilla mínima devuelta por resources/read
│ │ └── src/my-widget/ # Código fuente de React + Fluent UI
│ ├── assets/ # Paquetes de widgets compilados servidos en /assets
│ └── package.json
├── scripts/
│ ├── setup-devtunnel.sh # Configuración de devtunnel para Linux/Mac
│ └── setup-devtunnel.ps1 # Configuración del devtunnel en Windows
└── env/.env.local # MCP_SERVER_URL, MCP_SERVER_DOMAIN
Nota sobre el lenguaje: aquí se muestra la estructura de un proyecto en TypeScript. Para Python, sustituye mcp-server/src/index.ts por tu punto de entrada de Python (por ejemplo, server.py). Para C#, utiliza una estructura de proyecto .NET estándar. Los directorios appPackage/, widgets/, scripts/ y env/ son independientes del lenguaje.
Protocolo de widgets de Copilot
Tu servidor MCP debe cumplir estos requisitos de protocolo para mostrar widgets en Copilot Chat. Esto se aplica independientemente del lenguaje:
- Transporte HTTP «streamable »: el punto final
/mcpgestiona los métodos POST, GET y DELETE con gestión de sesiones - Encabezados CORS: comprobación de origen en
/mcpque permitam365.cloud.microsofty*.m365.cloud.microsoft, con los encabezados MCP obligatorios - Capacidades del servidor: la respuesta
de inicializacióndebe declararresources: {}ytools: {} - Recursos MCP: registrar los widgets con los URI
ui://widget/, el tipo MIME.html text/html+skybridgeyel _metade CSP - Formato de respuesta de la herramienta: devolver
contenido(texto) +structuredContent(datos del widget) +_metaconopenai/outputTemplate - Servicio de widgets: ruta HTTP en
/widgets/*.htmlpara archivos shell y en/assets/*para paquetes compilados, ambos con CORS con verificación de origen
Para obtener detalles completos sobre el protocolo, las estructuras JSON y una lista de verificación para la adaptación de servidores MCP existentes, consulta references/copilot-widget-protocol.md.
Implementación
Patrón de servidor MCP (referencia de TypeScript)
Consulte references/mcp-server-pattern.md para ver la implementación completa.
Para otros lenguajes, implementa los requisitos descritos en el Protocolo de widgets de Copilot utilizando el SDK de MCP de tu lenguaje. Consulta la tabla «Referencias de SDK por lenguaje» para ver los paquetes del SDK.
Requisitos básicos:
- Exponer el transporte HTTP Streamable en
/mcp - Devuelve
structuredContent+_metaconopenai/outputTemplate - Servir widgets a través de un punto final HTTP
- Gestionar CORS para las solicitudes de origen cruzado
- Gestionar los datos parciales de forma adecuada (rellenar con «Unknown» los campos que falten)
Formato de respuesta de la herramienta:
return {
content: [{ type: "text", text: "Resumen" }],
structuredContent: { /* datos del widget */ },
_meta: { "openai/outputTemplate": "ui://widget/name.html", "openai/widgetAccessible": true }
};
Gestión de datos parciales
Normaliza siempre los datos de entrada para gestionar los campos que faltan:
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
const args = request.params.arguments as { title?: string; items?: Partial- [] };
// Normalizar los datos: rellenar con «Desconocido» los campos que falten
const title = args.title || "Título predeterminado";
const items = (args.items || []).map(item => ({
name: item.name || "Desconocido",
value: item.value || «Desconocido»,
}));
// Crear contenido estructurado para el widget
const structuredContent = { title, items };
// ...
});
Patrón de widget
Consulta references/widget-patterns.md para ver ejemplos completos.
Requisitos básicos:
- Utilizar React + componentes de Fluent UI (
@fluentui/react-components) - Asegúrate de que las dependencias del paquete del widget incluyan
@fluentui/react-components,reactyreact-dom - Aplica un tema con
FluentProvider(webLightTheme/webDarkTheme) ytokensde Fluent - Accede a los datos mediante hooks compartidos (por ejemplo,
useOpenAiGlobal("toolOutput")) - Solución de depuración: datos simulados integrados cuando
window.openaino esté disponible - Gestiona los valores «desconocidos» de forma adecuada (por ejemplo, ocultando los botones de acción)
Esquema del complemento
Consulta references/plugin-schema.md para conocer el formato de mcpPlugin.json.
Requisitos básicos:
- Esquema
v2.4con el tiempo de ejecución deRemoteMCPServer - Matriz `
run_for_functions` que coincida con los nombres de las herramientas _metaen las definiciones de herramientas para el enlace de widgetsinputSchema: hacer que las propiedades sean opcionales para mayor flexibilidad; describir los valores predeterminados en las descripciones
Configuración de DevTunnels
Solo para pruebas locales. DevTunnels está destinado al desarrollo y las pruebas en tu equipo. Antes de compartir el agente de forma más amplia, implementa tanto el servidor MCP como los recursos de los widgets en un entorno alojado (por ejemplo, Azure App Service, Azure Static Web Apps u otro proveedor de alojamiento) y actualiza las URL del manifiesto del agente en consecuencia.
DevTunnels expone tu servidor MCP de localhost a M365 Copilot mediante túneles con nombre para obtener URL estables. Consulta references/devtunnels.md para ver los scripts de configuración, la referencia de comandos y la resolución de problemas.
El script de configuración (npm run tunnel / npm run tunnel:win):
- Crea un túnel con nombre en la primera ejecución (o reutiliza el ya existente)
- Inicia el alojamiento del túnel en el puerto configurado
- Actualiza
el archivo env/.env.localconMCP_SERVER_URLyMCP_SERVER_DOMAIN(solo en la primera ejecución) - Continúa alojando el túnel
Inicio rápido
Terminal 1 - Iniciar el servidor MCP:
cd mcp-server
npm install
npm run dev
Terminal 2 - Iniciar DevTunnel:
npm run tunnel
# O en Windows:
npm run tunnel:win
En la primera ejecución, configura el agente una vez que el túnel esté activo (consulta la regla CONFIGURACIÓN DEL AGENTE). En ejecuciones posteriores, la URL del túnel es estable; no es necesario volver a configurarlo a menos que cambie el manifiesto del agente.
Flujo de trabajo de desarrollo
Inicia el servidor MCP (modo de desarrollo con recarga en caliente):
- 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:
Inicia devtunnel (crea un túnel con nombre la primera vez que se ejecuta; se reutiliza en ejecuciones posteriores):
npm run tunnelAprovisionamiento + prueba — consulta la regla APROVISIONAMIENTO DE AGENTES para saber cuándo es necesario; actualiza
la versiónen manifest.json si Copilot no refleja los cambios
Prácticas recomendadas
Consulte references/best-practices.md para obtener orientación detallada.
Puntos clave:
- Herramientas de renderizado: Acepta datos como entrada, no los recupere internamente
- Instrucciones: Indica al agente que utilice las capacidades PRIMERO y, a continuación, pasa los datos a las herramientas de MCP
- Temas: Utiliza
FluentProviderytokensFluent para la compatibilidad con los temas oscuro y claro - Modo de depuración: incluye datos de reserva para las pruebas locales de widgets
- Datos parciales: gestionar los campos que faltan con valores predeterminados «Desconocido»
- Botones de acción: Ocultar los botones de correo electrónico/chat cuando los datos sean «Desconocidos»
- Actualización de la versión: Actualizar la versión del manifiesto cuando los cambios no se reflejen en 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
Todos los archivos
0 archivosInstalar ui-widget-developer
Descarga y descomprime los archivos de habilidades en tu directorio .claude/skills/.
Descargar ZIPClona el repositorio y copia los archivos de la habilidad a tu proyecto.
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
Copiar





Hogar
