La vue embarquée
show_schedule affiche un planning vivant dans la conversation : pas une image ni un export, mais le Gantt de PalmaPlan, peint par le même code que l'écran. Il est éditable si votre rôle le permet et se tient à jour quand quelqu'un d'autre écrit.
show_schedule { schedule: "Migration S/4HANA", view: "gantt", critical_path: true }
Options :
view:gantt(par défaut),kanbanouagenda;filter: la syntaxe du volet Filtre de l'écran ;zoom;- une fenêtre de dates :
frometto; - le panneau :
grid,timeline, ou les deux ; critical_pathetreference(la version de référence) ;editable: falsepour une vue qui lit seulement.
Le rôle réel de l'utilisateur plafonne toujours : editable ne donne jamais plus qu'un rôle n'accorde.
Ce qu'est la vue : une MCP App native
PalmaPlan sert une MCP App : la ressource ui://palmaplan/planning, de type text/html;profile=mcp-app. C'est une page unique : le HTML, le JavaScript et le CSS sont en ligne, et rien ne se charge d'ailleurs. show_schedule, what_if et diff_versions la désignent (_meta.ui.resourceUri). Un hôte qui rend les MCP Apps l'affiche dans la conversation, dans un cadre isolé. Elle s'ouvre en ligne, puis en plein écran sur demande, aux couleurs du thème de l'hôte (clair ou sombre), dans sa langue et son fuseau.
La vue ne parle qu'à l'hôte. Sa CSP n'ouvre aucun domaine : ni connectDomains, ni frameDomains, ni resourceDomains. Elle lit et écrit en appelant les tools du serveur (tools/call), que l'hôte relaie sous le jeton ou l'OAuth du connecteur. La vue n'a donc ni jeton à elle, ni cadre imbriqué, ni accès direct à PalmaPlan. Trois tools lui sont réservés (_meta.ui.visibility: ["app"]) ; l'hôte les cache au modèle :
| Tool | Ce qu'il rend |
|---|---|
get_schedule_page | le planning par pages, chaque réponse sous 120 000 caractères (le plafond d'un résultat d'outil chez Claude est d'environ 150 000) |
get_changes_since | les entrées du journal après un rang, pour rester à jour |
get_schedule_absences | l'agenda : les noms des membres et les dates de leurs absences, jamais le motif ni l'e-mail |
Les trois vues, et l'avant/après
- Le Gantt : en ligne, la fenêtre utile du planning et ses lignes occupées ; en plein écran, le planning entier, groupes compris.
- Le kanban (
view: "kanban") : les actions des blocs, en trois colonnes (à faire, en cours, faites). Chaque carte nomme son bloc. - L'agenda (
view: "agenda") : les absences des membres, jour par jour, sans motif. - L'avant/après :
what_if(une proposition) etdiff_versions(une comparaison) s'affichent comme un Gantt où l'état proposé est peint par-dessus l'ombre de l'état courant. Viennent ensuite la liste des changements (le bloc, l'ancienne et la nouvelle date), la fin du projet et les nouveaux risques d'absence. Une proposition porte deux boutons, « Accepter » et « Refuser » ; une comparaison se lit seulement.
« Accepter » applique exactement ce que l'avant/après montre, et rien d'autre.
- Le bouton appelle
apply_patchavec les commandes de la simulation et le rang auquel elle a été faite (expected_rank). L'API n'écrit que si le planning est toujours à ce rang, et elle en juge dans la transaction même qui écrit. Si quelqu'un a écrit entre-temps, rien n'est écrit et la vue dit : « La proposition est périmée — demandez-en une nouvelle. » - Si la simulation porte des changements que la liste et le Gantt ne savent pas montrer (des réglages, un membre de ligne, des notes…), « Accepter » n'est pas proposé. Le texte de
what_ifet la vue nomment ces « changements non représentés ». - La vue n'accepte qu'
apply_patch, sur le planning affiché.
Les gestes
Dans le Gantt, quand le rôle le permet :
- choisir un bloc ; Maj+→ le décale d'une semaine ouvrée, → et ← d'une unité ;
- glisser un bloc, ou tirer son bord pour le redimensionner ;
- changer le statut d'une action ;
- Annuler et Rétablir (Ctrl/Cmd+Z, Maj+Ctrl/Cmd+Z) ;
- supprimer, toujours après une confirmation dans la vue.
Chaque geste appelle un tool existant (move_periods, resize_period, set_action_status, undo…), comme le ferait l'agent. Le journal, l'annulation et le temps réel sont donc ceux de l'écran. Une écriture refusée (rôle, verrou, conflit) s'affiche dans la vue. Un lecteur voit une vue qui lit seulement.
Le direct : la vue relit le journal par get_changes_since, d'abord toutes les deux secondes, puis de moins en moins souvent quand rien ne bouge (jusqu'à quinze secondes). Elle s'arrête quand le cadre est caché. Chaque appel de show_schedule monte un nouveau cadre ; une vue plus ancienne du même planning se retire d'elle-même et dit qu'une vue plus récente est ouverte plus bas.
Parler au modèle
- Le contexte du modèle (
ui/update-model-context) : la vue lui dit ce qui est sélectionné (l'identifiant et le libellé du bloc), la fenêtre visible et la vue ouverte. « Décale ce bloc d'une semaine » vise donc le bon bloc. - « Demander à Claude… » (
ui/message) écrit dans la conversation, à propos du bloc choisi. - « Aide » s'ouvre par l'hôte (
ui/open-link) : un cadre isolé n'ouvre rien lui-même.
La négociation : qui voit quoi
Un hôte MCP Apps annonce l'extension io.modelcontextprotocol/ui avec le type text/html;profile=mcp-app. Le serveur lit trois voies, et la première qui l'annonce suffit :
capabilities.extensions["io.modelcontextprotocol/ui"]àinitialize(la voie de la spec stable) ;capabilities.experimental["io.modelcontextprotocol/ui"](l'ancienne voie) ;_meta["io.modelcontextprotocol/clientCapabilities"]d'une requête : la voie du protocole sans état. Le SDK du serveur (1.x) ne parle pas encore ce protocole, mais la voie est lue.
Ce qui en dépend :
| Un hôte MCP Apps | Un client sans MCP Apps (Claude Code) | |
|---|---|---|
tools/list | les 101 tools ; l'hôte cache au modèle les trois réservés à la vue | les 98 tools du modèle : les trois réservés à la vue ne sont pas listés (rien ne les y cacherait au modèle, et get_schedule_page rend jusqu'à 120 000 caractères) |
show_schedule | la vue ; un texte qui la résume ; aucun lien ni jeton | le texte et le lien /embed d'une heure |
what_if, diff_versions | l'avant/après, et leur texte | leur texte |
L'appel d'un tool réservé reste servi à tous. La visibilité n'est pas une frontière de droits : les mêmes données se lisent par palmaplan://schedule/{id}/json, sous les mêmes droits. Et un hôte qui afficherait la vue sans avoir annoncé l'extension ne la trouve pas vide.
Ce que montre Claude Code : du texte
Claude Code n'affiche pas les MCP Apps. show_schedule y rend un texte qui se suffit : le nom, la période, le nombre de groupes, de lignes, de blocs et de jalons, les risques (liens violés, blocs à risque d'absence) et le lien d'une heure :
Planning « Migration S/4HANA » — du 2026-11-02 au 2026-12-18 ; 2 groupes, 6 lignes, 6 blocs, 1 jalon.
Risques : 1 bloc à risque d absence.
Cet hôte n'affiche pas les vues MCP Apps : cette adresse ouvre le planning, valable une heure, en lecture seule : http://127.0.0.1:4785/embed/<id>?t=…
Son structuredContent est compact : { schedule_id, name, view, options, rank, read_only, shown_at, url, expires_at }. Il ne porte jamais le modèle, parce que Claude Code le donne au modèle à la place du texte. Les 98 tools du modèle sont éprouvés de même par un test : chacun rend un texte utile (un résultat, ou un refus qui dit quoi corriger) et un structuredContent compact.
Le lien /embed, pour les hôtes sans MCP Apps
Le lien ouvre l'écran de PalmaPlan, réduit au planning, dans un navigateur ou dans une iframe d'un autre site. Il porte un jeton signé d'une heure, plafonné au planning et au rôle réel. Ce lien :
- lit seulement, sauf si l'on demande
editable: true; - sous son jeton, la liste des membres n'a pas d'e-mail et l'agenda n'a pas de motif ;
- ne se renouvelle pas lui-même : il expire, et la vue le dit.
- tombe avant son heure si ce qui l'a demandé ne vaut plus : l'agent révoqué, ou la session fermée (mot de passe changé, « Déconnecter partout », compte repris par le propriétaire de son adresse).
Ce n'est pas un jeton nommé : il n'apparaît pas dans Réglages › Connexions et agents › Applications connectées (voir Les jetons et les portées).
Les limites
- La taille : un planning se lit par pages de 120 000 caractères au plus. Un texte plus long qu'une page entière (un libellé démesuré) arrive coupé, et la vue le dit d'une ligne.
- Le direct passe par le sondage, pas par le canal WebSocket : quelques secondes de délai, pas l'instantané de l'écran.
- Pas d'origine stable (
ui.domain) : rien n'est gardé d'une conversation à l'autre dans la vue. - Mobile : la hauteur en ligne est limitée par l'hôte, et le plein écran convient mieux pour modifier. À 390 px, « Aide » et « Plein écran » se replient sur leur icône.
- Les écritures depuis la vue passent par les mêmes tools que l'agent : une écriture que l'agent ne peut pas faire, la vue ne peut pas la faire non plus.
Ce qui est prouvé, ce qui ne l'est pas
Prouvé, dans le dépôt :
- par l'hôte de recette (
scripts/hote-mcp-apps.ts), construit sur l'AppBridgedu SDK MCP Apps et piloté par Chromium. Il reproduit ce que la documentation publique de Claude dit de ses hôtes : deux cadres sur deux origines distinctes, la CSP construite depuis_meta.ui.cspet posée en en-tête,frameDomainsignoré, la visibilité des tools respectée ; - par la recette du sprint 24 (vingt points) : chaque message du protocole joué, aucune violation de CSP, aucune requête de la vue vers quelque origine que ce soit ; un geste dans la vue retrouvé dans l'écran web et inversement ; le contexte du modèle ; l'avant/après et « Accepter », y compris la proposition périmée ; la négociation (les 98 tools du modèle sans la capacité, les 101 avec) ; un tool réservé refusé au modèle ; un tool au-delà du rôle refusé à la vue ; Claude Code en texte seul.
Pas prouvé : aucun hôte réel n'a encore affiché cette vue. Ni Claude Desktop, ni claude.ai, ni un autre hôte MCP Apps. L'hôte de recette suit la documentation, pas le code de Claude : sa CSP exacte, sa façon d'annoncer la capacité MCP Apps à initialize, sa WebView mobile peuvent différer. En particulier, si un hôte réel affiche la vue sans annoncer l'extension, le modèle n'y verra que les 98 tools du modèle, et la vue continuera de fonctionner, puisque les appels réservés restent servis. La marche à suivre de l'essai réel est dans Brancher Claude Desktop, section « L’essai de la vue dans Claude Desktop ». Tant qu'il n'a pas été joué, considérez la vue comme prouvée au protocole, pas comme prouvée dans Claude.