ui-widget-developer
microsoft/skills
Développez des serveurs MCP pour Copilot Chat avec des widgets interactifs à l'aide de React et de Fluent UI, en prenant en charge à la fois les normes OpenAI Apps SDK et MCP Apps Extension.
...Développer toutDéveloppement du serveur Copilot MCP
Marquage télémétrique
Avant d'exécuter toute commande CLI atk, définissez la variable d'environnement de session afin que tous les appels CLI soient marqués comme ayant été lancés par une skill :
export ATK_CLI_SKILL=true
Exécutez cette commande une seule fois au début de la session. Toutes les commandes atk suivantes dans le même terminal en hériteront.
🔀 SÉLECTION DU PARCOURS
Avant de continuer, demandez à l’utilisateur quel chemin il souhaite emprunter à l’aide de AskUserQuestion. Les deux options sont prises en charge pour le rendu du widget M365 Copilot. Présentez-lui les avantages et inconvénients de chaque option et laissez-le choisir :
| Applications OAI (cette compétence) | Extension d’applications MCP (modelcontextprotocol/ext-apps) |
|
|---|---|---|
| Standard | Spécifique à OpenAI | Norme MCP officielle |
| Fonctionne avec | ChatGPT + M365 Copilot | M365 Copilot, ChatGPT, VSCode et bien d’autres |
| Maturité | Éprouvé sur le terrain, prêt pour la production | Nouvelle norme officielle, écosystème en pleine expansion |
| Conception | SDK OpenAI Apps | Protocole MCP Apps (multiplateforme) |
| Quand l'adopter | Investissement existant dans les applications OAI | Préférence pour une norme ouverte, souhait de bénéficier d’une prise en charge client la plus large possible |
Demandez : « Souhaitez-vous développer une application OAI (SDK OpenAI Apps — éprouvé, fonctionne dans ChatGPT et M365 Copilot) ou une application MCP (nouvelle norme officielle — fonctionne dans M365 Copilot, ChatGPT, VSCode, et plus encore) ? »
- Applications OAI → Continuez ci-dessous. Cette compétence couvre tout ce dont vous avez besoin.
- Applications MCP → Installez le plugin
modelcontextprotocol/ext-apps(voir ci-dessous), puis utilisez la compétence appropriée de ce plugin.
Applications MCP : installer le plugin ext-apps
Si l’utilisateur choisit les applications MCP, effectuez cette opération automatiquement (ne vous contentez pas d’une simple explication) :
- Exécutez
/plugin marketplace add modelcontextprotocol/ext-apps - Exécutez
/plugin install mcp-apps@mcp-apps - Vérifiez que le plugin est disponible, puis invoquez la compétence ext-apps appropriée en fonction de l’intention de l’utilisateur
Si les commandes du plugin ne sont pas disponibles dans l’environnement actuel, fournissez les commandes exactes ci-dessous et demandez à l’utilisateur de les exécuter une fois, puis poursuivez en invoquant la compétence ext-apps sélectionnée.
Commandes de référence :
Pour créer une application MCP, installez le plugin ext-apps depuis le marketplace :
1. /plugin marketplace add modelcontextprotocol/ext-apps
2. /plugin install mcp-apps@mcp-apps
Utilisez ensuite l’une des compétences suivantes de ce plugin :
- create-mcp-app — Créer de toutes pièces une nouvelle application MCP avec une interface utilisateur interactive
- add-app-to-server — Ajouter une interface utilisateur interactive aux outils d’un serveur MCP existant
- migrate-oai-app — Convertit une application OAI existante pour qu’elle utilise les applications MCP
- convert-web-app — Transforme une application web en une application hybride web + MCP
Une fois l’installation terminée, invoquez la compétence appropriée pour continuer.
Remarque : le plugin ext-apps se trouve sur la place de marché external
modelcontextprotocol/ext-apps— il ne fait pas partie de cette collection de plugins.
Mappage de transfert après l’installation :
- Nouvelle application MCP créée à partir de zéro →
create-mcp-app - Ajouter l’interface utilisateur d’une application à un serveur MCP existant →
add-app-to-server - Migrer une application OAI existante →
migrate-oai-app - Convertir une application web existante →
convert-web-app
📛 DÉTECTION DE PROJET 📛
Cette compétence se déclenche lors de la création de serveurs MCP avec une application OAI ou un rendu de widget pour Microsoft 365 Copilot Chat. Le serveur MCP peut être écrit dans n’importe quel langage prenant en charge le protocole MCP (TypeScript, Python, C#, etc.). Le projet d’agent et le serveur MCP peuvent se trouver dans le même dépôt, dans des dossiers distincts ou dans des projets totalement différents.
Routage des scénarios
| Point de départ | Ce dont vous avez besoin | Parcours |
|---|---|---|
| Privilégier la norme MCP Apps | Prise en charge des widgets multiplateformes (M365 Copilot, ChatGPT, VSCode, etc.) | Installez modelcontextprotocol/ext-apps, puis utilisez create-mcp-app ou add-app-to-server — voir la section « Choix du chemin » ci-dessus |
| Partir de zéro (sans agent, sans serveur MCP) | Configuration complète de l’application OAI | Confiez d’abord la création de la structure de base de l’agent à declarative-agent-developer, puis revenez ici pour le serveur MCP et les widgets |
| Agent M365 existant, nouveau serveur MCP | Serveur MCP + widgets + mcpPlugin.json | Commencer par la mise en œuvre |
| Serveur MCP existant, ajout de widgets Copilot | Prise en charge des widgets ajoutée au serveur existant | Commencer par le protocole des widgets Copilot |
| Choix du langage (autre que TypeScript) | Exigences du protocole | Consultez le protocole des widgets Copilot pour savoir ce qu'il faut implémenter, et le modèle de serveur MCP (TypeScript) comme référence |
🚨 RÈGLES D'EXÉCUTION CRITIQUES 🚨
APPLICATION DE FLUENT UI (OBLIGATOIRE) : les implémentations de widgets DOIVENT utiliser React + les composants Fluent UI. Avant d’écrire le moindre code de widget, l’agent DOIT lire et respecter :
references/widget-patterns.mdreferences/best-practices.mdEXIGENCE RELATIVE AU PAQUET FLUENT UI (OBLIGATOIRE) : le projet de widget DOIT inclure les dépendances Fluent UI avant toute implémentation. Au minimum, installez et conservez les éléments suivants dans les dépendances du paquet du widget :@fluentui/react-componentsreactreact-dom
Si l'un de ces paquets est manquant, installez-le automatiquement avant de poursuivre la génération du code du widget.
Si le widget généré ne comprend pas de fichiers d’entrée React (par exemple, widgets/src/ et un fichier de composant React) ni les importations Fluent provenant de @fluentui/react-components, la tâche est incomplète et DOIT être corrigée avant de renvoyer des résultats.
PAS DE WIDGETS EN HTML BRUT UNIQUEMENT (PAR DÉFAUT) : N’implémentez pas le contenu de l’application directement à l’aide de modèles HTML statiques et de code JavaScript en ligne comme solution finale pour le widget. Un fichier HTML minimal de base est autorisé uniquement en tant que chargeur pour les ressources React compilées. Les widgets bruts/autonomes composés uniquement de code HTML ne sont autorisés que lorsque l’utilisateur demande explicitement un prototype non-React.
PROCESSUS D'ARRIÈRE-PLAN : le serveur MCP et devtunnel DOIVENT être lancés en tant que processus indépendants du système d’exploitation — ils NE DOIVENT PAS s’exécuter au sein de la session shell de l’agent. isBackground: true, mode: « async » et Start-Job s’exécutent tous au sein de la session shell de l’agent et seront interrompus entre deux messages. La seule approche fiable consiste à lancer un processus système détaché.
Windows — utilisez Start-Process -WindowStyle Hidden:
# Démarrer devtunnel
$t = Start-Process -FilePath "devtunnel" `
-ArgumentList "host","","-a" `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput "tunnel.log" -RedirectStandardError "tunnel-err.log"
# Démarrer le serveur MCP — utilisez cmd.exe /c pour définir le répertoire de travail et hériter du PATH
$s = Start-Process -FilePath "cmd.exe" `
-ArgumentList "/c","cd /d && " `
-WindowStyle Hidden -PassThru `
-RedirectStandardOutput "server.log" -RedirectStandardError "server-err.log"
# Enregistrer les PID afin de pouvoir les arrêter ultérieurement
"$($t.Id),$($s.Id)" | Out-File pids.txt
Write-Host "Tunnel démarré : PID $($t.Id), serveur : PID $($s.Id)"
Pour arrêter : Stop-Process -Id (Get-Content pids.txt).Split(',') ou Stop-Process -Id .
Linux/Mac — utilisez nohup avec &:
nohup devtunnel host > tunnel.log 2>tunnel-err.log &
echo "tunnel:$!" >> pids.txt
nohup > server.log 2>server-err.log &
echo "server:$!" >> pids.txt
Pour arrêter : kill $(grep -oP '\d+' pids.txt).
Après le démarrage, surveillez les fichiers journaux pour vérifier que les deux processus sont bien en cours d'exécution avant de continuer :
# Windows
Start-Sleep 3; Get-Content tunnel.log, server.log
# Linux/Mac
sleep 3 && tail tunnel.log server.log
AUTOMATISATION TOTALE : ne demandez jamais à l’utilisateur d’exécuter des commandes manuellement. Installez les outils, authentifiez-vous, démarrez les services — faites tout automatiquement. Ne demandez à l’utilisateur une intervention interactive que lorsque cela est vraiment nécessaire (comme la confirmation du code du périphérique lors de la connexion de l’utilisateur devtunnel avec les options -g -d). Si un outil n’est pas installé, installez-le. Si un service doit être démarré, démarrez-le. L’utilisateur s’attend à une automatisation complète.
CHOIX DU CHEMIN (OBLIGATOIRE — S'ARRÊTER AVANT TOUTE ÉCRITURE DE CODE) : Vous DEVEZ utiliser AskUserQuestion pour demander à l’utilisateur s’il souhaite les applications OAI ou l’extension MCP Apps avant d’écrire le moindre code, d’exécuter la moindre commande ou de prendre la moindre décision architecturale.
Il n’y a aucune exception à cette règle. L’erreur la plus courante consiste à penser que « la demande de l’utilisateur est évidente, donc lui demander est superflu ». Ce raisonnement est toujours erroné — appelez AskUserQuestion quoi qu’il arrive. Un utilisateur qui dit « créez un serveur MCP avec des widgets » n’apporte PAS de réponse à cette question. Un utilisateur qui invoque cette compétence par son nom n’apporte PAS de réponse. Seule une réponse explicite à la question compte. Voir la section « SÉLECTION DU PARCOURS » ci-dessus pour connaître la question exacte à poser.
PROVISIONNEMENT DE L’AGENT : un nouveau provisionnement n’est nécessaire que lorsque le manifeste de l’agent change (par exemple, les définitions d’outils dans mcpPlugin.json, l’URL du serveur MCP, declarativeAgent.json, instruction.txt). Les modifications du code du serveur MCP (implémentations d’outils, code des widgets React, logique du serveur) ne nécessitent PAS de réapprovisionner l’agent : l’exécution ou le déploiement du serveur prend automatiquement en compte ces modifications.
Lorsqu’un provisionnement est nécessaire :
- Mettez à jour la version dans
manifest.json(incrémenter la version de correctif, par exemple1.0.0→1.0.1) - Déployez l’agent :
npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local
LIENS DE TEST DES WIDGETS : Chaque fois que vous transmettez un résultat à l’utilisateur alors que le serveur MCP est en cours d’exécution, vous DEVEZ inclure les liens vers TOUS les widgets afin qu’il puisse les tester localement. Format :
🧪 Tester les widgets localement :
- http://localhost:3001/widgets/widget-name.html
- http://localhost:3001/widgets/another-widget.html
Répertoriez tous les fichiers .html du répertoire mcp-server/widgets/ (ou du dossier de widgets équivalent). Cela permet aux utilisateurs de vérifier le rendu des widgets avant de les tester dans Copilot.
DÉPLOIEMENT AUTOMATIQUE À LA FIN (OBLIGATOIRE — NE PAS IGNORER) : Une fois le codage terminé, passez automatiquement à l’étape suivante sans attendre l’utilisateur :
- Démarrez le serveur MCP et devtunnel en arrière-plan (conformément à la section PROCESSUS EN ARRIÈRE-PLAN ci-dessus)
- Effectuez une vérification de bout en bout avec MCP Inspector (conformément à la section « RÈGLE DE CONFIGURATION DES OUTILS MCP » ci-dessous) — corrigez les éventuels échecs avant de continuer
- Provisionnez l’agent si nécessaire (conformément à la section « PROVISIONNEMENT DE L’AGENT » ci-dessus)
- Imprimer un résumé du projet au format suivant :
## ✅ — Prêt
### Widgets
- [widget-name.html](http://localhost:/widgets/widget-name.html)
- [nom-du-widget2.html](http://localhost:/widgets/nom-du-widget2.html)
### Points de terminaison
- Serveur MCP : http://localhost:/mcp
- MCP via un tunnel : https:///mcp
### Test dans Copilot
Local : https://m365.cloud.microsoft/chat/?titleId={M365_TITLE_ID issu de env/.env.local}
Autres environnements : {SHARE_LINK issu de env/.env.{environment}}
DÉLÉGATION DE PROJET D'AGENT : cette compétence crée des serveurs et des widgets MCP, et NON des projets d'agent déclaratifs. Si la requête de l’utilisateur implique la création ou la configuration de l’agent déclaratif lui-même (scaffolding, m365agents.yml, m365agents.local.yml, declarativeAgent.json, cycle de vie du manifeste), déléguez à la compétence « declarative-agent-developer ».
ENREGISTREMENT DES RESSOURCES MCP : chaque widget DOIT disposer d’une ressource MCP correspondante. Sans ressources, Copilot ne peut pas récupérer les coques de widgets via le protocole MCP et les widgets ne s’afficheront pas.
Pour chaque nouveau widget, suivez cette liste de contrôle :
- ☐ Créez un fichier HTML de coque de widget dans
le répertoire widgets/et une entrée de widget React souswidgets/src/(voir widget-patterns.md)/ - ☐ Définissez une constante URI
ui://widget/.html - ☐ Ajoutez une entrée
Resourceau tableauresourcesavec :uri: l’URIui://widget/.html mimeType:« text/html+skybridge »_meta: configuration CSP avecopenai/widgetDomainetopenai/widgetCSP(provenant de l'environnement)
- ☐ Ajouter un gestionnaire pour
resources/readqui renvoie le code HTML du shell du widget correspondant à cette URI - ☐ Ajouter l’outil avec
_meta.openai/outputTemplatepointant vers la même URIui://widget/.html - ☐ Vérifier que les capacités du serveur incluent
resources: {}dans la réponse d’initialisation
Considérations relatives à la structure du widget et aux ressources :
- Préféré (React + Fluent UI): le code HTML de la ressource doit être une structure minimale renvoyant vers des ressources JS/CSS intégrées, servies depuis la route
/assets/du serveur MCP. - Exception uniquement: le code HTML autonome via
resources/readest réservé aux prototypes explicitement demandés par l’utilisateur. La voie par défaut et de production est React + Fluent UI.
Exemple de structure pour la sortie de compilation React :
Utilisez la variable d’environnement WIDGET_BASE_URL ou MCP_SERVER_URL comme base d’URL pour les ressources (voir la section « URL de base configurable pour les widgets » du fichier mcp-server-pattern.md).
Consultez le fichier mcp-server-pattern.md pour connaître l’ensemble des modèles de mise à disposition des ressources et des éléments.
⚠️ RÈGLE DE CONFIGURATION DES OUTILS MCP ⚠️
N’écrivez JAMAIS manuellement les définitions d’outils dans mcpPlugin.json. Utilisez toujours MCP Inspector pour récupérer les définitions complètes des outils à partir du serveur MCP en cours d’exécution.
CONVENTION DE NOMINATION DES OUTILS : Les noms d’outils DOIVENT respecter le modèle ^[A-Za-z0-9_]+$ (lettres, chiffres et traits de soulignement uniquement). N’utilisez JAMAIS de tirets (-) dans les noms d’outils. Utilisez plutôt des traits de soulignement (par exemple, render_profile et non render-profile).
PROCÉDURE OBLIGATOIRE :
- Démarrez le serveur MCP (en arrière-plan)
- Utilisez MCP Inspector pour récupérer les dernières définitions d’outils :
npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --transport http --method tools/list - Copiez la définition COMPLÈTE de l’outil depuis l’inspector (y compris
le nom,la description,inputSchema,_meta,les annotations,le titre) - Collez-la dans
mcpPlugin.jsonsousruntimes[].spec.mcp_tool_description.tools(à l’intérieur de l’objetspecdu runtimeRemoteMCPServer) - Effectuez une vérification de bout en bout via le devtunnel : appelez chaque outil et vérifiez que la réponse contient
« structuredContent »et «_meta.openai/widgetAccessible: true» :
Vérifiez également quenpx @modelcontextprotocol/[email protected] --cli https:///mcp --transport http --method tools/call --tool-name la requête GET https://renvoie/health {"status":"ok"}. Corrigez tout échec avant le provisionnement.
MCP Inspector affiche le schéma exact des outils de votre serveur. Copiez-le intégralement — ne rédigez ni ne modifiez manuellement ces définitions. Cela garantit que le fichier mcpPlugin.json reste synchronisé avec le serveur MCP.
Créez des serveurs MCP qui s’intègrent à Microsoft 365 Copilot Chat et affichent des widgets interactifs riches.
Architecture
M365 Copilot ──▶ mcpPlugin.json ──▶ Serveur MCP ──▶ structuredContent ──▶ Widget React + Fluent UI
│ (RemoteMCPServer) (Streamable HTTP) (window.openai.toolOutput)
│
└── Les fonctionnalités (Personnes, etc.) fournissent des données à transmettre aux outils MCP
Structure du projet
Exemple de structure de projet ; il ne s’agit pas d’une exigence stricte, mais d’un modèle courant pour organiser le développement du serveur MCP et des widgets :
project/
├── appPackage/
│ ├── manifest.json # Manifeste des équipes (incrémenter la version lors du déploiement)
│ ├── declarativeAgent.json # Configuration de l’agent + capacités
│ ├── mcpPlugin.json # Définitions d’outils avec _meta
│ └── instruction.txt # Instructions de comportement de l’agent
├── mcp-server/
│ ├── src/index.ts # Serveur avec Streamable HTTP
│ ├── widgets/ # Coques de widgets + code source React
│ │ ├── my-widget.html # Coque minimale renvoyée par resources/read
│ │ └── src/my-widget/ # Code source React + Fluent UI
│ ├── assets/ # Bundles de widgets compilés, servis à l'adresse /assets
│ └── package.json
├── scripts/
│ ├── setup-devtunnel.sh # Configuration du devtunnel sous Linux/Mac
│ └── setup-devtunnel.ps1 # Configuration du devtunnel sous Windows
└── env/.env.local # MCP_SERVER_URL, MCP_SERVER_DOMAIN
Remarque concernant le langage: ceci illustre la structure d’un projet TypeScript. Pour Python, remplacez mcp-server/src/index.ts par votre point d'entrée Python (par exemple, server.py). Pour C#, utilisez une structure de projet .NET standard. Les répertoires appPackage/, widgets/, scripts/ et env/ sont indépendants du langage.
Protocole des widgets Copilot
Votre serveur MCP doit respecter ces exigences de protocole pour afficher des widgets dans Copilot Chat. Cela s’applique quel que soit le langage :
- Transport HTTP streamable — point de terminaison
/mcpgérant les requêtes POST, GET et DELETE avec gestion des sessions - En-têtes CORS — Vérification de l’origine sur
/mcpautorisantm365.cloud.microsoftet*.m365.cloud.microsoft, avec les en-têtes MCP requis - Capacités du serveur — la réponse
d’initialisationdoit déclarerles ressources : {}etles outils : {} - Ressources MCP — Enregistrement des widgets avec les URI
ui://widget/, le type MIME.html text/html+skybridgeet le paramètre CSP_meta - Format de réponse des outils — Renvoyer
le contenu(text) +structuredContent(données du widget) +_metaavecopenai/outputTemplate - Diffusion des widgets — Route HTTP à l’adresse
/widgets/*.htmlpour les fichiers shell et/assets/*pour les bundles compilés, les deux avec vérification d’origine CORS
Pour obtenir tous les détails du protocole, les structures JSON et une liste de contrôle pour l’adaptation des serveurs MCP existants, consultez le fichier references/copilot-widget-protocol.md.
Implémentation
Modèle de serveur MCP (référence TypeScript)
Consultez le fichier references/mcp-server-pattern.md pour l’implémentation complète.
Pour les autres langages, implémentez les exigences décrites dans le protocole Copilot Widget à l’aide du SDK MCP de votre langage. Consultez le tableau « Références des SDK par langage » pour connaître les paquets SDK.
Exigences de base :
- Exposer le transport HTTP Streamable sur
/mcp - Renvoyer
structuredContent+_metaavecopenai/outputTemplate - Fournir les widgets via un point de terminaison HTTP
- Gérer le CORS pour les requêtes inter-origines
- Gérer les données partielles de manière appropriée (remplacer les champs manquants par « Unknown »)
Format de réponse de l'outil :
return {
content: [{ type: "text", text: "Résumé" }],
structuredContent: { /* données du widget */ },
_meta: { "openai/outputTemplate": "ui://widget/name.html", "openai/widgetAccessible": true }
};
Gestion des données partielles
Toujours normaliser les données d'entrée pour gérer les champs manquants :
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
const args = request.params.arguments as { title?: string; items?: Partial- [] };
// Normaliser les données – remplacer les champs manquants par « Unknown »
const title = args.title || "Titre par défaut";
const items = (args.items || []).map(item => ({
name: item.name || "Unknown",
value: item.value || « Inconnu »,
}));
// Créer le contenu structuré pour le widget
const structuredContent = { title, items };
// ...
});
Modèle de widget
Consultez le fichier references/widget-patterns.md pour des exemples complets.
Exigences de base :
- Utilisez React et les composants Fluent UI (
@fluentui/react-components) - S'assurer que les dépendances du package du widget incluent
@fluentui/react-components,reactetreact-dom - Appliquer un thème à l'aide de
FluentProvider(webLightTheme/webDarkTheme) etdes jetonsFluent - Accéder aux données via des hooks partagés (par exemple,
useOpenAiGlobal("toolOutput")) - Débogage de secours : données factices intégrées lorsque
window.openain'est pas disponible - Gérer les valeurs « inconnues » de manière appropriée (par exemple, masquer les boutons d’action)
Schéma du plugin
Consultez le fichier references/plugin-schema.md pour connaître le format du fichier mcpPlugin.json.
Exigences de base :
- schéma
v2.4avec le runtimeRemoteMCPServer - Tableau
run_for_functionscorrespondant aux noms des outils _metadans les définitions d'outils pour la liaison des widgetsinputSchema: rendre les propriétés facultatives pour plus de flexibilité, décrire les valeurs par défaut dans les descriptions
Configuration de DevTunnels
Test local uniquement. Les DevTunnels sont destinés au développement et aux tests sur votre machine. Avant de partager l’agent à plus grande échelle, déployez à la fois le serveur MCP et les ressources des widgets dans un environnement hébergé (par exemple, Azure App Service, Azure Static Web Apps ou un autre fournisseur d’hébergement) et mettez à jour les URL du manifeste de l’agent en conséquence.
Les DevTunnels exposent votre serveur MCP localhost à M365 Copilot à l’aide de tunnels nommés pour des URL stables. Consultez le fichier references/devtunnels.md pour les scripts de configuration, la référence des commandes et le dépannage.
Le script d’installation (npm run tunnel / npm run tunnel:win) :
- Crée un tunnel nommé lors de la première exécution (ou réutilise celui qui existe déjà)
- Lance l’hébergement du tunnel sur le port configuré
- Met à jour
le fichier env/.env.localavecMCP_SERVER_URLetMCP_SERVER_DOMAIN(lors de la première exécution uniquement) - Poursuit l’hébergement du tunnel
Démarrage rapide
Terminal 1 - Démarrer le serveur MCP :
cd mcp-server
npm install
npm run dev
Terminal 2 - Démarrer DevTunnel :
npm run tunnel
# Ou sous Windows :
npm run tunnel:win
Lors du premier lancement, provisionnez l'agent une fois le tunnel établi (voir la règle PROVISIONNEMENT DE L'AGENT). Lors des lancements suivants, l'URL du tunnel reste stable — aucun réapprovisionnement n'est nécessaire, sauf en cas de modification du manifeste de l'agent.
Workflow de développement
Démarrez le serveur MCP (mode développement avec rechargement à chaud) :
- 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 :
Démarrez devtunnel (crée un tunnel nommé lors de la première exécution, le réutilise lors des exécutions suivantes) :
npm run tunnelProvisionnement + test — voir la règle AGENT PROVISIONING pour savoir quand cela est nécessaire ; mettez à jour
la versiondans manifest.json si Copilot ne reflète pas les modifications
Bonnes pratiques
Consultez le fichier references/best-practices.md pour obtenir des conseils détaillés.
Points clés :
- Outils de rendu: acceptez les données en entrée, ne les récupérez pas en interne
- Instructions: demandez d’abord à l’agent d’utiliser ses capacités, puis transmettez les données aux outils MCP
- Thèmes: utilisez
FluentProvider+les jetonsFluent pour la prise en charge des modes sombre/clair - Mode débogage: inclure des données de secours pour les tests de widgets en local
- Données partielles: gérer les champs manquants avec des valeurs par défaut « Inconnu »
- Boutons d’action: masquer les boutons d’e-mail/de chat lorsque les données sont « Inconnues »
- Mise à jour de la version: mettre à jour la version du manifeste lorsque les modifications ne sont pas prises en compte dans 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
Tous les fichiers
0 fichiersInstaller ui-widget-developer
Téléchargez et décompressez les fichiers de compétences dans votre répertoire .claude/skills/.
Télécharger le ZIPClonez le dépôt et copiez les fichiers de compétence dans votre projet.
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
Copier





Maison
