Phase 13 - Lesson 14
MCP Apps — Recursos de UI Interativos via `ui://`
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
A saída de ferramenta apenas em texto limita o que os agentes podem exibir. O MCP Apps (SEP-1724, oficial em 26/01/2026) permite que uma ferramenta retorne HTML interativo em sandbox renderizado inline no Claude Desktop, ChatGPT, Cursor, Goose e VS Code. Painéis, formulários, mapas, cenas 3D, tudo através de uma única extensão. Esta lição percorre o esquema de recurso
ui://, o MIMEtext/html;profile=mcp-app, o protocolo postMessage do sandbox de iframe e a superfície de segurança decorrente de permitir que um servidor renderize HTML.
Tipo: Build Idiomas: Python (stdlib, emissor de recursos de UI), HTML (aplicativo de exemplo) Pré-requisitos: Fase 13 · 07 (servidor MCP), Fase 13 · 10 (recursos) Tempo: ~75 minutos
Objetivos de Aprendizado
- Retornar um recurso
ui://a partir de uma chamada de ferramenta e definir o MIME e os metadados corretos. - Declarar a UI associada de uma ferramenta com
_meta.ui.resourceUri,_meta.ui.cspe_meta.ui.permissions. - Implementar o postMessage JSON-RPC do sandbox de iframe para comunicação entre UI e host.
- Aplicar padrões de CSP e política de permissões (permissions-policy) que defendam contra ataques originados da UI.
O Problema
Uma ferramenta visualize_timeline da era de 2025 pode retornar "Aqui estão 14 notas organizadas cronologicamente: ...". Isso é um parágrafo. Na verdade, os usuários querem a linha do tempo interativa. Antes do MCP Apps, as opções eram: APIs de widgets específicas do cliente (Claude Artifacts, HTML de GPTs personalizados da OpenAI) ou nenhuma UI de forma alguma.
O MCP Apps (SEP-1724, lançado em 26 de janeiro de 2026) padroniza o contrato. O resultado de uma ferramenta contém um resource cuja URI é ui://... e cujo MIME é text/html;profile=mcp-app. O host o renderiza em um iframe em sandbox com uma CSP limitada e sem acesso à rede, a menos que explicitamente concedido. A UI dentro do iframe envia mensagens para o host por meio de um pequeno dialeto de postMessage JSON-RPC.
Cada cliente compatível (Claude Desktop, ChatGPT, Goose, VS Code) renderiza o mesmo recurso ui:// da mesma forma. Um servidor, um pacote HTML, UI universal.
O Conceito
O esquema de recurso ui://
Uma ferramenta retorna:
{
"content": [
{"type": "text", "text": "Here is your notes timeline:"},
{"type": "ui_resource", "uri": "ui://notes/timeline"}
],
"_meta": {
"ui": {
"resourceUri": "ui://notes/timeline",
"csp": {
"defaultSrc": "'self'",
"scriptSrc": "'self' 'unsafe-inline'",
"connectSrc": "'self'"
},
"permissions": []
}
}
}
O host então chama resources/read na URI ui://notes/timeline e obtém de volta:
{
"contents": [{
"uri": "ui://notes/timeline",
"mimeType": "text/html;profile=mcp-app",
"text": "<!doctype html>..."
}]
}
Sandbox de iframe
O host renderiza o HTML dentro de um <iframe> em sandbox com:
sandbox="allow-scripts allow-same-origin"(ou mais restrito conforme declaração do servidor)- CSP declarada pelo servidor aplicada através de cabeçalhos de resposta.
- Sem cookies, sem localStorage da origem do host.
- Acesso à rede limitado a
connectSrcna CSP.
Protocolo postMessage
O iframe comunica-se com o host através de window.postMessage. Um dialeto simplificado de JSON-RPC 2.0:
Sempre fixe (pin) targetOrigin na origem exata do par e, no lado receptor, valide event.origin contra uma lista de permissões antes de processar qualquer payload. Nunca use "*" em nenhum dos lados deste canal — o corpo transporta chamadas de ferramentas e leituras de recursos.
// iframe to host (pin to host origin)
window.parent.postMessage({
jsonrpc: "2.0",
id: 1,
method: "host.callTool",
params: { name: "notes_update", arguments: { id: "note-14", title: "..." } }
}, "https://host.example.com");
// host to iframe (pin to iframe origin)
iframe.contentWindow.postMessage({
jsonrpc: "2.0",
id: 1,
result: { content: [...] }
}, "https://iframe.example.com");
// receiver on both sides
window.addEventListener("message", (event) => {
if (event.origin !== "https://expected-peer.example.com") return;
// safe to process event.data
});
Métodos disponíveis no lado do host que a UI pode chamar:
host.callTool(name, arguments)— invoca uma ferramenta do servidor.host.readResource(uri)— lê um recurso MCP.host.getPrompt(name, arguments)— busca um modelo de prompt.host.close()— fecha a UI.
Cada chamada ainda passa pelo protocolo MCP e herda as permissões do servidor.
Permissões
A lista _meta.ui.permissions solicita recursos adicionais:
camera— acesso à câmera do usuário (usado para UIs de digitalização de documentos).microphone— entrada de voz.geolocation— localização.network:*— acesso à rede mais amplo do que o permitido apenas porconnectSrc.
Cada permissão gera um aviso que o usuário vê antes de a UI ser renderizada.
Riscos de segurança
HTML em um iframe ainda é HTML. Nova superfície de ataque:
- Injeção de prompt via UI. Uma UI de servidor maliciosa pode exibir um texto que pareça uma mensagem do sistema e enganar o usuário. A renderização do host deve distinguir visualmente a UI do servidor da UI do host.
- Exfiltração via
connectSrc. Se a CSP permitirconnect-src: *, a UI poderá enviar dados para qualquer lugar. O padrão deve ser estrito. - Clickjacking. A UI se sobrepõe ao chrome do host. Os hosts devem impedir a manipulação de z-index e aplicar regras de opacidade.
- Roubo de foco. A UI assume o foco do teclado e captura a próxima mensagem. Os hosts devem interceptar isso.
A Fase 13 · 15 aborda estes pontos em detalhes como parte da segurança do MCP; esta lição os introduz.
Handshake de ui/initialize
Depois de carregar o iframe, ele envia ui/initialize por postMessage:
{"jsonrpc": "2.0", "id": 0, "method": "ui/initialize",
"params": {"theme": "dark", "locale": "en-US", "sessionId": "..."}}
O host responde com as capacidades e um token de sessão. A UI usa o token de sessão em cada chamada subsequente ao host.
Primitivas do SDK AppRenderer / AppFrame
O SDK ext-apps expõe duas primitivas utilitárias:
AppRenderer(lado do servidor) — empacota um componente React / Vue / Solid e emite um recursoui://com o MIME e metadados corretos.AppFrame(lado do cliente) — recebe o recurso, monta o iframe e media o postMessage.
Você pode usar estas primitivas ou criar manualmente o HTML e o JSON-RPC.
Status do ecossistema
O MCP Apps foi lançado em 26 de janeiro de 2026. Suporte do cliente a partir de abril de 2026:
- Claude Desktop. Suporte completo desde janeiro de 2026.
- ChatGPT. Suporte completo através do SDK do Apps (mesmo protocolo MCP Apps subjacente).
- Cursor. Beta; habilite através das configurações.
- VS Code. Apenas compilações do Insider (Insider builds).
- Goose. Suporte completo.
- Zed, Windsurf. Planejado no roadmap.
Servidores em produção: painéis, visualizações de mapa, tabelas de dados, construtores de gráficos, pré-visualizações de IDE em sandbox.
Use
O code/main.py estende o servidor de notas com uma ferramenta visualize_timeline que retorna um recurso ui://notes/timeline, além de um tratador para resources/read nessa URI que retorna um pacote HTML pequeno, mas completo, com uma linha do tempo em SVG. O HTML usa templates da stdlib — sem sistema de build. O postMessage é esboçado em comentários JS, já que a stdlib não pode controlar um navegador.
O que observar:
_meta.uina resposta da ferramenta carrega resourceUri, CSP, permissões.- O HTML é renderizado sem acesso à rede; todos os dados são inseridos inline.
- O JS chama
host.callToolviawindow.parent.postMessage(documentado, mas inerte nesta demonstração com stdlib).
Entregue
Esta lição produz outputs/skill-mcp-apps-spec.md. Dada uma ferramenta que se beneficiaria de uma UI interativa, a habilidade produz o contrato completo do MCP Apps: URI ui://, CSP, permissões, pontos de entrada postMessage e uma lista de verificação de segurança.
Exercícios
- Execute
code/main.pye inspecione o HTML emitido. Abra o HTML diretamente em um navegador; verifique se o SVG é renderizado. Em seguida, esboce o contrato postMessage que a UI usaria para chamarhost.callTool("notes_update", ...). - Restrinja a CSP: remova
'unsafe-inline'e use uma política de script baseada em nonce. O que muda no código de geração do HTML? - Adicione um segundo recurso de UI
ui://notes/editorcom um formulário para editar uma nota diretamente no local. Quando o usuário enviar, o iframe chamaráhost.callTool("notes_update", ...). - Faça uma auditoria na superfície de ataque da UI. Onde um servidor malicioso poderia injetar conteúdo? Contra o que o sandbox de iframe defende e contra o que ele não defende?
- Leia a especificação SEP-1724 e identifique uma capacidade no SDK do MCP Apps que esta implementação simples não utiliza. (Dica: sincronização de estado no nível do componente.)
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| MCP Apps | "Recursos de UI interativos" | Extensão SEP-1724 lançada em 26/01/2026 |
ui:// |
"Esquema de URI do App" | Esquema de recurso para pacotes de UI |
text/html;profile=mcp-app |
"O MIME" | Tipo de conteúdo para o HTML de MCP Apps |
| Sandbox de iframe | "Contêiner de renderização" | Sandbox do navegador para a UI com CSP e permissões |
| postMessage JSON-RPC | "Conexão UI-para-host" | Pequeno dialeto JSON-RPC sobre postMessage para chamadas ao host |
_meta.ui |
"Associação Tool-UI" | Metadados que vinculam o resultado de uma ferramenta a um recurso de UI |
| CSP | "Política de Segurança de Conteúdo" | Declara origens permitidas para scripts, rede e estilos |
| AppRenderer | "Primitiva do SDK do servidor" | Converte um componente de framework em um recurso ui:// |
| AppFrame | "Primitiva do SDK do cliente" | Auxiliar de montagem do iframe que media o postMessage |
ui/initialize |
"Aperto de mão (Handshake)" | Primeiro postMessage da UI para o host |
Leituras Adicionais
- MCP ext-apps — GitHub — implementação de referência e SDK
- MCP Apps specification 2026-01-26 — documento de especificação formal
- MCP — Apps extension overview — documentação de alto nível
- MCP blog — MCP Apps launch — post de lançamento de janeiro de 2026
- MCP Apps API reference — referência de API do SDK estilo JSDoc |