WebMCP : le Pourquoi?, le Quoi?, le Comment?

SAMI
September 5, 2026 18 mins to read
Share

Google a proposé WebMCP. OpenAI l’a livré en premier.

WebMCP permet à une page web de tendre à un agent une fonction plutôt qu’un bouton. Un site enregistre des tools nommés, en JavaScript ou en HTML, chacun avec une description et un JSON Schema en entrée, et un agent qui tourne dans le navigateur peut les lister et les appeler. Au lieu de regarder une capture d’écran et de deviner quel élément soumet le formulaire, l’agent appelle lookup_ticket avec une référence et récupère une chaîne.

Le nom vient de Model Context Protocol, la parenté est réelle, mais la forme diffère. Pas de serveur, pas de transport, pas de connexion à maintenir. Les tools vivent dans l’onglet, s’exécutent dans la session de l’utilisateur avec ses cookies, et disparaissent au déchargement de la page. Microsoft et Google coéditent la spec au W3C. Chrome l’expose derrière un origin trial ouvert depuis Chrome 149.

L’idée tient en une phrase, et elle est bonne. Sauf que l’essentiel de ce qui a été écrit dessus ne s’exécute plus. Le point d’entrée est passé de navigator.modelContext à document.modelContext pendant que les tutoriels s’écrivaient, et deux autres morceaux de l’API avaient déjà été retirés avant ça. D’où le premier réflexe devant un article sur WebMCP : regarder si le code écrit document. ou navigator.. Ce seul caractère vous dit si l’auteur a ouvert les DevTools ce trimestre ou s’il a résumé un billet de février.

le pourquoi?

Les agents pilotent déjà des navigateurs. Ils le font depuis un moment, et le mécanisme n’a rien d’élégant : capture d’écran, sérialisation du DOM ou de l’arbre d’accessibilité, on demande au modèle sur quoi cliquer, on synthétise le clic, on recapture, on recommence. La documentation de Chrome appelle ça l’actuation, et le terme est juste. L’agent fait semblant d’être une paire de mains.

Ça casse de façon ennuyeuse et permanente, jamais spectaculaire. Les sélecteurs bougent dès que le marketing livre un nouveau bandeau. Un date picker maison construit en div n’offre aucune affordance que le modèle reconnaisse. Une transition d’état asynchrone, et la capture sur laquelle le modèle a raisonné a deux états de retard. Chacune de ces étapes coûte un aller-retour complet dans un gros modèle, d’où la latence et la facture en tokens : un formulaire à cinq champs, ce n’est pas une inférence, c’en est cinq ou six, chacune transportant une image ou un dump de DOM. Et rien dans cette boucle n’indique à l’agent si le bouton qu’il s’apprête à presser débite une carte bancaire.

C’est ce dernier point qui m’intéresse le plus. Le scraping ne donne à l’agent ni schema ni notion de conséquence. Il ne distingue un filtre d’un achat qu’en lisant le libellé, exactement le type d’inférence qui échoue en silence dès qu’on passe à l’échelle.

La réponse de WebMCP consiste à laisser la page déclarer ses actions. Le site enregistre des tools nommés, avec description et JSON Schema en entrée, le navigateur les expose à l’agent qu’il héberge, et l’agent appelle une fonction au lieu de deviner à partir de pixels. Chrome formule bien la question de propriété : avec une application MCP, votre interface est invitée chez l’agent, alors qu’ici l’agent passe chez vous et votre interface reste la vôtre.

Reste la question de l’argent.

Google a besoin que les agents fonctionnent, parce que c’est là que sont les paris produit, et Google a besoin que les humains continuent de regarder des pages, parce que c’est là qu’est le revenu. WebMCP règle cette tension par construction. Les tools s’exécutent visiblement, dans un vrai onglet, dans la session de l’utilisateur, page affichée. La documentation Chrome range le headless dans les limitations et précise que l’API vise avant tout des workflows locaux avec un humain dans la boucle.

Lisez la formule attentivement, elle est plus molle que le non-objectif qu’on prête souvent au standard. Elle ne dit pas que le headless est interdit. Elle dit que WebMCP n’est pas conçu pour ça. Mon interprétation : cette histoire d’humain dans la boucle fait deux métiers à la fois, et les deux se tiennent. C’est un vrai argument de sûreté, un appel d’outil qu’on voit est un appel d’outil qu’on peut annuler. C’est aussi un argument business, parce qu’un navigateur qui affiche la page affiche la publicité, et parce qu’un agent qui finalise un achat en silence sur un serveur a effacé la marque du marchand de la transaction. Google ne cache aucun des deux motifs. Je trouve simplement qu’un praticien devrait nommer les deux plutôt que de citer la moitié sûreté.

Si vous êtes responsable d’un site aujourd’hui, WebMCP est une option parmi d’autres, et pas la première qui s’impose. Un serveur MCP côté backend fonctionne sans onglet ouvert, gère les traitements de fond, et se déploie cet après-midi avec des outils matures. L’API HTTP que vous avez déjà reste la surface d’intégration la plus rentable pour quiconque lit une doc. A2A, désormais hébergé par la Linux Foundation avec plus de 150 organisations en avril 2026, traite un tout autre problème : votre agent qui parle à l’agent d’un tiers, pas un modèle qui touche votre UI. Les données structurées dans votre balisage restent ce qui coûte le moins cher et servent la découvrabilité plutôt que l’action. Ne rien faire est une vraie option, avec de vrais coûts, et pour la plupart des applications métier internes c’est encore le bon choix ce trimestre.

WebMCP ne se justifie que dans un cas précis : un humain est sur votre page, un agent l’assiste, et vous voulez que cette assistance soit exacte.

le quoi?

Un tool, c’est un nom, une description, un JSON Schema d’entrée et une fonction. Le navigateur tient le registre par Document et le transmet à l’agent qu’il héberge.

L’API impérative est la moitié spécifiée :

// document.modelContext, et non navigator.modelContext.
// Le getter est passé sur Document dans la révision de mai 2026,
// Chrome a déprécié la surface navigator en Chrome 150.
await document.modelContext.registerTool({
  name: 'lookup_ticket',
  description: 'Look up a support ticket by its reference and return status, owner and last update.',
  inputSchema: {
    type: 'object',
    properties: {
      reference: { type: 'string', description: 'Ticket reference, format INC-000000' },
    },
    required: ['reference'],
  },
  annotations: {
    readOnlyHint: true,
    untrustedContentHint: true, // du texte rédigé par le client repart vers le modèle
  },
  execute: async ({ reference }, { signal }) => {
    const res = await fetch(`/api/tickets/${encodeURIComponent(reference)}`, { signal });
    if (!res.ok) return `No ticket found for ${reference}.`;
    const t = await res.json();
    return `${t.reference}: ${t.status}, owner ${t.owner}, updated ${t.updatedAt}.`;
  },
});

Les noms sont contraints : de 1 à 128 caractères, alphanumérique ASCII plus underscore, tiret et point. Enregistrer un nom déjà pris ne lève pas d’exception. La promesse est rejetée avec un InvalidStateError, bon à savoir si vous comptiez entourer l’appel d’un try/catch synchrone. Même traitement pour un nom vide, une description vide, ou un schema qui échoue à la sérialisation JSON.

Le cycle de vie s’accroche à un AbortSignal, qui a remplacé l’ancien unregisterTool(name) dans la révision d’avril 2026 :

const controller = new AbortController();
await document.modelContext.registerTool(cartTool, { signal: controller.signal });

// Plus tard, quand le panier se vide et que le checkout n'a plus de sens :
controller.abort();

C’est ce qui rend l’intégration framework supportable. Vous liez le controller à la durée de vie d’un composant et le jeu de tools suit l’UI. La doc Chrome précise qu’à partir de Chrome 153 la désinscription ne tue plus les exécutions en cours, ce qui posait un vrai problème au démontage d’un composant pendant un appel.

L’API déclarative transforme un formulaire en tool avec trois attributs :

<form toolname="createSupportRequest"
      tooldescription="Submit a request for support."
      action="/submit">
  <label for="firstName">Prénom</label>
  <input type="text" name="firstName" id="firstName">

  <select name="team" required
          toolparamdescription="Determines what team this request is routed to.">
    <option value="Returns">Retourner un achat.</option>
    <option value="Logistics">Suivre un colis.</option>
    <option value="WebSupport">Obtenir de l'aide sur le site.</option>
  </select>

  <button type="submit">Envoyer</button>
</form>

Le navigateur synthétise le JSON Schema à partir du formulaire : les name deviennent des propriétés, required devient required, les valeurs d’<option> forment un enum avec titres. Sans toolparamdescription, il retombe sur le <label> associé, puis sur aria-description. Retirer toolname ou tooldescription désenregistre le tool. Par défaut l’agent remplit et l’humain clique sur Envoyer. Avec toolautosubmit, l’agent soumet lui-même, et SubmitEvent gagne un booléen agentInvoked plus une méthode respondWith(Promise) pour renvoyer un résultat au modèle au lieu de naviguer.

Il existe deux annotations, pas une de plus. readOnlyHint signale que rien ne change d’état, c’est là-dessus que l’agent décide s’il doit demander confirmation. untrustedContentHint marque une sortie contenant des données que vous ne maîtrisez pas, pour que l’agent l’encadre ou l’encode avant de la donner au modèle. Les hints MCP classiques sur la destructivité et l’idempotence ne sont pas dans cette spec.

Ce que WebMCP n’est pas. Ce n’est pas du MCP sur le réseau : la spec laisse les navigateurs libres d’exposer les tools à leur agent via MCP, via du function calling propriétaire, ou autrement. Ce n’est pas de l’automatisation headless. Ce n’est pas un remplacement de votre API, puisque les tools disparaissent à la fermeture de l’onglet. Et la moitié déclarative n’est, à ce jour, pas spécifiée du tout : la section 4.3 du brouillon W3C est un TODO, l’algorithme de synthèse du schema n’est pas écrit.

Voici comment les options se comparent réellement, aujourd’hui.

qui appellemodèle d’authoù tourne le codematurité, sept. 2026
scrapingn’importe quel agent, sans invitationce que porte la session du navigateurharness de l’agentuniversel, marche partout, peu fiable
automatisation navigateur (Playwright, CDP)vos propres scriptsidentifiants que vous injectezvotre CI ou vos serveursmature, bien compris
WebMCPagent du navigateur, ou agent in-page via getTools()les cookies de session vivantel’onglet du visiteurorigin trial, Chromium plus un client desktop
MCP côté serveurtout client MCP, partoutOAuth ou jetons que vous émettezvos serveursproduction, large support client
A2Aun autre agent, pas un modèleidentité d’agent, TLS et JWTvos serveursprojet Linux Foundation, déploiements en production

le comment?

L’installation locale tient en un flag. Ouvrez chrome://flags/#enable-webmcp-testing, activez, relancez. Pour du trafic réel il faut un jeton d’origin trial servi en en-tête ou en balise meta. Les deux API sont contrôlées par la Permissions Policy tools, dont la valeur par défaut est self : un iframe cross-origin a donc besoin de allow="tools". WebMCP n’existe par ailleurs que dans les documents à origine isolée. Si vous envoyez Origin-Agent-Cluster: ?0 ou activez document.domain, l’API disparaît, le genre de détail qui coûtera un après-midi à quelqu’un sur un portail hérité.

Testez la présence de l’API avant d’enregistrer, la forme a déjà bougé et rebougera :

if (typeof document.modelContext?.registerTool === 'function') {
  await registerAllTools();
}

Pour voir ce qu’une page a enregistré, appelez getTools() depuis la page. Le retour est trié par nom, same-origin par défaut :

const tools = await document.modelContext.getTools();
console.table(tools.map(t => ({
  name: t.name,
  readOnly: t.annotations?.readOnlyHint,
  origin: t.origin,
})));

Vous pouvez aussi exécuter un tool directement, c’est ainsi qu’on teste sans attendre un agent :

const [ticketTool] = await document.modelContext.getTools();
const result = await document.modelContext.executeTool(ticketTool, { reference: 'INC-004417' });

Un avertissement ici. L’IDL de la spec déclare executeTool(RegisteredTool tool, optional object inputObject = {}, optional options = {}) et son propre exemple passe un objet. La doc Chrome de l’API impérative montre le même appel avec une chaîne JSON. Les deux pages ont été touchées à six jours d’intervalle et se contredisent. Si votre appel est rejeté sans raison apparente, essayez l’autre forme avant de déboguer votre schema.

Pour tester de façon agentique sans câbler un modèle, Google publie l’extension Model Context Tool Inspector : elle liste les tools enregistrés, les appelle avec du JSON écrit à la main, et fait passer des prompts en langage naturel par gemini-3-flash-preview si vous fournissez une clé. Chrome expose aussi une surface de test derrière --enable-features=WebMCPTesting,DevToolsWebMCPSupport, utilisée par des harness tiers pour lister et invoquer des tools sans agent. Cette surface n’est pas documentée sur developer.chrome.com : considérez sa forme comme instable et n’y adossez pas votre CI sans épingler une version de Chrome.

Instrumentez trois choses dès le premier jour. Comptez les invocations de tools séparément des événements UI, sinon le trafic agent pollue silencieusement votre entonnoir et vous ne saurez pas quelles conversions ont été assistées. Journalisez les arguments réellement envoyés par l’agent face au schema que vous publiez : une dérive de schema se voit à la hausse des appels rejetés bien avant qu’un utilisateur signale un bug. Suivez la latence par tool, parce qu’un tool à quatre secondes se fait abandonner en plein plan par l’agent, et vous le verrez comme une chute inexpliquée plutôt que comme un timeout.

Qui devrait prototyper maintenant. Ceux dont les utilisateurs sont déjà dans un onglet avec un agent ouvert, sur des parcours multi-champs : réservation, saisie structurée, tableaux de bord dont les filtres se décrivent mal en prose. Ceux qui sont sur Shopify, où les tools sont déjà enregistrés sur toutes les vitrines Liquid que vous l’ayez demandé ou non, et où le travail utile consiste à auditer ce que la plateforme a décidé d’exposer à votre place. Ceux dont le concurrent s’y est mis, dans le commerce en particulier.

Qui devrait attendre. Les produits régulés, où un appel d’outil authentifié devient un contrôle à documenter devant un auditeur. Tout ce qui déplace de l’argent ou modifie un état de compte, au moins tant que la primitive de consentement bouge. Les équipes sans trafic agent dans leurs logs, c’est-à-dire la majorité.

Le signal à surveiller n’est ni la spec atteignant le stade Candidate Recommendation, ni Chrome passant en stable. C’est un deuxième éditeur d’agent indépendant qui implémente le côté client. Un éditeur, c’est une décision produit. Deux, c’est un écosystème, et à ce moment-là seulement enregistrer des tools cesse d’être un pari sur la feuille de route d’une seule entreprise.

l’écart entre la spec et le terrain

Pendant l’essentiel de 2026, la description honnête de WebMCP tenait en une phrase : vous pouviez enregistrer des tools, rien ne les appelait. Google avait annoncé à I/O que Gemini dans Chrome supporterait bientôt ces API. Pour autant que je puisse le vérifier, ce n’est toujours pas le cas, plus de trois mois après.

Ce qui a changé, c’est qu’un autre est passé devant. Le 25 août 2026, OpenAI a livré les Site tools dans le navigateur intégré de l’application ChatGPT desktop. ChatGPT Work et Codex découvrent et appellent les tools fournis par la page. Les contraintes valent la lecture avant de bâtir un plan dessus : GPT-5.6 Sol ou Terra uniquement, désactivé sur Luna, indisponible sur les espaces Enterprise et Edu, aucun support de l’API déclarative, aucune découverte des tools enregistrés dans un iframe, même same-origin. Ces deux derniers points comptent. Si vous avez choisi la voie déclarative parce que c’était trois attributs HTML, le premier client grand public livré ne la voit pas.

Shopify a activé les tools sur toutes les vitrines Liquid, et Cloudflare a livré un pont côté edge qui injecte un script same-origin dans les réponses HTML, de sorte qu’un site obtient des tools sans toucher au code de son origine. Ce sont de vrais déploiements. Cela signifie aussi que la version agent d’un site est écrite par une plateforme plutôt que par le propriétaire du site, et la doc publiée par Shopify ne dit pas si un marchand peut désactiver un tool ou en réécrire la description. Si votre discours de marque compte dans l’UI humaine, il est curieux d’accepter les valeurs par défaut d’un fournisseur dans l’UI agent.

Sur les chiffres d’adoption, méfiance. La dernière mesure propre que j’ai trouvée est le relevé Cloudflare Radar AI Insights repris par freeCodeCamp pour la semaine du 17 au 23 mai 2026, sur 111 076 domaines scannés parmi le top 200 000, qui classait WebMCP dans la tranche proche de zéro. C’était avant Shopify. Tout graphique publié à partir de septembre mesure surtout la décision produit d’une plateforme.

Vient ensuite l’instabilité. provideContext({tools}) supprimé en mars, unregisterTool() en avril, le point d’entrée déplacé hors de navigator en mai. Trois ruptures en une année civile, dans une spec dont la ligne de statut affiche toujours Draft Community Group Report et qui n’est ni un standard W3C ni sur le Standards Track. La position de Mozilla est neutre, l’issue est close. Celle de WebKit est ouverte avec huit labels de préoccupation : conception d’API, duplication, internationalisation, portabilité, vie privée, sécurité, cas d’usage, lieu de normalisation. J’ai vu cet état rapporté comme une opposition de Safari. Ce n’est pas une position publiée, et la nuance compte, mais huit réserves émises par l’éditeur qui détient un quart du marché mobile ne sont pas une erreur d’arrondi.

C’est sur la surface de sécurité que je pousserais le plus fort contre le récit optimiste. Le guide de sécurité pour agents de Chrome nomme deux vecteurs : les manifestes malveillants, c’est-à-dire des instructions cachées dans le nom ou la description d’un tool, et les sorties contaminées, c’est-à-dire des données tierces renvoyées par un tool par ailleurs honnête. La parade côté agent s’appelle le spotlighting, et la comparaison de Chrome est agréablement terre à terre : les délimiteurs coûtent peu et cèdent si l’attaquant devine votre balise de fermeture, l’encodage base64 y résiste et coûte environ 33 % de tokens en plus sur chaque sortie de tool. Côté site, vous disposez d’un booléen, untrustedContentHint, et de budgets de caractères, 500 pour une description et 1,5 K par sortie, dont le but est de vous garder sous les garde-fous des agents.

Il existe désormais des travaux académiques propres à WebMCP, pas seulement sur MCP en général. Lee et ses co-auteurs décrivent le Mid-Session Tool Injection : un script tiers partageant votre document mute la surface de tools en pleine session. Ils distinguent le hijacking, qui abuse d’AbortSignal et de courses à l’enregistrement pour changer les tools visibles par l’agent, et le framing, qui laisse la fonctionnalité intacte et modifie les métadonnées, readOnlyHint compris, pour changer la lecture que le modèle fait d’un tool. Le hijacking atteint 100 % d’exfiltration dans leurs scénarios mais perturbe la tâche au point de se remarquer. Le framing préserve jusqu’à 85 % de complétion tout en réussissant, et c’est le résultat le plus inquiétant. Leur constat selon lequel les attaques au niveau protocole ne faiblissent pas d’une génération de modèle à l’autre, alors que les attaques par description, si, est ce sur quoi je reviens sans cesse : on n’achètera pas la sortie de ce problème avec un meilleur modèle.

Et la primitive qui aiderait le plus manque à l’appel. La page de sécurité de Chrome, mise à jour le 1er juillet, vous dit que le brouillon inclut requestUserInteraction() pour solliciter l’utilisateur en cours d’exécution, et pointe vers une ancre de spec qui n’existe plus. ModelContextClient et requestUserInteraction ont été retirés fin juin. Le consentement fait encore l’objet d’une discussion ouverte. Les sémantiques de confirmation censées distinguer WebMCP du scraping vivent donc aujourd’hui dans les décisions produit des éditeurs d’agents, pas dans la plateforme. OpenAI passe chaque invocation en revue et applique ses règles habituelles aux achats et aux suppressions, ce qui est très bien de leur part et n’est pas un standard du web.

Donc : prototypez, ne vous engagez pas. Enregistrez des tools en lecture seule sur une surface où un appel cassé ne coûte rien, mettez untrustedContentHint sur tout ce qui renvoie du contenu utilisateur, laissez l’humain cliquer sur Envoyer, et instrumentez les appels pour disposer d’une base de comparaison le jour où un deuxième éditeur d’agent se lance. Le coût d’ingénierie est faible et l’option achetée est réelle. Ce que je ne ferais pas : refactorer la logique applicative pour dégager une belle couche de tools, ou exposer quoi que ce soit qui déplace de l’argent, sur la foi d’un client livré il y a neuf jours et d’une section de spec encore marquée TODO.

Leave a comment

Your email address will not be published. Required fields are marked *