Phase 13 - Lesson 14
MCP Apps — Recursos de UI Interactivos vía `ui://`
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
La salida de la herramienta de solo texto limita lo que los agentes pueden mostrar. MCP Apps (SEP-1724, oficial el 26/01/2026) permite que una herramienta devuelva HTML interactivo aislado (sandboxed) renderizado en línea (inline) en Claude Desktop, ChatGPT, Cursor, Goose y VS Code. Tableros, formularios, mapas, escenas 3D, todo a través de una sola extensión. Esta lección recorre el esquema de recursos
ui://, el MIMEtext/html;profile=mcp-app, el protocolo postMessage de sandbox de iframe y la superficie de seguridad que conlleva permitir que un servidor renderice HTML.
Tipo: Build Idiomas: Python (stdlib, emisor de recursos de UI), HTML (aplicación de ejemplo) Prerrequisitos: Fase 13 · 07 (servidor MCP), Fase 13 · 10 (recursos) Tiempo: ~75 minutos
Objetivos de Aprendizaje
- Devolver un recurso
ui://desde una llamada a una herramienta y establecer el MIME y los metadatos correctos. - Declarar la interfaz de usuario asociada de una herramienta con
_meta.ui.resourceUri,_meta.ui.cspy_meta.ui.permissions. - Implementar el postMessage JSON-RPC de sandbox de iframe para la comunicación entre la UI y el host.
- Aplicar configuraciones predeterminadas de CSP y políticas de permisos (permissions-policy) que defiendan contra ataques originados en la interfaz de usuario.
El Problema
Una herramienta visualize_timeline de la era de 2025 puede devolver "Aquí hay 14 notas organizadas cronológicamente: ...". Eso es un párrafo. En realidad, los usuarios quieren la línea de tiempo interactiva. Antes de MCP Apps, las opciones eran: APIs de widgets específicas del cliente (Claude artifacts, OpenAI Custom GPT HTML) o ninguna interfaz de usuario en absoluto.
MCP Apps (SEP-1724, lanzado el 26 de enero de 2026) estandariza el contrato. El resultado de una herramienta contiene un resource cuya URI es ui://... e cuyo MIME es text/html;profile=mcp-app. El host lo renderiza en un iframe aislado con una CSP limitada y sin acceso a la red a menos que se otorgue explícitamente. La UI dentro del iframe envía mensajes al host a través de un pequeño dialecto de postMessage JSON-RPC.
Cada cliente compatible (Claude Desktop, ChatGPT, Goose, VS Code) renderiza el mismo recurso ui:// de la misma manera. Un servidor, un paquete HTML, UI universal.
El Concepto
El esquema de recursos ui://
Una herramienta devuelve:
{
"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": []
}
}
}
El host luego llama a resources/read en la URI ui://notes/timeline y recibe:
{
"contents": [{
"uri": "ui://notes/timeline",
"mimeType": "text/html;profile=mcp-app",
"text": "<!doctype html>..."
}]
}
Sandbox de iframe
El host renderiza el HTML dentro de un <iframe> aislado (sandboxed) con:
sandbox="allow-scripts allow-same-origin"(o más estricto según la declaración del servidor)- CSP declarada por el servidor aplicada a través de cabeceras de respuesta.
- Sin cookies, sin localStorage del origen del host.
- Acceso a la red limitado a
connectSrcen la CSP.
Protocolo postMessage
El iframe se comunica con el host a través de window.postMessage. Un pequeño dialecto de JSON-RPC 2.0:
Fije siempre targetOrigin al origen exacto del par y, en el lado receptor, valide event.origin contra una lista de permitidos antes de procesar cualquier carga útil. Nunca use "*" para ningún lado de este canal: el cuerpo transporta llamadas a herramientas y lecturas 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 disponibles en el lado del host que la UI puede llamar:
host.callTool(name, arguments)— invoca una herramienta del servidor.host.readResource(uri)— lee un recurso MCP.host.getPrompt(name, arguments)— obtiene una plantilla de prompt.host.close()— cierra la UI.
Cada llamada sigue pasando por el protocolo MCP y hereda las del servidor.
Permisos
La lista _meta.ui.permissions solicita capacidades adicionales:
camera— acceso a la cámara del usuario (usado para interfaces de escaneo de documentos).microphone— entrada de voz.geolocation— ubicación.network:*— acceso a la red más amplio de lo que permiteconnectSrcpor sí solo.
Cada permiso es una solicitud que el usuario ve antes de que se renderice la interfaz de usuario.
Riesgos de seguridad
HTML en un iframe sigue siendo HTML. Nueva superficie de ataque:
- Inyección de prompts a través de la UI. Una UI de servidor maliciosa puede mostrar texto que parezca un mensaje del sistema y engañar al usuario. La renderización del host debe distinguir visiblemente la UI del servidor de la UI del host.
- Exfiltración a través de
connectSrc. Si la CSP permiteconnect-src: *, la UI puede enviar datos a cualquier lugar. El valor predeterminado debe ser estricto. - Clickjacking. La UI se superpone al cromo del host. Los hosts deben evitar la manipulación de z-index y hacer cumplir las reglas de opacidad.
- Robo de foco. La UI toma el foco del teclado y captura el siguiente mensaje. Los hosts deben interceptarlo.
La Fase 13 · 15 cubre esto en profundidad como parte de la seguridad de MCP; esta lección los presenta.
Handshake de ui/initialize
Después de que se carga el iframe, envía ui/initialize a través de postMessage:
{"jsonrpc": "2.0", "id": 0, "method": "ui/initialize",
"params": {"theme": "dark", "locale": "en-US", "sessionId": "..."}}
El host responde con las capacidades y un token de sesión. La UI utiliza el token de sesión en cada llamada posterior al host.
Primitivas del SDK AppRenderer / AppFrame
El SDK ext-apps expone dos primitivas de conveniencia:
AppRenderer(lado del servidor) — envuelve un componente React / Vue / Solid y emite un recursoui://con el MIME y metadatos correctos.AppFrame(lado del cliente) — recibe el recurso, monta el iframe y media postMessage.
Puede utilizar estos o crear el HTML y el JSON-RPC manualmente.
Estado del ecosistema
MCP Apps se lanzó el 26 de enero de 2026. Soporte de clientes a partir de abril de 2026:
- Claude Desktop. Soporte completo desde enero de 2026.
- ChatGPT. Soporte completo a través del Apps SDK (mismo protocolo MCP Apps subyacente).
- Cursor. Beta; habilitar a través de la configuración.
- VS Code. Solo versiones Insider.
- Goose. Soporte completo.
- Zed, Windsurf. En hoja de ruta.
Servidores en producción: tableros, visualizaciones de mapas, tablas de datos, creadores de gráficos, vistas previas de IDE en sandbox.
Úsalo
code/main.py extiende el servidor de notas con una herramienta visualize_timeline que devuelve un recurso ui://notes/timeline, además de un controlador para resources/read en esa URI que devuelve un paquete HTML pequeño pero completo con una línea de tiempo SVG. El HTML es stdlib-templated, sin sistema de compilación. postMessage está esbozado en comentarios de JS ya que la stdlib no puede controlar un navegador.
Qué observar:
_meta.uien la respuesta de la herramienta contiene resourceUri, CSP, permisos.- El HTML se renderiza sin acceso a la red; todos los datos están integrados en línea.
- JS llama a
host.callToola través dewindow.parent.postMessage(documentado pero inerte en esta demostración de stdlib).
Entrégalo
Esta lección produce outputs/skill-mcp-apps-spec.md. Dada una herramienta que se beneficiaría de una UI interactiva, la habilidad produce el contrato completo de MCP Apps: URI ui://, CSP, permisos, puntos de entrada postMessage y una lista de verificación de seguridad.
Ejercicios
- Ejecuta
code/main.pye inspecciona el HTML emitido. Abre el HTML directamente en un navegador; verifica que se renderice el SVG. Luego, esboza el contrato postMessage que usaría la interfaz de usuario para llamar ahost.callTool("notes_update", ...). - Ajusta la CSP: elimina
'unsafe-inline'y usa una política de scripts basada en nonce. ¿Qué cambia en el código de generación de HTML? - Agrega un segundo recurso de UI
ui://notes/editorcon un formulario para editar una nota en su lugar. Cuando el usuario lo envía, el iframe llama ahost.callTool("notes_update", ...). - Audita la superficie de ataque de la interfaz de usuario. ¿Dónde podría inyectar contenido un servidor malicioso? ¿Contra qué defiende el sandbox de iframe y contra qué no?
- Lee la especificación SEP-1724 e identifica una capacidad en el SDK de MCP Apps que esta implementación de juguete no utiliza. (Pista: sincronización de estado a nivel de componente).
Términos Clave
| Término | Lo que la gente dice | Lo que realmente significa |
|---|---|---|
| MCP Apps | "Recursos de UI interactivos" | Extensión SEP-1724 lanzada el 26/01/2026 |
ui:// |
"Esquema de URI de la aplicación" | Esquema de recursos para paquetes de interfaz de usuario |
text/html;profile=mcp-app |
"El MIME" | Tipo de contenido (Content-type) para el HTML de MCP Apps |
| Sandbox de iframe | "Contenedor de renderizado" | Aislamiento del navegador de la interfaz de usuario con CSP y permisos |
| postMessage JSON-RPC | "Cable de UI a host" | Pequeño dialecto de JSON-RPC sobre postMessage para llamadas al host |
_meta.ui |
"Vinculación Tool-UI" | Metadatos que vinculan el resultado de una herramienta a un recurso de interfaz de usuario |
| CSP | "Política de Seguridad de Contenido" | Declara las fuentes permitidas para scripts, red y estilos |
| AppRenderer | "Primitiva del SDK del servidor" | Convierte un componente del framework en un recurso ui:// |
| AppFrame | "Primitiva del SDK del cliente" | Asistente de montaje de iframe que media postMessage |
ui/initialize |
"Apretón de manos (Handshake)" | Primer postMessage de la interfaz de usuario al host |
Lectura Adicional
- MCP ext-apps — GitHub — implementación de referencia y SDK
- MCP Apps specification 2026-01-26 — documento de especificación formal
- MCP — Apps extension overview — documentación de alto nivel
- MCP blog — MCP Apps launch — publicación de lanzamiento de enero de 2026
- MCP Apps API reference — referencia de la API del SDK estilo JSDoc |