MCP Apps: UI interattive dentro una tool call
Per tutto il suo primo anno MCP è stato un protocollo testuale: una tool call restituiva JSON o Markdown e il client renderizzava come voleva. MCP Apps — la prima estensione ufficiale, finalizzata il 26 gennaio 2026 e integrata nella core spec del 2026-07-28 — aggiunge un canale UI. Un server può ora inviare un blocco di HTML, il client lo renderizza in un iframe sandboxed e l'iframe risponde tramite JSON-RPC 2.0 su postMessage. Ogni scrittura continua a passare per lo stesso audit path di qualsiasi altra tool call. Pensa a un report interattivo + conferma-prima-di-eseguire, non a "esegui una webapp completa dentro Claude."
- Cos'è davvero MCP Apps — e le quattro cose che deliberatamente NON è
- La forma sul filo: negoziazione di capability, risorse ui://, il link _meta.ui del tool e il ponte postMessage
- Il modello di sicurezza — sandbox, CSP, Permission Policy, approvazione dell'host — e dove ti lascia comunque esposto
- Quando ricorrere a un App invece che a un semplice risultato di tool (pattern rari ma che valgono la pena)
- Come si compone con il resto della spec stateless 2026-07-28 che già parli
La versione in un paragrafo
Il server dichiara una o più risorse UI su URI ui://<server>/<name> con MIME type text/html;profile=mcp-app. Un tool segnala "ho una UI" mettendo _meta.ui.resourceUri nel suo schema. Su una tools/call il server può restituire il solito testo/dato più un riferimento alla risorsa UI; il client scarica l'HTML con resources/read, lo mette in un <iframe> sandboxed, e ora iframe e host chiacchierano via postMessage usando JSON-RPC 2.0. Le tool call originate dalla UI richiedono comunque la stessa approvazione utente di qualsiasi altra tool call — l'App può renderizzare, può proporre, non può eseguire in silenzio.
Cosa MCP Apps NON è
- Non è un runtime per webapp complete. L'iframe di default non ha rete (`connect-src 'none'`), niente navigazione top-level, niente script di terze parti. Se la tua idea richiede di caricare React da una CDN e chiamare la tua API, non è questo lo strumento.
- Non è un modo per bypassare l'approvazione delle tool call. Le azioni UI che modificano qualcosa viaggiano comunque come normali tool call JSON-RPC che l'host può loggare, throttlare e per cui può richiedere il consenso esplicito dell'utente.
- Non è sanificazione. La sandbox limita ciò che la UI può FARE, non ciò a cui può indurre l'utente a digitare. Tratta ogni App come codice di terze parti — metti in allowlist quali server possono renderizzare UI, punto.
- Non è persistente. Non c'è sessione; quando la tool call finisce, l'iframe scompare. Ogni stato che ti serve tra chiamate vive in un handle server-side che restituisci, esattamente come nel resto della spec stateless.
Le quattro parti in movimento
- Il client dichiara l'estensione nel suo _meta.capabilities per-richiesta sotto il namespace reverse-DNS io.modelcontextprotocol/ui, elencando i MIME type che sa renderizzare. Un server che non vede questa capability salta il canale UI e restituisce risultati semplici — tutta l'estensione è opt-in su entrambi i lati.
- Il server espone una o più risorse su URI ui://<server>/<name> con MIME text/html;profile=mcp-app. Le risorse sono pre-dichiarate così che l'host possa prefetcharle, calcolarne l'hash e ispezionarle prima che qualsiasi tool venga mai eseguito. Il contenuto viene servito tramite la normale chiamata resources/read — nessun endpoint nuovo.
- Ogni tool che vuole renderizzare una UI aggancia _meta.ui = { resourceUri: 'ui://…', visibility: ['model' | 'app'] } al suo schema. visibility controlla se il modello può 'vedere' il riferimento alla UI (utile per modelli che ragionano sulle affordance UI) o se è solo lato app.
- Dentro l'iframe sandboxed, il codice UI parla JSON-RPC 2.0 verso window.parent via postMessage. Il primo messaggio è tipicamente ui/initialize con gli argomenti del tool e ogni handle che il server ha passato. Da lì la UI può richiedere altre risorse, chiamare tool (con approvazione utente) e inviare notifiche indietro.
Forme sul filo che scriverai davvero
Dichiarazione di capability su una richiesta client:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": { "name": "get_weather", "arguments": { "location": "Milan" } },
"_meta": {
"capabilities": {
"extensions": {
"io.modelcontextprotocol/ui": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
}
}
}
Lo schema del tool, lato server:
{
"name": "get_weather",
"description": "Get current weather and a 7-day forecast for a location.",
"inputSchema": {
"type": "object",
"properties": { "location": { "type": "string" } },
"required": ["location"]
},
"_meta": {
"ui": {
"resourceUri": "ui://weather-server/dashboard-template",
"visibility": ["model", "app"]
}
}
}
La risorsa UI dichiarata accanto:
{
"uri": "ui://weather-server/dashboard-template",
"mimeType": "text/html;profile=mcp-app",
"_meta": {
"ui": {
"connectDomains": [],
"permissions": []
}
}
}
E il primo messaggio che l'iframe rimanda indietro:
UI → host: JSON-RPC 2.0 su postMessage
window.parent.postMessage(
{
jsonrpc: "2.0",
id: 1,
method: "ui/initialize",
params: {
toolName: "get_weather",
toolArguments: { location: "Milan" },
toolResult: /* whatever the server returned alongside the UI */
}
},
"*"
);Tutto ciò che segue — chiedere all'host di chiamare un altro tool, sottoscriversi a una risorsa, inviare un evento lato UI — è altro JSON-RPC 2.0 sullo stesso canale, strutturato in modo identico al protocollo sul filo che già parli lato server. È tutto il punto: gli sviluppatori UI possono usare l'@modelcontextprotocol/sdk standard invece di imparare uno shim custom.
Modello di sicurezza in una schermata
- Sandbox: il contenuto è renderizzato in un <iframe> sandboxed. Default: niente navigazione top-level, niente form verso origin arbitrarie, niente popup, niente plugin.
- CSP di default: connect-src 'none'. La UI non può fetch() da nessuna parte. Per consentire origin specifiche il server le dichiara nel _meta.ui.connectDomains della risorsa e l'host costruisce l'header CSP da quella lista.
- Permission Policy: il _meta.ui.permissions della risorsa mappa nell'attributo allow dell'iframe — camera, microfono, geolocalizzazione, clipboard-write. Nulla è concesso implicitamente.
- Ogni scrittura originata dalla UI è una normale tool call. L'host la valida, può richiedere approvazione utente e la logga nello stesso stream di audit di una chiamata originata dal modello.
- I template sono prefetchabili e hashabili. Un host che si aggancia a un hash specifico intercetta un attacco di swap-sotto-di-te; un host che non lo fa si prende quello che il server serve quel giorno.
Quando ricorrere davvero a un App
L'asticella è alta di proposito — ogni App è superficie client per te e superficie d'attacco per l'utente. Ricorri a uno solo quando una UI batte un turno di chat con un margine chiaro:
- Conferme su data-grid. "Ecco 47 righe che sto per aggiornare — deseleziona quelle che non vuoi." Un rendering in chat è o enorme o disonesto; una griglia con checkbox è onesta e veloce.
- Approvazione guidata da grafici. "Ecco il query plan / la proiezione di costo / il trace waterfall. Approva o rifiuta." I grafici costano poco in HTML e sono terribili come ASCII.
- Picker strutturati dove la forma dell'input non è testo. Date-range con mini calendario, tree-select su un filesystem o un organigramma, map picker con bounding box.
- Editor in-place per il piccolo passaggio in cui una vista diff + accetta/rifiuta batte il rigenerare tutta la risposta.
Se la tua idea è "fatemi embeddare un'intera dashboard", non è questo il posto — metti un link. Se la tua idea è "fatemi eseguire codice utente arbitrario nella sandbox", questo posto non fa proprio per te.
Il contratto di compatibilità con 2025-11-25
MCP Apps cavalca il framework di estensioni introdotto nella core spec 2026-07-28, ma l'estensione in sé è Final dal 2026-01-26. Implicazione pratica: un client che parla solo la vecchia revisione 2025-11-25 più questa estensione può già renderizzare Apps; un client stateless 2026-07-28 la raccoglie come una entry nella sua mappa di capability per-richiesta. Poiché l'estensione è opt-in su entrambi i lati, un server che l'aggiunge non rompe mai un client vecchio — semplicemente salta il ramo UI. Questa è la forma che avrà ogni futura estensione MCP, quindi capire questo handshake ammortizza il debito per quelle che verranno.
Dove MCP Apps si colloca sulla mappa di AILmanac
- MCP 2026-07-28: la spec stateless — il protocollo core su cui viaggia Apps; in particolare la sezione sul framework di Extensions (SEP-2133).
- MCP & connettersi ai tool — il connettore lato API. È il client che renderizzerà l'iframe; oggi il connettore astrae il trasporto, quindi Apps ci passa attraverso senza modifiche.
- MCP in Claude Code — dove sta andando il supporto MCP di Claude Code; il rendering UI è una capability del client, non del server, quindi è la superficie che decide quali Apps verranno mai renderizzate per te.
- Mettere in sicurezza i server MCP — il pattern di hardening per i server dall'altra parte di quell'iframe.
Controllo veloce
Check yourself
0/4Vocabolario
Fonti e approfondimenti
- SEP-1865: MCP Apps — Interactive User Interfaces for MCP — la specifica stessa.
- Repository
ext-apps— il testo congelato della spec 2026-01-26: forma delle capability, default CSP, semantica postMessage. - MCP Apps: Extending servers with interactive user interfaces — il post di annuncio che ripercorre il razionale di design.
- The 2026-07-28 MCP Specification Release Candidate — la release core insieme a cui Apps viene distribuita; la sezione sul framework Extensions spiega il pattern di negoziazione che tutte le estensioni seguiranno.
- Anthropic and OpenAI Join Forces to Standardize Interactive AI Interfaces with MCP Apps — contesto sulla collaborazione multi-vendor dietro SEP-1865.
Prossimi passi
- MCP 2026-07-28: la spec stateless — il protocollo core che ogni host e server di MCP Apps parla comunque.
- Mettere in sicurezza i server MCP — l'hardening lato server che vuoi in atto prima di accettare tool call originate dalla UI.
- MCP & connettersi ai tool — come il connettore lato API raggiunge davvero il server la cui App stai renderizzando.