Un panneau cliquable dans Claude : le tuto MCP Apps 2026
Le spec MCP 2026-07-28 sort les serveurs du texte brut et les rend serverless. J'ai codé un premier MCP App un week-end.
Jusqu'au mois dernier, un serveur MCP ne savait faire qu'une chose : renvoyer du texte à Claude. Je demandais mes devis en attente, Claude me recrachait une liste en markdown, et pour en valider un il fallait redemander à l'oral. Le nouveau spec MCP 2026-07-28, finalisé le 28 juillet et en cours de déploiement dans Claude ce mois-ci, casse cette limite : un serveur peut afficher une vraie interface cliquable — un tableau, des boutons — dans la conversation. Et comme le protocole est passé stateless, on l'héberge en serverless pour quasi rien.
MCP Apps traînait en preview depuis janvier. Ce qui change vraiment maintenant, c'est que le spec de juillet en fait une extension officielle, versionnée, et surtout qu'il vire les sessions du protocole. J'ai passé un week-end à porter un vieux serveur MCP interne dessus et à coder un premier App. Voici le tuto, avec le code qui tourne, ce que ça coûte, et les trois trucs qui m'ont mangé deux heures.
Ce que change vraiment le spec MCP 2026-07-28
Trois changements comptent pour un dev. Le reste, c'est de la plomberie.
Le protocole devient stateless. Fini le handshake initialize / initialized et l'en-tête Mcp-Session-Id. La version du protocole, les infos client et les capabilities voyagent désormais dans _meta sur chaque requête. Conséquence directe : n'importe quelle instance de votre serveur peut traiter n'importe quelle requête. Plus besoin de session collante, plus de store partagé. Vous mettez ça derrière un simple round-robin, ou mieux, sur du serverless.
MCP Apps passe extension officielle. Un outil déclare à l'avance un gabarit d'interface HTML. Le host (Claude) le préfetch, le met en cache et le passe en revue sécurité avant le moindre rendu. L'UI s'affiche dans une iframe sandboxée et cause avec le host en JSON-RPC par-dessus postMessage. Point clé : chaque action déclenchée depuis l'UI repasse par le même chemin d'audit et de consentement qu'un appel d'outil direct. Pas de porte dérobée.
Tasks devient stateless aussi. L'ancien tasks/result bloquant disparaît. Un tools/call renvoie un handle de tâche, et le client pilote la suite en polling via tasks/get, tasks/update, tasks/cancel. tasks/list a été supprimé — impossible à scoper proprement sans session.
L'écosystème suit : plus de 400 millions de téléchargements du SDK par mois (×4 sur un an), plus de 950 connecteurs listés dans l'annuaire de Claude, et des boîtes comme Figma, Intuit, Netlify, Xero ou Zoom déjà sur le spec. Si vous débutez sur le sujet, notre guide MCP pour PME pose les bases avant d'attaquer ce qui suit.
Le tuto : un panneau d'approbation de devis dans Claude en 45 min
Objectif concret : je tape « montre-moi les devis en attente » dans Claude, un tableau s'affiche avec un bouton Approuver par ligne, et le clic valide côté serveur. Cas d'usage typique PME : validation de devis, de notes de frais, de congés. Stack : Node + les SDK @modelcontextprotocol/server et @modelcontextprotocol/ext-apps.
npm i @modelcontextprotocol/server @modelcontextprotocol/ext-apps
1. Déclarer un outil qui pointe vers une UI
Un MCP App, c'est deux choses à enregistrer : un outil avec le champ _meta.ui.resourceUri, et une ressource qui sert le HTML correspondant. Le schéma d'URI ui:// relie les deux.
import { MCPServer } from '@modelcontextprotocol/server';
import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps';
const server = new MCPServer({ name: 'devis-approvals', version: '1.0.0' });
registerAppTool(server, 'view_devis', {
title: 'Voir les devis en attente',
inputSchema: {},
_meta: { ui: { resourceUri: 'ui://devis/board' } },
});
registerAppTool(server, 'approve_devis', {
title: 'Approuver un devis',
inputSchema: {
type: 'object',
properties: { devisId: { type: 'string' } },
required: ['devisId'],
},
_meta: { ui: { resourceUri: 'ui://devis/board' } },
});
2. L'UI qui parle en JSON-RPC
La ressource sert un HTML autonome. Il reçoit le résultat de l'outil via window.app.ontoolresult et rappelle un outil via window.app.callTool. Le serveur renvoie ses données dans structuredContent (avec un fallback texte pour les hosts sans UI).
registerAppResource(server, 'ui://devis/board', () => `
<table id='devis'></table>
<script>
window.app.ontoolresult = (r) => render(r.structuredContent.devis, r.structuredContent.canApprove);
function approve(id) { window.app.callTool('approve_devis', { devisId: id }); }
function render(rows, canApprove) { /* injecte les lignes + boutons */ }
</script>
`);
Côté handler, l'outil view_devis renvoie la liste et un flag canApprove calculé selon les droits de l'utilisateur. L'UI se sert du flag pour afficher — ou masquer — le bouton.
server.setToolHandler('view_devis', async (_args, { user }) => {
const devis = await listDevis(user.org_id);
return { structuredContent: { devis, canApprove: canApprove(user) } };
});
3. Déployer stateless, en serverless
C'est là que le spec paie. Sans session, pas d'état à conserver entre deux appels : chaque requête se suffit à elle-même. Vous poussez le même code sur Cloudflare Workers, Vercel ou un conteneur derrière un load balancer bête, sans affinité de session. Sur le tier gratuit de Cloudflare Workers, vous tenez 100 000 requêtes par jour pour 0 € ; au-delà, c'est 5 $/mois. Pour un connecteur interne à une PME, vous ne verrez jamais la facture. On est loin du serveur MCP qu'il fallait garder allumé 24/7 façon workflows n8n branchés sur Claude.
Dernière étape : dans Claude, vous ajoutez l'URL du serveur comme connecteur, vous tapez votre requête, et le tableau s'affiche inline. Comptez trois quarts d'heure du npm i au premier clic si vous partez d'une base propre.
Sécurité : cacher le bouton ne suffit pas
Le piège du débutant en MCP Apps, c'est de croire que masquer le bouton Approuver dans l'UI protège quoi que ce soit. Non. L'iframe est du HTML côté client, l'utilisateur peut rappeler callTool à la main. La règle, formulée noir sur blanc par les équipes qui ont écrit le spec :
Refuser dans le handler, c'est la vraie frontière de sécurité. Cacher le bouton, c'est juste de la politesse.
Donc on vérifie toujours le droit côté serveur, dans le handler :
server.setToolHandler('approve_devis', async ({ devisId }, { user }) => {
if (!canApprove(user)) throw new Error('Droit insuffisant');
await approveInDb(devisId, user.org_id);
return { content: [{ type: 'text', text: 'Devis approuvé' }] };
});
Bonne nouvelle du spec : l'auth s'aligne enfin sur OAuth 2.0 et OIDC en production. Vous branchez Entra ou Okta sans bricolage, et Claude gère l'auth managée côté admin — l'utilisateur n'a rien à configurer. Si vos agents commencent à toucher des données réelles, relisez notre checklist de sécurisation des agents IA avant de mettre en prod.
Migrer un serveur MCP existant : ce qui casse
Je pensais recompiler mon vieux serveur de 2025 en une heure. J'en ai passé trois. Le portage n'est pas cosmétique — voici les murs sur lesquels je me suis cogné, dans l'ordre.
Les sessions n'existent plus. Tout mon état passait par Mcp-Session-Id. Il a fallu émettre des handles explicites et les passer en arguments d'outil. C'est la partie la plus lourde si votre serveur était stateful.
Tasks est passé de bloquant à polling. Mon tasks/result qui attendait la fin d'un job renvoyait dans le vide. Réécriture en tasks/get côté client avec logique de retry. Si vous aviez livré Tasks en prod sur le spec 2025-11-25, c'est une migration cassante, pas une mise à jour.
Le champ resultType est obligatoire. Celui-là m'a coûté le plus de temps bêtement : mes réponses passaient la validation en local mais Claude les rejetait en silence. Il manquait "resultType": "complete" sur chaque résultat. Un client 2026-07-28 refuse tout résultat sans ce champ.
La politique de dépréciation garantit au moins 12 mois entre l'annonce et la suppression d'une fonctionnalité. Vous avez donc le temps, mais un serveur non migré tourne aujourd'hui en mode compatibilité, pas en stateless — vous ne touchez pas les gains serverless tant que le portage n'est pas fait.
Combien coûte un MCP App, et pour quels cas d'usage en PME
Le coût d'infra tend vers zéro pour un usage interne : le tier gratuit Cloudflare Workers (100 000 req/jour) couvre largement une équipe de 50 personnes qui consultent des devis. Le vrai coût, c'est le temps de dev — une demi-journée pour un App simple si vous connaissez Node, une journée si vous migrez un serveur existant.
Les cas où ça vaut le coup, concrètement : un panneau de validation (devis, notes de frais, congés) où le clic doit rester tracé ; un mini-dashboard qui affiche un état de stock ou de trésorerie sans quitter Claude ; un formulaire de saisie structuré plutôt que de faire dicter des champs à l'oral. Partout où le texte brut oblige l'utilisateur à un aller-retour, l'UI cliquable gagne. Là où un simple retour texte suffit — une recherche, un résumé — MCP Apps est du sur-ingénierie. Un avec quelques connecteurs bien pensés remplace une bonne partie des petits outils internes que vous auriez codés en full-stack il y a un an. Dans la même veine, notre tuto sur le connecteur Semrush branché sur Claude montre le pattern côté données externes.
Mon verdict après le week-end
MCP Apps sur le spec 2026-07-28, c'est le premier moment où MCP arrête d'être un tuyau à texte pour devenir une brique d'app. Le passage stateless est le vrai cadeau : héberger un connecteur ne coûte plus rien et ne demande plus de babysitter un process. Pour un freelance ou une petite équipe tech, c'est le feu vert pour remplacer des micro-outils internes par des Apps dans Claude.
Le bémol, c'est la migration. Si vous partez d'un serveur stateful de 2025, prévoyez une journée et attendez-vous à jurer sur resultType et sur Tasks. Neuf. Le spec est jeune, l'outillage aussi — l'inspecteur et le debug UI restent frustes. Mais pour un nouveau projet, en partant de zéro, c'est net : commencez direct sur 2026-07-28, en stateless, et déployez serverless. Vous n'aurez rien à remigrer dans six mois.