opção
LarLar Skill Desenvolvimento Web ui-widget-developer

ui-widget-developer

microsoft/skills microsoft/skills

Crie servidores MCP para o Copilot Chat com widgets interativos usando React e Fluent UI, com suporte tanto ao OpenAI Apps SDK quanto aos padrões da MCP Apps Extension.

...Expandir tudo
5
Tempo atualizado 18 de Setembro de 2026

Desenvolvimento do servidor Copilot MCP

Marcação de telemetria

Antes de executar qualquer comando da CLI do ATK, defina a variável de ambiente da sessão para que todas as chamadas da CLI sejam marcadas como iniciadas pela skill:

export ATK_CLI_SKILL=true

Execute isso uma vez no início da sessão. Todos os comandos atk subsequentes no mesmo terminal herdarão essa configuração.

🔀 SELEÇÃO DE CAMINHO

Antes de prosseguir, pergunte ao usuário qual caminho ele deseja seguir usando AskUserQuestion. Ambos são compatíveis com a renderização do widget do M365 Copilot. Apresente as vantagens e desvantagens e deixe que ele escolha:

Aplicativos OAI (esta skill) Extensão de aplicativos MCP (modelcontextprotocol/ext-apps)
Padrão Específico da OpenAI Padrão oficial do MCP
Funciona no ChatGPT + M365 Copilot M365 Copilot, ChatGPT, VSCode e muito mais
Maturidade Testado em produção, pronto para uso Novo padrão oficial, ecossistema em crescimento
Design SDK do OpenAI Apps Protocolo MCP Apps (multiplataforma)
Quando escolher Investimento existente em aplicativos OAI Preferência pelo padrão aberto, desejo de suporte mais amplo aos clientes

Pergunte: “Você gostaria de desenvolver um aplicativo OAI (SDK do OpenAI Apps — comprovado em prática, funciona no ChatGPT e no M365 Copilot) ou um aplicativo MCP (novo padrão oficial — funciona no M365 Copilot, ChatGPT, VSCode e muito mais)?”

  • Aplicativos OAI → Continue abaixo. Esta habilidade abrange tudo o que você precisa.
  • Aplicativos MCP → Instale o plug-in modelcontextprotocol/ext-apps (veja abaixo) e, em seguida, use a habilidade apropriada desse plug-in.

Aplicativos MCP: Instale o plug-in ext-apps

Se o usuário escolher os aplicativos MCP, faça isso automaticamente (não se limite apenas à explicação):

  1. Execute /plugin marketplace add modelcontextprotocol/ext-apps
  2. Execute /plugin install mcp-apps@mcp-apps
  3. Confirme se o plug-in está disponível e, em seguida, invoque a skill ext-apps correta com base na intenção do usuário

Se os comandos do plugin não estiverem disponíveis no ambiente atual, forneça os comandos exatos abaixo e peça ao usuário para executá-los uma vez; em seguida, continue invocando a skill ext-apps selecionada.

Comandos de referência:

Para criar um aplicativo MCP, instale o plug-in ext-apps a partir do marketplace:

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

Em seguida, use uma destas habilidades desse plug-in:
- create-mcp-app      — Crie do zero um novo aplicativo MCP com interface de usuário interativa
- add-app-to-server   — Adicione uma interface de usuário interativa às ferramentas de um servidor MCP existente
- migrate-oai-app     — Converte um aplicativo OAI existente para usar aplicativos MCP
- convert-web-app     — Transforma um aplicativo web em um aplicativo híbrido web + MCP

Após a instalação, chame a função relevante para continuar.

Observação: o plug-in ext-apps está disponível no marketplace externo modelcontextprotocol/ext-apps — ele não faz parte desta coleção de plug-ins.

Mapeamento de transferência após a instalação:

  • Novo aplicativo MCP criado do zero → create-mcp-app
  • Adicionar interface de usuário do aplicativo a um servidor MCP existente → add-app-to-server
  • Migrar aplicativo OAI existente → migrate-oai-app
  • Converter um aplicativo web existente → convert-web-app

📛 DETECÇÃO DE PROJETO 📛

Esta habilidade é acionada ao criar servidores MCP com renderização de aplicativos OAI ou widgets para o Microsoft 365 Copilot Chat. O servidor MCP pode ser escrito em qualquer linguagem que suporte o protocolo MCP (TypeScript, Python, C#, etc.). O projeto do agente e o servidor MCP podem estar no mesmo repositório, em pastas separadas ou em projetos totalmente diferentes.

Roteamento de cenários

Ponto de partida O que você precisa Caminho
Preferência pelo padrão do MCP Apps Suporte a widgets multiplataforma (M365 Copilot, ChatGPT, VSCode e outros) Instale o modelcontextprotocol/ext-apps e, em seguida, use o create-mcp-app ou o add-app-to-server — consulte a seção “Seleção do caminho” acima
Do zero (sem agente, sem servidor MCP) Configuração completa do aplicativo OAI Delegue a estrutura do agente ao declarative-agent-developer primeiro; depois, volte aqui para o servidor MCP + widgets
Agente M365 existente, novo servidor MCP Servidor MCP + widgets + mcpPlugin.json Comece pela implementação
Servidor MCP existente, adicione widgets do Copilot Suporte a widgets adicionado ao servidor existente Comece pelo Protocolo de Widgets do Copilot
Escolha da linguagem (que não seja TypeScript) Requisitos do protocolo Consulte o Protocolo de Widgets do Copilot para saber o que implementar e o Padrão de Servidor MCP (TypeScript) como referência

🚨 REGRAS CRÍTICAS DE EXECUÇÃO 🚨

APLICAÇÃO DO FLUENT UI (OBRIGATÓRIO): As implementações de widgets DEVEM usar componentes React + Fluent UI. Antes de escrever qualquer código de widget, o agente DEVE ler e seguir:

  • references/widget-patterns.md
  • references/best-practices.md REQUISITO DE PACOTE DO FLUENT UI (OBRIGATÓRIO): O projeto do widget DEVE incluir as dependências do Fluent UI antes da implementação. No mínimo, instale e mantenha estas dependências no pacote do widget:
  • @fluentui/react-components
  • react
  • react-dom

Se algum desses pacotes estiver faltando, instale-os automaticamente antes de continuar com a geração do código do widget.

Se o widget gerado não incluir arquivos de entrada do React (por exemplo , widgets/src//main.tsx e um arquivo de componente do React) e as importações do Fluent de @fluentui/react-components, a tarefa estará incompleta e DEVE ser corrigida antes de retornar os resultados.

NENHUM WIDGET SOMENTE EM HTML BRUTO (PADRÃO): Não implemente o conteúdo do aplicativo diretamente com modelos HTML estáticos e JS embutido como solução final do widget. Um arquivo HTML mínimo é permitido apenas como carregador para ativos React compilados. Widgets apenas em HTML bruto/autônomos são permitidos somente quando o usuário solicitar explicitamente um protótipo não-React.

PROCESSOS EM SEGUNDO PLANO: O servidor MCP e o devtunnel DEVEM ser iniciados como processos independentes do sistema operacional — NÃO devem ser executados dentro da sessão de shell do agente. isBackground: true, mode: "async" e Start-Job são executados dentro da sessão de shell do agente e serão encerrados entre as mensagens. A única abordagem confiável é iniciar um processo separado do sistema operacional.

Windows — use Start-Process -WindowStyle Hidden:

# Iniciar o devtunnel
$t = Start-Process -FilePath "devtunnel" `
    -ArgumentList "host","","-a" `
    -WindowStyle Hidden -PassThru `
    -RedirectStandardOutput "tunnel.log" -RedirectStandardError "tunnel-err.log"

# Inicie o servidor MCP — use cmd.exe /c para definir o diretório de trabalho e herdar o PATH
$s = Start-Process -FilePath "cmd.exe" `
    -ArgumentList "/c","cd /d  && " `
    -WindowStyle Hidden -PassThru `
    -RedirectStandardOutput "server.log" -RedirectStandardError "server-err.log"

# Salvar os PIDs para que possam ser interrompidos posteriormente
"$($t.Id),$($s.Id)" | Out-File pids.txt
Write-Host "Túnel iniciado com PID $($t.Id), servidor com PID $($s.Id)"

Para interromper: Stop-Process -Id (Get-Content pids.txt).Split(',') ou Stop-Process -Id .

Linux/Mac — use o nohup com &:

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 interromper: kill $(grep -oP '\d+' pids.txt).

Após iniciar, monitore os logs para confirmar se ambos os processos estão em execução antes de prosseguir:

# Windows
Start-Sleep 3; Get-Content tunnel.log, server.log
# Linux/Mac
sleep 3 && tail tunnel.log server.log

AUTOMAÇÃO TOTAL: Nunca peça ao usuário para executar comandos manualmente. Instale ferramentas, autentique, inicie serviços — faça tudo automaticamente. Só solicite ao usuário entradas interativas que realmente sejam necessárias (como a confirmação do código do dispositivo durante o login do usuário no devtunnel com -g -d). Se uma ferramenta não estiver instalada, instale-a. Se um serviço precisar ser iniciado, inicie-o. O usuário espera automação total.

SELEÇÃO DO CAMINHO (OBRIGATÓRIO — PARE ANTES DE ESCREVER QUALQUER CÓDIGO): Você DEVE usar AskUserQuestion para perguntar ao usuário se ele deseja o OAI Apps ou a extensão MCP Apps antes de escrever qualquer código, executar qualquer comando ou tomar qualquer decisão arquitetônica.

Não há exceção a essa regra. O erro mais comum é o raciocínio de que “a solicitação do usuário deixa isso óbvio, portanto, perguntar é redundante”. Esse raciocínio está sempre errado — invoque AskUserQuestion independentemente disso. Um usuário que diga “crie um servidor MCP com widgets” NÃO é uma resposta a essa pergunta. Um usuário que invoque essa habilidade pelo nome NÃO é uma resposta. Apenas uma resposta explícita à pergunta é válida. Consulte SELEÇÃO DE CAMINHO acima para saber exatamente qual pergunta fazer.

PROVISIONAMENTO DO AGENTE: O re-provisionamento só é necessário quando o manifesto do agente é alterado (por exemplo, definições de ferramentas no mcpPlugin.json, URL do servidor MCP, declarativeAgent.json, instruction.txt). Alterações no código do servidor MCP (implementações de ferramentas, código do widget React, lógica do servidor) NÃO exigem o reabastecimento do agente — a execução ou a implantação do servidor detecta as alterações automaticamente.

Quando o provisionamento for necessário:

  1. Aumente a versão no manifest.json (incrementa a versão de patch, por exemplo, 1.0.01.0.1)
  2. Implemente o agente:
    npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local
    
    

LINKS PARA TESTE DE WIDGETS: Sempre que você enviar um resultado ao usuário enquanto o servidor MCP estiver em execução, você DEVE incluir links para TODOS os widgets para que eles possam testá-los localmente. Formato:

🧪 Testar widgets localmente:
- http://localhost:3001/widgets/widget-name.html
- http://localhost:3001/widgets/another-widget.html

Liste todos os arquivos .html no diretório mcp-server/widgets/ (ou pasta de widgets equivalente). Isso ajuda os usuários a verificar a renderização dos widgets antes de testá-los no Copilot.

IMPLEMENTAÇÃO AUTOMÁTICA APÓS A CONCLUSÃO (OBRIGATÓRIO — NÃO IGNORE): Quando a codificação estiver concluída, prossiga automaticamente sem esperar pelo usuário:

  1. Inicie o servidor MCP + devtunnel em segundo plano (conforme PROCESSOS EM SEGUNDO PLANO acima)
  2. Execute a verificação E2E com o MCP Inspector (conforme a REGRA DE CONFIGURAÇÃO DA FERRAMENTA MCP abaixo) — corrija quaisquer falhas antes de continuar
  3. Provisionar o agente, se necessário (conforme “PROVISIONAMENTO DO AGENTE” acima)
  4. Imprima um resumo do projeto neste formato:
## ✅  — Pronto

### Widgets
- [widget-name.html](http://localhost:/widgets/widget-name.html)
- [nome-do-widget2.html](http://localhost:/widgets/nome-do-widget2.html)

### Endpoints
- Servidor MCP: http://localhost:/mcp
- MCP via túnel: https:///mcp

### Teste no Copilot
Local:      https://m365.cloud.microsoft/chat/?titleId={M365_TITLE_ID de env/.env.local}
Outros ambientes: {SHARE_LINK de env/.env.{environment}}

DELEGAÇÃO DE PROJETOS DE AGENTE: Esta habilidade cria servidores e widgets MCP, NÃO projetos de agente declarativos. Se a solicitação do usuário envolver a criação ou configuração do próprio agente declarativo (estrutura, m365agents.yml, m365agents.local.yml, declarativeAgent.json, ciclo de vida do manifesto), delegue à habilidade declarative-agent-developer.

REGISTRO DE RECURSOS DO MCP: Cada widget DEVE ter um recurso MCP correspondente. Sem recursos, o Copilot não consegue buscar os shells dos widgets por meio do protocolo MCP e os widgets não serão renderizados.

Para cada novo widget, preencha esta lista de verificação:

  1. ☐ Crie um arquivo HTML de shell de widget em widgets/ e uma entrada de widget React em widgets/src// (consulte widget-patterns.md)
  2. ☐ Defina uma constante de URI ui://widget/.html
  3. ☐ Adicione uma entrada de recurso à matriz de recursos com:
    • uri: a URI ui://widget/.html
    • mimeType: "text/html+skybridge"
    • _meta: configuração de CSP com openai/widgetDomain e openai/widgetCSP (do ambiente)
  4. ☐ Adicione um manipulador para resources/read que retorne o HTML do shell do widget para este URI
  5. ☐ Adicione a ferramenta com _meta.openai/outputTemplate apontando para a mesma URI ui://widget/.html
  6. ☐ Verifique se os recursos do servidor incluem resources: {} na resposta de inicialização

Considerações sobre o shell do widget e os ativos:

  • Preferencial (React + Fluent UI): O HTML do recurso deve ser um shell mínimo que aponte para os ativos JS/CSS compilados, servidos pela rota /assets/ do servidor MCP.
  • Apenas como exceção: HTML autônomo via resources/read destina-se exclusivamente a protótipos explicitamente solicitados pelo usuário. O caminho padrão e de produção é React + Fluent UI.

Exemplo de shell para saída de compilação do React:


  
  

  

Use a variável de ambiente WIDGET_BASE_URL ou MCP_SERVER_URL como base da URL dos ativos (consulte a seção “Configurable Widget Base URL” no arquivo mcp-server-pattern.md).

Consulte mcp-server-pattern.md para conhecer os padrões completos de fornecimento de recursos e ativos.

⚠️ REGRA DE CONFIGURAÇÃO DA FERRAMENTA MCP ⚠️

NUNCA escreva manualmente definições de ferramentas no mcpPlugin.json. Sempre use o MCP Inspector para obter as definições completas das ferramentas a partir do servidor MCP em execução.

CONVENÇÃO DE NOMEAÇÃO DE FERRAMENTAS: Os nomes das ferramentas DEVEM seguir o padrão ^[A-Za-z0-9_]+$ (apenas letras, números e sublinhados). NUNCA use hífens (-) nos nomes das ferramentas. Use sublinhados em vez disso (por exemplo, render_profile em vez de render-profile).

FLUXO DE TRABALHO OBRIGATÓRIO:

  1. Inicie o servidor MCP (em segundo plano)
  2. Use o MCP Inspector para obter as definições mais recentes das ferramentas:
    npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --transport http --method tools/list
    
    
  3. Copie a definição COMPLETA da ferramenta do inspector (incluindo nome, descrição, inputSchema, _meta, anotações, título)
  4. Cole no arquivo mcpPlugin.json, na seção runtimes[].spec.mcp_tool_description.tools (dentro do objeto spec do runtime RemoteMCPServer )
  5. Execute a verificação E2E por meio do devtunnel — chame cada ferramenta e confirme se a resposta contém `structuredContent ` e `_meta.openai/widgetAccessible: true`: `
    npx @modelcontextprotocol/[email protected]` --cli https:///mcp --transport http --method tools/call --tool-name 
    
    
    Verifique também se a chamada GET https:///health retorna {"status":"ok"}. Corrija quaisquer falhas antes do provisionamento.

O MCP Inspector exibe o esquema exato da ferramenta do seu servidor. Copie-o na íntegra — não escreva nem modifique manualmente essas definições. Isso garante que o mcpPlugin.json permaneça sincronizado com o servidor MCP.

Crie servidores MCP que se integrem ao Microsoft 365 Copilot Chat e exibam widgets interativos avançados.

Arquitetura

M365 Copilot ──▶ mcpPlugin.json ──▶ Servidor MCP ──▶ structuredContent ──▶ Widget React + Fluent UI
     │              (RemoteMCPServer)    (Streamable HTTP)                  (window.openai.toolOutput)
     │
     └── Recursos (Pessoas, etc.) fornecem dados para serem passados às ferramentas MCP

Estrutura do projeto

Exemplo de estrutura de projeto; não é um requisito obrigatório, mas um padrão comum para organizar o desenvolvimento do servidor MCP + widget:

project/
├── appPackage/
│   ├── manifest.json           # Manifesto das equipes (atualize a versão na implantação)
│   ├── declarativeAgent.json   # Configuração do agente + recursos
│   ├── mcpPlugin.json          # Definições de ferramentas com _meta
│   └── instruction.txt         # Instruções de comportamento do agente
├── mcp-server/
│   ├── src/index.ts            # Servidor com Streamable HTTP
│   ├── widgets/                # Estruturas de widgets + código-fonte do React
│   │   ├── my-widget.html      # Estrutura mínima retornada por resources/read
│   │   └── src/my-widget/      # Código-fonte do React + Fluent UI
│   ├── assets/                 # Pacotes de widgets compilados servidos em /assets
│   └── package.json
├── scripts/
│   ├── setup-devtunnel.sh      # Configuração do devtunnel para Linux/Mac
│   └── setup-devtunnel.ps1     # Configuração do devtunnel no Windows
└── env/.env.local              # MCP_SERVER_URL, MCP_SERVER_DOMAIN

Nota sobre a linguagem: isto mostra a estrutura de um projeto em TypeScript. Para Python, substitua mcp-server/src/index.ts pelo seu ponto de entrada em Python (por exemplo, server.py). Para C#, use uma estrutura padrão de projeto .NET. Os diretórios appPackage/, widgets/, scripts/ e env/ são independentes da linguagem.

Protocolo de Widgets do Copilot

Seu servidor MCP deve implementar esses requisitos de protocolo para renderizar widgets no Copilot Chat. Isso se aplica independentemente da linguagem:

  1. Transporte HTTP streamable — endpoint /mcp que processa POST, GET e DELETE com gerenciamento de sessão
  2. Cabeçalhos CORS — verificação de origem em /mcp permitindo m365.cloud.microsoft e *.m365.cloud.microsoft, com os cabeçalhos MCP obrigatórios
  3. Recursos do servidor — a resposta de inicialização deve declarar resources: {} e tools: {}
  4. Recursos do MCP — Registre widgets com URIs do tipo ui://widget/.html, tipo MIME text/html+skybridge e CSP _meta
  5. Formato de resposta da ferramenta — Retorne conteúdo (text) + structuredContent (dados do widget) + _meta com openai/outputTemplate
  6. Serviço de widgets — rota HTTP em /widgets/*.html para arquivos shell e /assets/* para pacotes compilados, ambos com CORS com verificação de origem

Para obter detalhes completos do protocolo, formatos JSON e uma lista de verificação de adaptação para servidores MCP existentes, consulte references/copilot-widget-protocol.md.

Implementação

Padrão de servidor MCP (Referência do TypeScript)

Consulte references/mcp-server-pattern.md para obter a implementação completa.

Para outras linguagens, implemente os requisitos descritos no Protocolo de Widgets do Copilot usando o SDK do MCP da sua linguagem. Consulte a tabela Referências do SDK por linguagem para obter os pacotes do SDK.

Requisitos essenciais:

  • Exponha o transporte HTTP Streamable em /mcp
  • Retorne structuredContent + _meta com openai/outputTemplate
  • Servir widgets por meio do endpoint HTTP
  • Lidar com CORS para solicitações entre origens
  • Lidar com dados parciais de maneira adequada (preencher com “Desconhecido” nos campos ausentes)

Formato de resposta da ferramenta:

return {
  content: [{ type: "text", text: "Summary" }],
  structuredContent: { /* dados do widget */ },
  _meta: { "openai/outputTemplate": "ui://widget/name.html", "openai/widgetAccessible": true }
};

Tratamento de dados parciais

Sempre normalize os dados de entrada para lidar com campos ausentes:

server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
  const args = request.params.arguments as { title?: string; items?: Partial[] };

  // Normalize os dados — preencha com “Desconhecido” nos campos ausentes
  const title = args.title || “Título padrão”;
  const items = (args.items || []).map(item => ({
    name: item.name || “Desconhecido”,
    value: item.value || "Desconhecido",
  }));

  // Cria o structuredContent para o widget
  const structuredContent = { title, items };
  // ...
});

Padrão de widget

Consulte references/widget-patterns.md para ver exemplos completos.

Requisitos básicos:

  • Use React + componentes do Fluent UI (@fluentui/react-components)
  • Certifique-se de que as dependências do pacote do widget incluam @fluentui/react-components, react e react-dom
  • Personalize o tema com o FluentProvider (webLightTheme/webDarkTheme) e tokens do Fluent
  • Acesse os dados por meio de hooks compartilhados (por exemplo, useOpenAiGlobal("toolOutput"))
  • Solução alternativa para depuração: dados simulados incorporados quando window.openai não estiver disponível
  • Lide com valores “Desconhecidos” de maneira adequada (por exemplo, oculte botões de ação)

Esquema do plug-in

Consulte references/plugin-schema.md para obter o formato do mcpPlugin.json.

Requisitos básicos:

  • Esquema v2.4 com o tempo de execução do RemoteMCPServer
  • Matriz `run_for_functions ` correspondente aos nomes das ferramentas
  • _meta nas definições das ferramentas para vinculação de widgets
  • inputSchema — torne as propriedades opcionais para maior flexibilidade e descreva os valores padrão nas descrições

Configuração do DevTunnels

Apenas para testes locais. Os DevTunnels destinam-se ao desenvolvimento e aos testes em sua máquina. Antes de compartilhar o agente de forma mais ampla, implante tanto o servidor MCP quanto os recursos do widget em um ambiente hospedado (por exemplo, Azure App Service, Azure Static Web Apps ou outro provedor de hospedagem) e atualize as URLs do manifesto do agente de acordo com isso.

Os DevTunnels expõem seu servidor MCP localhost ao M365 Copilot usando túneis nomeados para URLs estáveis. Consulte references/devtunnels.md para obter scripts de configuração, referência de comandos e solução de problemas.

O script de configuração (npm run tunnel / npm run tunnel:win):

  1. Cria um túnel nomeado na primeira execução (ou reutiliza o já existente)
  2. Inicia a hospedagem do túnel na porta configurada
  3. Atualiza o arquivo env/.env.local com MCP_SERVER_URL e MCP_SERVER_DOMAIN (apenas na primeira execução)
  4. Continua hospedando o túnel

Início rápido

Terminal 1 – Inicie o servidor MCP:

cd mcp-server
npm install
npm run dev

Terminal 2 — Inicie o DevTunnel:

npm run tunnel
# Ou no Windows:
npm run tunnel:win

Na primeira execução, provisionar o agente assim que o túnel estiver ativo (consulte a regra PROVISIONAMENTO DO AGENTE). Nas execuções subsequentes, a URL do túnel permanece estável — não é necessário re-provisionar, a menos que o manifesto do agente seja alterado.

Fluxo de trabalho de desenvolvimento

  1. Inicie o servidor MCP (modo de desenvolvimento com recarga dinâmica):

    • 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. Inicie o devtunnel (cria um túnel com nome na primeira execução e o reutiliza nas execuções seguintes):

    npm run tunnel
    
  3. Provisionamento + teste — consulte a regra PROVISIONAMENTO DE AGENTE para saber quando isso é necessário; atualize a versão no manifest.json se o Copilot não refletir as alterações

Melhores práticas

Consulte references/best-practices.md para obter orientações detalhadas.

Pontos-chave:

  1. Ferramentas de renderização: Aceitem dados como entrada, não os busquem internamente
  2. Instruções: Instrua o agente a usar os recursos PRIMEIRO e, em seguida, passe os dados para as ferramentas do MCP
  3. Temas: Use o FluentProvider + tokens do Fluent para suporte a temas escuros/claros
  4. Modo de depuração: inclua dados de fallback para testes locais de widgets
  5. Dados parciais: Lide com campos ausentes usando valores padrão “Desconhecido”
  6. Botões de ação: Oculte os botões de e-mail/chat quando os dados forem “Desconhecidos”
  7. Atualização de versão: Atualize a versão do manifesto quando as alterações não forem refletidas no Copilot
Ver no 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 os arquivos

0 arquivos

Instalar ui-widget-developer

Baixe e descompacte os arquivos de habilidades no diretório .claude/skills/.

Baixar ZIP

Clone o repositório e copie os arquivos da habilidade para o seu projeto.

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
Configuração rápida: Copie a pasta da habilidade para .claude/skills/ O Claude detectará e utilizará automaticamente a habilidade
Repositório microsoft/skills

Habilidades relacionadas

github-code-search
Tempo atualizado 29 de Junho de 2026
drizzle-orm
Tempo atualizado 29 de Junho de 2026
clickhouse-io
Tempo atualizado 29 de Junho de 2026
prisma-client-api
Tempo atualizado 29 de Junho de 2026
OR