opción
HogarHogar Skill Desarrollo web ui-widget-developer

ui-widget-developer

microsoft/skills 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 todo
5
Tiempo actualizado 18 de septiembre de 2026

Desarrollo 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):

  1. Ejecuta /plugin marketplace add modelcontextprotocol/ext-apps
  2. Ejecuta /plugin install mcp-apps@mcp-apps
  3. 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.md
  • references/best-practices.md REQUISITO 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-components
  • react
  • react-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//main.tsx 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:

  1. Actualiza la versión en manifest.json (incrementa la versión de parche, p. ej., 1.0.01.0.1)
  2. 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:

  1. Inicia el servidor MCP y devtunnel en segundo plano (según los «PROCESOS EN SEGUNDO PLANO» anteriores)
  2. 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
  3. Aprovisiona el agente si es necesario (según «APROVISIONAMIENTO DEL AGENTE» más arriba)
  4. 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:

  1. ☐ Crea un archivo HTML de plantilla de widget en widgets/ y una entrada de widget React en widgets/src// (consulta widget-patterns.md)
  2. ☐ Define una constante URI ui://widget/.html
  3. ☐ Añade una entrada de recurso al array de recursos con:
    • uri: la URI ui://widget/.html
    • mimeType: «text/html+skybridge»
    • _meta: configuración de CSP con openai/widgetDomain y openai/widgetCSP (del entorno)
  4. ☐ Añade un controlador para resources/read que devuelva el código HTML del shell del widget para esta URI
  5. ☐ Añade la herramienta con _meta.openai/outputTemplate apuntando a la misma URI ui://widget/.html
  6. ☐ 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/read se 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:

  1. Inicia el servidor MCP (en segundo plano)
  2. 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
    
    
  3. Copia la definición COMPLETA de la herramienta desde el inspector (incluyendo nombre, descripción, inputSchema, _meta, anotaciones y título)
  4. Pégala en mcpPlugin.json, en runtimes[].spec.mcp_tool_description.tools (dentro del objeto spec del entorno de ejecución RemoteMCPServer )
  5. 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`:
    npx @modelcontextprotocol/[email protected] --cli https:///mcp --transport http --method tools/call --tool-name 
    
    
    Comprueba también que la solicitud GET https:///health devuelva {"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:

  1. Transporte HTTP «streamable »: el punto final /mcp gestiona los métodos POST, GET y DELETE con gestión de sesiones
  2. Encabezados CORS: comprobación de origen en /mcp que permita m365.cloud.microsoft y *.m365.cloud.microsoft, con los encabezados MCP obligatorios
  3. Capacidades del servidor: la respuesta de inicialización debe declarar resources: {} y tools: {}
  4. Recursos MCP: registrar los widgets con los URI ui://widget/.html, el tipo MIME text/html+skybridge y el _meta de CSP
  5. Formato de respuesta de la herramienta: devolver contenido (texto) + structuredContent (datos del widget) + _meta con openai/outputTemplate
  6. Servicio de widgets: ruta HTTP en /widgets/*.html para 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 + _meta con openai/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, react y react-dom
  • Aplica un tema con FluentProvider (webLightTheme/webDarkTheme) y tokens de Fluent
  • Accede a los datos mediante hooks compartidos (por ejemplo, useOpenAiGlobal("toolOutput"))
  • Solución de depuración: datos simulados integrados cuando window.openai no 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.4 con el tiempo de ejecución de RemoteMCPServer
  • Matriz `run_for_functions ` que coincida con los nombres de las herramientas
  • _meta en las definiciones de herramientas para el enlace de widgets
  • inputSchema: 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):

  1. Crea un túnel con nombre en la primera ejecución (o reutiliza el ya existente)
  2. Inicia el alojamiento del túnel en el puerto configurado
  3. Actualiza el archivo env/.env.local con MCP_SERVER_URL y MCP_SERVER_DOMAIN (solo en la primera ejecución)
  4. 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

  1. 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
  2. Inicia devtunnel (crea un túnel con nombre la primera vez que se ejecuta; se reutiliza en ejecuciones posteriores):

    npm run tunnel
    
  3. Aprovisionamiento + prueba — consulta la regla APROVISIONAMIENTO DE AGENTES para saber cuándo es necesario; actualiza la versión en manifest.json si Copilot no refleja los cambios

Prácticas recomendadas

Consulte references/best-practices.md para obtener orientación detallada.

Puntos clave:

  1. Herramientas de renderizado: Acepta datos como entrada, no los recupere internamente
  2. Instrucciones: Indica al agente que utilice las capacidades PRIMERO y, a continuación, pasa los datos a las herramientas de MCP
  3. Temas: Utiliza FluentProvider y tokens Fluent para la compatibilidad con los temas oscuro y claro
  4. Modo de depuración: incluye datos de reserva para las pruebas locales de widgets
  5. Datos parciales: gestionar los campos que faltan con valores predeterminados «Desconocido»
  6. Botones de acción: Ocultar los botones de correo electrónico/chat cuando los datos sean «Desconocidos»
  7. Actualización de la versión: Actualizar la versión del manifiesto cuando los cambios no se reflejen en Copilot
Ver en 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

Todos los archivos

0 archivos

Instalar ui-widget-developer

Descarga y descomprime los archivos de habilidades en tu directorio .claude/skills/.

Descargar ZIP

Clona 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 Copiar
Configuración rápida: Copia la carpeta de la habilidad en .claude/skills/ Claude detectará y utilizará automáticamente la habilidad
Repositorio microsoft/skills

Habilidades relacionadas

github-code-search
Tiempo actualizado 29 de junio de 2026
drizzle-orm
Tiempo actualizado 29 de junio de 2026
clickhouse-io
Tiempo actualizado 29 de junio de 2026
prisma-client-api
Tiempo actualizado 29 de junio de 2026
OR