Newsletter para devsEntra

WebMCP: Qué es y cómo convierte tu web en agéntica

🏄‍♂️ Antes de que sigas leyendo, una actualización importante: este post se publicó en febrero de 2026 y se ha refrescado el 19 de mayo con las novedades anunciadas por el equipo de Chrome. Lo detallamos todo justo a continuación.

Sigue siendo tecnología en fase experimental (early preview), pero la dirección está cada vez más clara y se nota que esto avanza.

Qué problema intenta resolver WebMCP

Vamos a ponernos en contexto. Hoy, cuando un agente de IA quiere interactuar con una web (comprar un billete, rellenar un formulario, buscar un producto), tiene que hacer lo mismo que haría un humano: mirar la pantalla, interpretar el HTML, encontrar botones e ir haciendo clic.

Esto funciona. Más o menos.

El problema es que es frágil, lento y caro. El agente necesita “tokenizar” toda la interfaz, procesarla con el modelo de lenguaje, decidir dónde hacer clic y rezar para que el DOM no haya cambiado desde la última vez. Es como si para pedir un café tuvieras que leer toda la carta, estudiar la tipografía del menú y adivinar dónde está el camarero.

Y no es solo una cuestión de elegancia técnica. Cada captura de pantalla, cada análisis del HTML, cada decisión sobre “¿es este el botón correcto?” consume tokens. Y los tokens cuestan dinero, tiempo y fiabilidad. Un cambio menor en el CSS de la página puede hacer que el agente deje de funcionar. Un popup inesperado, un banner de cookies, una variación en el texto de un botón… cualquier cosa rompe la cadena.

Herramientas como Playwright o Puppeteer han sido el parche durante años. Funcionan, pero obligan al agente a operar como un titiritero que mueve hilos sobre una interfaz diseñada para dedos humanos. WebMCP propone cortar esos hilos.

WebMCP propone algo más elegante: que la propia web le diga al agente qué puede hacer y cómo hacerlo. En lugar de que el agente interprete la interfaz humana, el sitio web expone acciones estructuradas (lo que llaman “tools”) que el agente puede descubrir e invocar directamente.

🔑 La idea central es sencilla: no obligues al agente a actuar como un humano. Dale un menú de acciones claras con instrucciones precisas.

MCP vs WebMCP: primos hermanos, no gemelos

Si llevas tiempo siguiendo el mundo de los agentes de IA, seguro que te suena MCP (Model Context Protocol). Es el protocolo que permite que herramientas como Claude Code o Cursor se conecten con servicios externos a través de un servidor que expone “tools”. Si quieres profundizar, tenemos un tutorial completo para crear un servidor MCP desde cero con TypeScript.

MCP vive en el lado del servidor. Necesitas levantar un servidor MCP, configurar la comunicación y gestionar la autenticación. Funciona genial para entornos de desarrollo, para automatización en segundo plano, y para tareas que no necesitan una interfaz visual. Con MCP Apps las herramientas MCP ya pueden devolver interfaces interactivas directamente dentro de la conversación. De hecho, instalar servidores MCP en agentes como Claude Code, Gemini CLI o Copilot es ya una práctica habitual entre developers.

WebMCP toma una ruta diferente. En lugar de vivir en un servidor, vive en el navegador. Las tools se registran en el contexto de la pestaña donde el usuario está navegando, aprovechando su sesión, sus credenciales y su interfaz.

Esto tiene implicaciones importantes:

  • El agente trabaja dentro de la misma sesión que el usuario, sin necesidad de tokens adicionales ni APIs de backend.
  • El usuario puede ver y confirmar lo que el agente hace (human-in-the-loop).
  • La lógica del frontend se reutiliza, no hay que duplicar nada en un servidor aparte.

Pero también tiene limitaciones claras: no funciona en modo headless (sin navegador visible), no hay forma de que un agente descubra qué tools ofrece un sitio sin visitarlo primero, y la superficie de la API todavía tiene agujeros. Más adelante hablamos de eso.

La forma más justa de ver la relación entre ambos es como herramientas complementarias. MCP para lo que ocurre en el backend y en segundo plano. WebMCP para lo que ocurre dentro de la pestaña del navegador, cara a cara con el usuario. Para entender cómo encajan estas piezas con A2A, ACP y otros estándares, consulta nuestra guía de los protocolos de IA generativa para programar.

Cómo funciona: la API de navigator.modelContext

El núcleo técnico de WebMCP es una extensión del objeto Navigator del navegador. Aparece una nueva propiedad llamada navigator.modelContext que permite registrar y gestionar tools desde JavaScript.

En el borrador del W3C Community Group, las operaciones principales se han ido depurando. En su versión de mayo de 2026 quedan así:

  • registerTool(tool, options): añade una tool. Falla si el nombre ya existe o si el esquema de entrada no es válido. El segundo argumento permite pasar un AbortSignal para gestionar la baja.

⚠️ Si has visto código antiguo con provideContext() o clearContext(), esos métodos se han retirado. El patrón actual usa AbortController para registrar y retirar tools de forma granular. unregisterTool() tampoco existe.

Cada tool tiene una estructura clara: un nombre, una descripción en lenguaje natural, un esquema JSON de entrada (inputSchema) y un callback execute que devuelve cualquier valor que tenga sentido para el agente (objeto, array, string, número o booleano).

Y hay un detalle de diseño que marca la diferencia: el mecanismo requestUserInteraction que apareció en versiones tempranas del borrador. Cuando el agente invoca una tool imperativa que necesita confirmación del usuario (una compra, una reserva, un envío), el callback puede pausar la ejecución y pedir al usuario que confirme antes de continuar.

En la vía declarativa, el equivalente moderno es omitir el atributo toolautosubmit: el navegador se encarga de pausar el submit y de mostrar feedback visual con :tool-submit-active. Y para tools imperativas que no mutan estado, el campo annotations: { readOnlyHint: true } le indica al agente que puede ejecutarlas sin riesgo.

Esto no es un “nice to have”. Es la pieza que hace viable usar agentes en flujos donde hay dinero o datos sensibles de por medio.

🛡️ WebMCP no quiere que el agente actúe sin supervisión. El diseño incluye varios mecanismos para que el humano tenga la última palabra cuando sea necesario.

Dos caminos para implementar: declarativo e imperativo

WebMCP ofrece dos vías para exponer tools al agente, y cada una tiene su momento.

La vía imperativa (JavaScript)

Es la más potente. Registras tools con navigator.modelContext.registerTool(), defines el esquema de entrada con JSON Schema y proporcionas un callback que se ejecuta cuando el agente invoca la herramienta.

Este es un ejemplo de una tool que busca vuelos y otra que permite reservar con confirmación del usuario, ya con el patrón actual:

// Verificar si WebMCP está disponible
if (!("modelContext" in navigator)) {
  console.warn("WebMCP no está disponible en este navegador.");
}

// Controller para gestionar el ciclo de vida de ambas tools
const searchController = new AbortController();
const bookController = new AbortController();

// Tool de búsqueda: read-only
navigator.modelContext.registerTool({
  name: "search_flights",
  description: "Search flights by origin, destination, and date (YYYY-MM-DD).",
  inputSchema: {
    type: "object",
    properties: {
      origin: { type: "string", description: "IATA code, e.g. MAD" },
      destination: { type: "string", description: "IATA code, e.g. LHR" },
      date: { type: "string", description: "Date in YYYY-MM-DD" }
    },
    required: ["origin", "destination", "date"]
  },
  async execute(input) {
    // Llamada real a tu backend o API de vuelos
    const response = await fetch("/api/search-flights", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(input)
    });
    return await response.json();
  },
  annotations: { readOnlyHint: true }
}, { signal: searchController.signal });

// Tool de reserva: necesita confirmación humana
navigator.modelContext.registerTool({
  name: "book_flight",
  description: "Book a flight by flightId. Requires user confirmation.",
  inputSchema: {
    type: "object",
    properties: {
      flightId: { type: "string", description: "Flight ID from search results" }
    },
    required: ["flightId"]
  },
  async execute(input, client) {
    // Pedir confirmación al usuario antes de reservar
    const confirmed = await client.requestUserInteraction(async () => {
      return window.confirm(`¿Confirmas la reserva del vuelo ${input.flightId}?`);
    });

    if (!confirmed) return { status: "cancelled" };

    const response = await fetch("/api/book-flight", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ flightId: input.flightId })
    });
    return await response.json();
  }
}, { signal: bookController.signal });

// Cuando la página o el componente se desmonta:
// searchController.abort();
// bookController.abort();

Fíjate en cómo el AbortSignal se convierte en el único punto de control del ciclo de vida. Las tools viven mientras tu componente esté montado y desaparecen del registro en cuanto llamas a abort().

La vía declarativa (formularios HTML)

Esta es la vía rápida. Si tu web ya tiene formularios bien estructurados, puedes añadir atributos especiales para que el navegador los exponga como tools al agente.

Los atributos clave son toolname, tooldescription, toolparamdescription y toolautosubmit. Un formulario de reserva de restaurante quedaría así:

<form id="bookingForm"
      toolname="book_table"
      tooldescription="Book a table. Collect name, date, time, and party size.">
  <label>
    Name:
    <input name="name" toolparamdescription="Full name (min 2 chars)" required />
  </label>

  <label>
    Date:
    <input name="date" type="date" toolparamdescription="Date in YYYY-MM-DD" required />
  </label>

  <label>
    Party size:
    <input name="partySize" type="number" min="1" max="12"
           toolparamdescription="Number of guests (1-12)" required />
  </label>

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

En este caso hemos omitido toolautosubmit porque una reserva es una acción que conviene que el usuario confirme con un clic. Si fuese una búsqueda en lugar de una reserva, lo habríamos activado.

Y en el JavaScript del formulario puedes detectar si el envío viene de un agente para devolver datos estructurados:

form.addEventListener("submit", (e) => {
  e.preventDefault();
  const payload = Object.fromEntries(new FormData(form).entries());

  const result = {
    ok: true,
    confirmation: `Reserva para ${payload.name} el ${payload.date} (${payload.partySize} personas).`
  };

  // Si el formulario lo envía un agente, devolver respuesta estructurada
  if (e.agentInvoked) {
    e.respondWith(Promise.resolve(result));
  }
});

⚡ Si ya tienes formularios HTML bien hechos, la vía declarativa puede ser tu primer paso hacia WebMCP. Poco esfuerzo, resultado inmediato.

Cómo probarlo hoy

Esto sigue siendo experimental, pero el flujo de instalación se ha simplificado bastante con respecto a febrero.

1. Instala una versión reciente de Chrome o Edge

Necesitas un navegador Chromium con versión 146.0.7672.0 o superior. Si quieres anticiparte al Origin Trial que se confirmará en Chrome 149, puedes usar Chrome Canary y mantenerlo actualizado.

2. Activa el flag de WebMCP

Abre chrome://flags/#enable-webmcp-testing (o el equivalente en Edge: edge://flags/#enable-webmcp-testing), ponlo a Enabled y reinicia el navegador.

3. Instala el inspector desde la Chrome Web Store

La extensión Model Context Tool Inspector ya está publicada oficialmente. Te permite:

  • Ver qué tools tiene registradas la página actual.
  • Invocarlas manualmente con JSON de entrada.
  • Chatear con un agente que por defecto usa gemini-3-flash-preview, para probar tus tools en lenguaje natural.

4. Juega con las demos oficiales

En el repositorio webmcp-tools de GoogleChromeLabs tienes ahora tres demos completas:

  • Pizza Maker (imperativa).
  • Travel demo en React (imperativa).
  • Le Petit Bistro (declarativa).

Son el mejor punto de partida para ver WebMCP en acción con código real.

Ejemplo de implementación con webmcp-starter

Desglosamos completo el código WebMCP en la demo Midnight Eats: 8 tools imperativas + 1 declarativa (form checkout). Análisis de arquitectura, integración con el navegador y flujo de trabajo de principio a fin.

1. Qué hace este código con WebMCP

La demo Midnight Eats es una app de food delivery (estilo Glovo, Uber Eats) que expone 9 tools al navegador para que un agente de IA pueda operar la app sin tocar el DOM directamente. El agente descubre las tools, las invoca con parámetros estructurados, y recibe respuestas JSON. La UI se actualiza como efecto colateral de la ejecución.

Dos mecanismos coexisten:

Vía imperativa (JavaScript): 8 tools registradas con navigator.modelContext.registerTool(). Cada una define nombre, descripción en lenguaje natural, JSON Schema de entrada y un callback execute().

  • search_restaurants — buscar restaurantes
  • get_menu — obtener menú de un restaurante
  • add_to_cart / remove_from_cart — gestión del carrito
  • get_cart / clear_cart — lectura y reseteo del carrito
  • apply_promo_code — aplicar código promocional
  • get_order_status — consultar estado del pedido

Vía declarativa (HTML Form): 1 tool definida como formulario HTML anotado con toolname, tooldescription y toolparamdescription. El navegador la convierte en una tool invocable.

  • checkout — completar el pedido con dirección, propina y método de pago

La detección es condicional: todo el bloque imperativo se envuelve en if ('modelContext' in navigator). Si el navegador no soporta WebMCP, la app funciona normalmente para humanos: las tools simplemente no se registran. Esto es progressive enhancement puro.

2. Arquitectura: tres capas

La app se organiza internamente en tres capas diferenciadas. Las tools WebMCP no contienen lógica de negocio: son adaptadores finos que llaman a las mismas funciones que la UI humana.

Capa 1 — Datos y lógica de negocio

Toda la lógica vive en JavaScript puro, sin dependencia de WebMCP:

  • RESTAURANTS: array con 6 restaurantes con id, name, cuisine, menu.
  • PROMO_CODES: objeto con códigos válidos (WELCOME20, FREEDELIVERY, SAVE5).
  • state: objeto global con cart, promo, order, currentView, etc.
  • Funciones de negocio: addToCart(), removeFromCart(), clearCart(), getCartTotal(), applyPromo().

Estas funciones mutan el estado y calculan totales. No saben nada de WebMCP ni de agentes.

Capa 2 — UI y rendering

Funciones que leen el estado y generan HTML: renderRestaurants(), renderMenu(), updateCartUI(), showView(), showMenu(), showOrderConfirmation().

Las tools WebMCP las llaman como efecto colateral: get_menu ejecuta showMenu() para que la UI navegue visualmente al restaurante, además de devolver el JSON al agente.

Capa 3 — Adaptadores WebMCP

Los 8 registerTool() + el form anotado. Cada tool valida la entrada, llama a funciones de la capa 1, dispara actualizaciones de la capa 2 y devuelve JSON estructurado.

Esta separación es exactamente lo que recomiendan las fuentes oficiales para SPAs que quieran adoptar WebMCP: aislar la lógica en servicios reutilizables para que las tools no dependan de detalles de UI.

3. La vía imperativa: estructura de cada tool

Cada tool sigue la misma estructura de campos obligatorios:

const controller = new AbortController();

navigator.modelContext.registerTool({
  name: "nombre_de_la_tool",
  description: "Texto en lenguaje natural para el agente. " +
    "Explica QUÉ hace, CUÁNDO usarla y QUÉ necesita.",
  inputSchema: {
    type: "object",
    properties: {
      param1: { type: "string", description: "..." }
    },
    required: ["param1"]
  },
  execute: ({ param1 }) => {
    // ... lógica ...
    return { success: true, data: param1 };
  },
  annotations: { readOnlyHint: true }
}, { signal: controller.signal });

El formato de respuesta puede ser un objeto plano, un string, un número o cualquier cosa que tenga sentido. El agente parsea lo que devuelvas.

Algunas decisiones de diseño que se repiten en el código:

  • Enums en inputSchema: get_menu usa enum con los IDs de restaurantes, restringiendo al agente a valores válidos a nivel de schema. Es más robusto que depender solo de la descripción.
  • Descripciones que enumeran opciones: la description de apply_promo_code incluye los códigos válidos directamente. Si el agente falla, el error también los enumera. Se autocorrige solo.
  • readOnlyHint: get_cart y get_order_status lo declaran como true. Le indica al agente que puede llamarlas sin riesgo de mutar estado.

4. La vía declarativa: el formulario de checkout

El navegador interpreta el formulario anotado como una tool invocable. El agente puede rellenar los campos y disparar el submit sin manipular el DOM.

<form
  toolname="checkout"
  tooldescription="Place a food delivery order and complete checkout.
    Requires a delivery address. Use this tool AFTER items have been
    added to the cart using the add_to_cart tool."
>
  <input name="delivery_address" required
    toolparamdescription="Full street delivery address including
      apartment or unit number" />

  <textarea name="delivery_instructions"
    toolparamdescription="Optional delivery instructions for the
      driver such as gate codes or drop-off preferences"></textarea>

  <select name="tip_amount" required
    toolparamdescription="Dollar tip amount for the delivery driver">
    <option value="0">No tip</option>
    <option value="3">$3.00</option>
    <option value="5" selected>$5.00</option>
    <option value="8">$8.00</option>
    <option value="10">$10.00</option>
  </select>

  <select name="payment_method" required
    toolparamdescription="Payment method to charge. Choose from
      saved cards or cash on delivery.">
    <option value="visa_4242">Visa ···4242</option>
    <option value="mastercard_8888">MC ···8888</option>
    <option value="cash">Cash on delivery</option>
  </select>

  <button type="submit">Place Order</button>
</form>

Fíjate en que el formulario no tiene toolautosubmit. Es checkout: el navegador parará el envío para que el usuario confirme con un clic en el botón. Si fuese un buscador de restaurantes, le habríamos puesto toolautosubmit para que el agente lo enviase sin pausa.

El submit handler usa dos propiedades WebMCP del SubmitEvent:

form.addEventListener('submit', (e) => {
  e.preventDefault();

  // Validación: ¿hay items en el carrito?
  if (state.cart.length === 0) {
    if (e.agentInvoked) {
      e.respondWith(Promise.resolve({
        success: false,
        error: "Cart is empty. Add items first."
      }));
    }
    return;
  }

  // Construir objeto de orden
  const order = {
    id: 'ME-' + Math.random().toString(36).substring(2, 8).toUpperCase(),
    items: state.cart.map(c => ({
      name: c.item.name, qty: c.qty,
      price: c.item.price, restaurant: c.restaurant.name
    })),
    total: totals.total + parseFloat(data.tip_amount || 0),
    status: "confirmed",
    placed_at: new Date().toISOString()
  };

  // Actualizar UI (efecto colateral)
  showOrderConfirmation(order);

  // Si lo invocó el agente, devolver JSON estructurado
  if (e.agentInvoked) {
    e.respondWith(Promise.resolve({
      success: true,
      message: `Order ${order.id} placed`,
      order
    }));
  }
});

e.agentInvoked es un booleano que indica si el submit lo disparó un agente (true) o un humano (false/undefined). El mismo formulario sirve para ambos casos con la misma lógica.

e.respondWith(Promise) devuelve un resultado estructurado al agente. Sin esto, el agente no recibiría feedback de éxito o error. El patrón está inspirado en Service Workers (FetchEvent.respondWith).

5. Eventos WebMCP y feedback visual

El código escucha dos eventos que el navegador dispara cuando un agente interactúa con las tools:

// Se dispara cuando el agente activa una tool
window.addEventListener('toolactivated', ({ toolName }) => {
  agentLog(`🤖 Agent activated: "${toolName}"`);
});

// Se dispara cuando el agente cancela la invocación
window.addEventListener('toolcancel', ({ toolName }) => {
  agentLog(`❌ Agent cancelled: "${toolName}"`);
});

Y las pseudo-clases CSS cierran el loop de human-in-the-loop:

/* Se activa cuando el agente está operando el formulario */
form:tool-form-active {
  outline: var(--orange) dashed 2px !important;
  outline-offset: 4px;
}

/* Se activa cuando el agente está a punto de hacer submit */
button:tool-submit-active {
  outline: var(--green) dashed 2px !important;
  outline-offset: 2px;
}

El humano que observa la pantalla ve exactamente cuándo un agente está operando el formulario y puede intervenir si algo no cuadra.

6. Flujo completo de un pedido

Así encadena un agente las 9 tools para completar un pedido. Cada paso es una invocación independiente: el agente decide la secuencia basándose en las descripciones de las tools.

1. 🔍 search_restaurants     query: "japanese"           → restaurant IDs
                                  ↓ el agente usa el ID
2. 📋 get_menu               restaurant_id: "koji_ramen" → items + precios
                                  ↓ el agente elige items
3. 🛒 add_to_cart            item_id: "tonkotsu", qty: 2 → confirma added
                                  ↓ opcionalmente aplica promo
4. 🏷️ apply_promo_code       code: "WELCOME20"           → descuento aplicado
                                  ↓ verifica antes de pagar
5. ✅ get_cart                (sin params)                → snapshot completo
                                  ↓ rellena form y submit
6. 📝 checkout (form)        delivery_address + tip + pay → order ID + ETA
                                  ↓ opcionalmente consulta
7. 📦 get_order_status       (sin params)                → estado actual

La cadena de datos fluye así: cada tool devuelve IDs que el siguiente paso necesita. search_restaurants devuelve restaurant_id, que get_menu consume para devolver item_id, que add_to_cart usa para poblar el carrito que checkout finaliza.

Actualización de mayo de 2026: lo que ha cambiado en la API

El 18 de mayo de 2026, el equipo de Chrome publicó la segunda tanda fuerte de cambios sobre WebMCP. Hay novedades que afectan a cómo escribes las tools, cómo se gestiona su ciclo de vida y a partir de cuándo vas a poder probarlo fuera de un flag de Chrome.

Resumen rápido antes de meternos en harina:

  • Origin Trial confirmado en Chrome 149. Hasta ahora solo había modo flag para desarrollo local. Pronto llegará el primer hueco para usarlo en producción acotada.
  • API simplificada. unregisterTool(), provideContext() y clearContext() se han ido. En su lugar, el patrón oficial es AbortController para registrar y retirar tools.
  • Nueva Permissions Policy tools. Por defecto vale self. Si quieres exponer tools desde un iframe cross-origin, vas a tener que añadir allow="tools" al iframe.
  • toolautosubmit para formularios. Un atributo que decide si el agente puede enviar el formulario sin esperar acción humana. Es la pieza clave para la confianza en flujos críticos.
  • Solo se soportan Tools. Las primitivas Resources y Prompts del MCP de backend no forman parte de WebMCP por ahora.
  • Inspector en Chrome Web Store. La extensión Model Context Tool Inspector ya está publicada oficialmente y usa gemini-3-flash-preview para hablar contigo en lenguaje natural.

🔑 La idea de fondo no cambia: una web que expone acciones estructuradas a agentes. Lo que cambia es cómo se escribe ese contrato y cómo se gestiona mientras la página vive.

El nuevo ciclo de vida con AbortController

Antes podías llamar a clearContext() para borrar todas las tools de golpe, o gestionar el alta y baja con un puñado de métodos. Esa API ha desaparecido. Ahora cada registerTool() admite un segundo argumento con un AbortSignal, y abortar ese signal es la única vía oficial para quitar la tool del registro.

El patrón típico queda así:

// Crear un controller específico para esta tool
const controller = new AbortController();

navigator.modelContext.registerTool({
  name: "get_user_preferences",
  description: "Retrieves the user's saved preferences.",
  inputSchema: { type: "object", properties: {} },
  execute() {
    const prefs = localStorage.getItem("user_prefs");
    return prefs ? JSON.parse(prefs) : { theme: "light" };
  },
  // El hint le dice al agente que esta tool no muta estado
  annotations: { readOnlyHint: true }
}, { signal: controller.signal });

// Desregistrar la tool (por ejemplo, al desmontar el componente)
controller.abort();

Si trabajas con React, Vue o Svelte, esto encaja bien con el ciclo de vida del componente. En el onMount registras las tools del contexto actual con su controller. En el onUnmount haces controller.abort() y todo queda limpio.

Es un patrón más alineado con el resto de APIs modernas del navegador, donde AbortController ya gobierna fetch, eventos del DOM y muchas más cosas.

toolautosubmit: el control de confianza para formularios

Esta es probablemente la novedad más interesante del lado declarativo. Cuando defines un formulario como tool y le pones el atributo toolautosubmit, el navegador deja que el agente complete y envíe el formulario sin pausar para que el usuario confirme.

Cuando lo omites, el navegador para la ejecución antes del submit, marca el botón con la pseudo-clase :tool-submit-active y espera la acción humana.

<form toolname="search_flights"
      tooldescription="Search flights by origin, destination and date"
      toolautosubmit>
  <!-- Búsqueda: operación inocua y reversible -->
  <label>Origen <input name="origin" required></label>
  <label>Destino <input name="destination" required></label>
  <label>Fecha <input type="date" name="date" required></label>
  <button type="submit">Buscar</button>
</form>

<form toolname="confirm_purchase"
      tooldescription="Place the final order with the selected payment method">
  <!-- Sin toolautosubmit: el usuario tiene que pulsar el botón final -->
  <label>Dirección <input name="address" required></label>
  <label>Tarjeta <input name="card" required></label>
  <button type="submit">Pagar</button>
</form>

La guía oficial es bastante clara sobre cuándo activarlo:

Activa toolautosubmit cuando:

  • Es una operación de solo lectura: búsquedas, filtros, consultas de estado.
  • La acción es reversible: añadir al carrito, aplicar un cupón, guardar un borrador, cambiar opciones temporales de layout.

Omítelo cuando:

  • Es destructiva o irreversible (borrar registros, resetear configuraciones).
  • Hay dinero o transacciones de por medio (checkout, transferencias, suscripciones).
  • Comunica con otros usuarios reales (envío de mensajes, publicación pública).
  • Toca ajustes sensibles de cuenta (cambio de contraseña, permisos, datos de facturación).

🛡️ Resumen para grabarlo a fuego: si la acción se puede deshacer en dos clics, deja que el agente la complete. Si no, obliga a que un humano pulse el botón.

toolparamdescription en cualquier campo, con orden de resolución claro

Antes solo se mencionaba en formularios. Ahora la guía detalla el orden de resolución que sigue el navegador cuando genera la descripción de cada parámetro:

  1. El atributo toolparamdescription si está presente en el input, select, textarea o fieldset.
  2. El textContent del <label> asociado (saltando descendientes labelables).
  3. El aria-description como último recurso.

Para grupos de campos relacionados (como un grupo de radio buttons), el toolparamdescription va en el <fieldset> padre y aplica al grupo completo.

<form toolname="schedule_demo" tooldescription="Schedule a product demo">
  <fieldset toolparamdescription="Preferred meeting format">
    <legend>Formato</legend>
    <label><input type="radio" name="format" value="video"> Videollamada</label>
    <label><input type="radio" name="format" value="onsite"> Presencial</label>
    <label><input type="radio" name="format" value="phone"> Teléfono</label>
  </fieldset>
</form>

La nueva Permissions Policy tools

WebMCP vive ahora detrás de una Permissions Policy. Por defecto vale self, lo que significa que el sitio principal puede registrar tools y exponerlas, pero los iframes de origen cruzado no.

Si tienes una arquitectura de microfrontends donde cargas widgets de otros dominios, vas a tener que añadir allow="tools" al iframe correspondiente para que sus tools sean visibles al agente.

<!-- Iframe cross-origin con WebMCP habilitado -->
<iframe src="https://widget.example.com/booking" allow="tools"></iframe>

Es una decisión sensata desde el punto de vista de seguridad: por defecto, ningún script de terceros puede colar tools en tu página solo por estar embebido.

Las nuevas demos oficiales que merece la pena revisar

El repositorio webmcp-tools se ha actualizado con tres demos completas, cada una pensada para un escenario distinto:

  • WebMCP Pizza Maker (vía imperativa): caso clásico de configurador con muchas opciones que el agente puede recorrer paso a paso.
  • Travel demo en React (vía imperativa): muestra cómo encajar WebMCP en una SPA con estado complejo y enrutado.
  • Le Petit Bistro (vía declarativa): caso prácticamente sin JavaScript de orquestación, basado en formularios HTML bien anotados.

Si vienes del ejemplo de Midnight Eats que veremos más adelante en este post, te recomiendo abrir Le Petit Bistro y comparar. Verás cuánta lógica te ahorras cuando los formularios ya están bien construidos.

Inspector en la Chrome Web Store

La extensión Model Context Tool Inspector ya está publicada oficialmente. Se llama exactamente así, Model Context Tool Inspector, y la puedes instalar desde la Chrome Web Store.

Te da tres cosas útiles:

  • Vista de las tools registradas en la página actual, con su nombre, descripción y esquema.
  • Invocación manual de cualquier tool con JSON de entrada, para probar sin necesidad de un agente real.
  • Chat en lenguaje natural contra un agente que por defecto usa gemini-3-flash-preview. Esto es separado de la integración Gemini in Chrome (gemini-3-auto-browse), así que no las confundas.

Si estás construyendo tools para tu propia web, esta extensión es ahora la forma más directa de validar que el agente las entiende. La descripción que escribas en tooldescription es lo que el modelo ve. Si suena ambigua, lo notarás aquí antes que en producción.

Solo Tools: nada de Resources ni Prompts (por ahora)

Pequeña aclaración importante. El protocolo MCP original tiene tres primitivas: Tools, Resources y Prompts. WebMCP, en su versión actual, solo soporta Tools. Si vienes del mundo MCP de backend y estabas pensando en exponer recursos legibles o prompts predefinidos, todavía no es el momento.

Esto puede cambiar. El borrador del W3C deja la puerta abierta, pero hoy lo único que vive en navigator.modelContext son tools.

Buenas prácticas oficiales que conviene fijar

La guía publicada incluye una serie de recomendaciones que merece la pena interiorizar antes de empezar a sembrar tools por tu aplicación:

  • Nombra con verbos específicos. create-event es mejor que start-event-creation-process. Los nombres largos generan ambigüedad.
  • Acepta entrada en crudo. No pidas al agente que calcule precios, sumas o porcentajes. Tu código lo hace mejor.
  • Tools atómicas y componibles. Cada tool debe hacer una cosa. Evita la tentación de meter flujos enteros en una sola tool.
  • No fuerces flujos al agente. Frases como “no llames a B después de A” en la descripción son ruido. Deja que el modelo decida.
  • Registra y retira según el contexto. Si tu app tiene rutas, vincula las tools al AbortController de cada ruta.
  • Errores descriptivos y recuperables. Si una tool falla, devuelve un mensaje que permita al agente reintentar con datos correctos.
  • Marca como readOnlyHint lo que no muta. Las tools de lectura pura son seguras de invocar y el agente necesita saberlo.

💡 Si solo te llevas una idea de toda esta actualización: el patrón mental ya no es “registro tools globales y luego limpio”. Es “cada parte de mi app expone sus tools mientras está viva, y las retira con AbortController cuando deja de estar viva”.

Con esto fresco, sigamos con la base de WebMCP. Lo que viene a continuación es el cuerpo del post original, con la idea y los ejemplos que ya estaban en febrero. Cuando veas provideContext() o clearContext() en código antiguo de la red, recuerda: sustitúyelo por el patrón de AbortController que acabas de ver arriba.

Lo que todavía no está resuelto

Sería injusto hablar de WebMCP sin poner encima de la mesa sus agujeros. Y los tiene, bastantes.

No hay descubrimiento de tools sin visitar la página. Un agente no puede saber qué ofrece un sitio hasta que navega a él. No existe (aún) algo como un manifiesto o un .well-known/webmcp.json que permita indexar las capacidades de un sitio. Esto limita mucho los escenarios de automatización a gran escala.

No funciona en modo headless. Las tools viven en el contexto de una pestaña visible. Si tu caso de uso requiere ejecución sin interfaz gráfica, WebMCP no es tu herramienta. Al menos no hoy.

El riesgo de abuso es real. En foros como Hacker News se ha debatido qué pasa cuando agentes ejecutan acciones de forma masiva. ¿Quién paga la factura del backend? ¿Cómo limitas el uso? Las anotaciones de seguridad del borrador son orientativas, no hay enforcement fuerte.

Las SPAs necesitan refactorización. Si tu aplicación tiene la lógica acoplada al estado de los componentes de React, Vue o Angular, exponer tools útiles va a requerir separar esa lógica en una capa de servicios reutilizable. No es trivial en aplicaciones grandes.

Sin Resources ni Prompts. Solo Tools. Si vienes de MCP de backend pensando en exponer un catálogo de recursos o prompts, hoy no es el momento.

⚠️ WebMCP es una apuesta de futuro, no una solución de presente. Pruébalo, experimenta con él, pero no lo metas en producción sin tener muy claro el alcance. Todavía no.

Qué significa esto para los developers

Si trabajas construyendo aplicaciones web, WebMCP te pide que empieces a pensar en tus interfaces de una forma nueva. No solo como algo que un humano ve y usa, sino como algo que un agente puede entender e invocar. Es una de las competencias clave que necesitan los programadores en la era de los agentes de IA.

Esto no significa reescribir todo. De hecho, si tu aplicación está bien diseñada, es probable que ya tengas medio camino hecho. Un formulario con campos bien nombrados, un API REST con endpoints claros, una separación razonable entre lógica y presentación… todo eso es terreno fértil para WebMCP.

Lo que sí cambia es la mentalidad. Hasta ahora diseñábamos pensando en un humano que lee, interpreta y decide. Ahora hay que añadir un segundo “usuario” que no ve colores, no entiende layouts y no hace scroll. Pero que es muy bueno leyendo JSON Schema y ejecutando callbacks.

Tres preguntas para empezar:

  1. ¿Qué acciones de tu aplicación podrían exponerse como tools? Piensa en los formularios de búsqueda, los flujos de reserva, los paneles de configuración. Cualquier acción estructurada con entradas claras y salidas definidas es candidata.
  2. ¿Tu lógica de negocio vive en el frontend o en el backend? Si todo está en componentes de UI, vas a necesitar extraerlo a funciones o servicios que las tools puedan llamar sin depender del estado visual. Es un buen ejercicio de arquitectura con o sin WebMCP.
  3. ¿Dónde necesitas al humano en el proceso? No todo debe ser automático. Una búsqueda puede ser libre. Una compra necesita confirmación. Un borrado de datos necesita doble confirmación. Identifica esos puntos y diseña tus tools en consecuencia: con toolautosubmit para lo seguro, sin él para lo crítico.

La ruta de migración si ya usas MCP

Si ya tienes tools expuestas a través de MCP en el servidor, la migración no es un “todo o nada”. Las fuentes oficiales coinciden en un enfoque progresivo:

  1. Haz inventario de tus tools MCP actuales. El contrato fundamental (nombre, descripción, JSON Schema de entrada) es el mismo en ambos mundos.
  2. Decide qué vive en cada sitio. Tools que necesitan la sesión del usuario y confirmación visual van a WebMCP. Tools que funcionan sin navegador o necesitan ejecución autónoma se quedan en MCP. Y si quieres que devuelvan interfaces interactivas dentro del chat, las MCP Apps con frameworks como mcp-use son el camino.
  3. Empieza por los formularios. Si tienes formularios HTML bien estructurados, añadir atributos declarativos es el cambio de menor coste. “Progressive enhancement” puro.
  4. Separa la lógica de la UI. Si tu frontend es una SPA con estado acoplado, este es el momento de extraer servicios reutilizables. Tus tools WebMCP los necesitarán.
  5. Adopta el patrón AbortController desde el día uno. Vincula cada tool al ciclo de vida del componente o ruta que la expone. Te ahorrarás dolores futuros.
  6. Valida con el inspector. La extensión Model Context Tool Inspector está ya en la Chrome Web Store. Úsala para verificar que tus tools se registran bien y que el agente las invoca como esperas.

Quién está detrás y qué peso tiene

El anuncio oficial de febrero de 2026 salió desde Chrome for Developers. La actualización del 18 de mayo de 2026 vino firmada por Alexandra Klepper en el mismo canal. El borrador del W3C Community Group tiene como editores a ingenieros de Google y Microsoft. Eso da peso, pero hay matices.

No es un estándar del W3C. No está en la “Standards Track”. Es un borrador de grupo comunitario, que es el nivel más bajo de formalización dentro del ecosistema W3C. Puede evolucionar hacia algo más sólido o puede quedarse en el camino.

No hay señales públicas de Firefox o Safari aún. Eso no significa que no estén mirando, pero significa que hoy esto es territorio Chromium (Chrome y Edge).

La cobertura mediática ha sido constante. InfoWorld publicó un resumen en febrero. La comunidad de DEV Community reaccionó desde el primer día. Y en WeAreDevelopers hay un vídeo donde los propios autores del borrador explican la propuesta.

Lo que viene

WebMCP todavía es un boceto con bordes difusos. Pero el trazo de fondo es claro: la web necesita una forma nativa de comunicarse con agentes de IA, y el enfoque de “simular lo que haría un humano” tiene los días contados.

Lo más interesante de esta propuesta no es la API en sí. Es el cambio de perspectiva. Durante años hemos construido webs pensando en que las usarían personas. Luego llegaron los bots de scraping y los motores de búsqueda, y aprendimos a hacer SEO. Después vinieron las APIs REST y GraphQL, y aprendimos a separar frontend de backend.

Ahora llegan los agentes de IA. Y la web tiene que adaptarse una vez más.

La diferencia es que esta vez no se trata de optimizar para un crawler o de diseñar endpoints elegantes. Se trata de que tu interfaz web pueda mantener una conversación con un agente, decirle qué sabe hacer, aceptar peticiones y devolver resultados. Es una evolución natural, aunque el camino todavía esté lleno de baches.

Si eres de los que disfrutan probando cosas antes de que estén listas, este es tu momento. Con Chrome 149 a la vuelta de la esquina y el Origin Trial confirmado, ya no hay excusa: instala la extensión del Chrome Web Store, monta un formulario con toolautosubmit y toolparamdescription, y mira cómo un agente lo invoca sin tocar el DOM.

Y si quieres que sea tu agente de IA quien escriba ese código siguiendo el patrón actual de AbortController, toolautosubmit y compañía, el equipo de Chrome ya lo tiene empaquetado: dentro de Modern Web Guidance hay una skill oficial específica para webmcp que enseña a Claude Code, GitHub Copilot CLI o Antigravity a exponer tools imperativas, marcar formularios como agentic-friendly y gestionar la autorización del usuario. Una instrucción de instalación y tu agente deja de inventarse la API.

Y si prefieres esperar a que la cosa madure, anota esto en tu libreta de las ideas (en la Moleskine, si puede ser): los agentes van a ser ciudadanos de primera en la web. La pregunta no es si tu aplicación tendrá que hablar con ellos, sino cuándo.

Referencias

Experimenta. Rompe cosas. Y cuando algo no funcione, lee el error desde el principio, no desde el final.

Imagen de Daniel Primo
Claude, IA de Anthropic

Escrito con la ayuda de la IA generativa de Claude, fuentes fidedignas y con un human in the loop:
Dani Primo.

CEO en pantuflas de Web Reactiva. Programador y formador en tecnologías que cambian el mundo y a las personas. Activo en linkedin, en substack y canal @webreactiva en telegram

12 recursos para developers cada domingo en tu bandeja de entrada

Además de una skill práctica bien explicada, trucos para mejorar tu futuro profesional y una pizquita de humor útil para el resto de la semana. Gratis.