Inteligencia Artificial

WebMCP: la API que deja a tu web ofrecer herramientas a los agentes

Autorangel cruz
Publicado
Lectura9 min de lectura
WebMCP: la API que deja a tu web ofrecer herramientas a los agentes

Hoy, cuando un agente de IA usa tu web, adivina. Mira el DOM, decide que ese <button> probablemente envía el formulario, hace clic y espera que salga bien. WebMCP propone lo contrario: que la propia página registre herramientas con nombre, descripción y esquema de entrada, y que el agente llame a esas funciones en vez de simular clics.

Es una propuesta de estándar web que Google y Microsoft están incubando en el Web Machine Learning Community Group del W3C, y desde Chrome 149 se puede probar en un origin trial. Aquí va lo que dicen las fuentes originales: la especificación, el explainer y la documentación de Chrome.

Qué es WebMCP exactamente

Una sola cosa nueva en el DOM: document.modelContext. Desde ahí registras tools, que son funciones JavaScript con un JSON Schema de entrada y una descripción en lenguaje natural. Un agente que esté operando en esa pestaña las descubre y las invoca.

El vocabulario viene de MCP: tools, schemas, parámetros. Pero el explainer es explícito en que WebMCP no reemplaza a MCP. MCP conecta un modelo con servidores; WebMCP vive dentro de una página, con las reglas del navegador (orígenes, permissions policy, DOM) y reutilizando la lógica de cliente que ya escribiste.

Los objetivos declarados en el explainer son estos, y el tercero es el más interesante para quien tiene un producto web:

  • Flujos con humano en el bucle, donde el usuario ve la página y mantiene el control.
  • Integración de agentes sin automatización frágil de UI.
  • Evitar la desintermediación: que tu frontend se adapte a los agentes en lugar de que lo salteen con una integración de backend.
  • Reutilizar la lógica de cliente que ya tienes.
  • Accesibilidad: un agente como intermediario capaz para quien usa tecnología asistiva.

Y los no objetivos importan igual: no está pensado para navegación headless, ni para flujos totalmente autónomos sin humano, ni para reemplazar la interfaz humana. Las tools se suman a la UI, no la sustituyen.

En qué estado está de verdad

Aquí es donde conviene ser preciso, porque circula mucho artículo que lo presenta como si ya fuera estándar.

  • La especificación es un Draft Community Group Report, con fecha del 26 de agosto de 2026. No es un estándar del W3C. Los editores son Brandon Walderman (Microsoft), Khushal Sagar y Dominic Farolino (Google).
  • En Chrome hubo developer trial desde la 146 y el origin trial va de Chrome 149 a Chrome 156. El estado de la feature en Chrome Platform Status sigue siendo "Proposed", y la review del TAG está pendiente: la piden antes de shippear.
  • Firefox y Safari: sin señal. Ninguno de los dos ha publicado una posición.

O sea: es real, es probable, es escribible hoy, y no está garantizado. Si lo adoptas ahora, lo haces como progressive enhancement detrás de un if ('modelContext' in document), no como camino crítico.

La API imperativa

El caso normal. Registras una tool con registerTool:

await document.modelContext.registerTool({
  name: 'toggle_layer',
  description: 'Control pizza layers (sauce, cheese). Use "add", "remove", or "toggle".',
  inputSchema: {
    type: 'object',
    properties: {
      layer: { type: 'string', enum: ['sauce-layer', 'cheese-layer'] },
      action: { type: 'string', enum: ['add', 'remove', 'toggle'] },
    },
    required: ['layer'],
  },
  execute: async ({ layer, action }) => {
    await toggleLayer(layer, action);
    return `Performed ${action || 'toggle'} on layer: ${layer}`;
  },
});

Cuatro piezas: name, description, inputSchema en JSON Schema y execute. La especificación limita el nombre a 128 caracteres alfanuméricos ASCII con guiones, guiones bajos y puntos, y exige que la descripción no esté vacía.

Fíjate en el return: la tool devuelve una frase, no un objeto opaco. Ese texto es lo que el modelo lee para saber si funcionó, así que escríbelo pensando en que lo va a leer un modelo.

execute recibe además un AbortSignal como segundo argumento, que es lo que te permite cancelar trabajo largo cuando el agente abandona:

execute: async ({ url, priority }, { signal }) => {
  const response = await fetch(url, { priority, signal });
  const stream = response.body.pipeThrough(new TextDecoderStream());
  for await (const chunk of stream) {
    document.querySelector('pre').textContent += chunk;
  }
  return 'Success';
}

Para desregistrar una tool, otro AbortController, esta vez en las opciones del registro:

const controller = new AbortController();
await document.modelContext.registerTool(addTodoTool, { signal: controller.signal });
controller.abort(); // adiós tool

Y el resto de la superficie: getTools() lista lo registrado en el documento y sus descendientes, executeTool() ejecuta una tool en su documento de origen, y el evento toolchange avisa cuando algo se registra o se va.

document.modelContext.addEventListener('toolchange', () => {
  // la lista de tools cambió
});

Si trabajas con tipos, existe el paquete webmcp-types en npm. Para React hay hooks experimentales en usewebmcp, y Angular tiene soporte experimental propio.

La API declarativa: formularios anotados

La segunda vía no lleva JavaScript. Anotas un formulario que ya existe y se convierte en una tool:

<form toolname="supportRequestTool"
      tooldescription="Submit a request for support."
      action="/submit">
  <label for="firstName">First Name</label>
  <input type=text name=firstName>
 
  <select name="select" required
    toolparamdescription="Determines what team this request is routed to.">
    <option value="Customer happiness team">Return my purchase.</option>
    <option value="Distribution team">Check where my package is.</option>
  </select>
 
  <button type=submit>Submit</button>
</form>

toolname y tooldescription sobre el <form>, toolparamdescription sobre cada campo que necesite explicación. Los name de los inputs se convierten en las propiedades del schema.

Con toolautosubmit el formulario se envía solo cuando lo invoca un agente, y el SubmitEvent trae dos cosas nuevas para que distingas quién lo disparó y devuelvas un resultado:

<form toolautosubmit toolname="search_tool"
      tooldescription="Search the web" action="/search">
  <input type=text name=query>
</form>
 
<script>
  document.querySelector("form").addEventListener("submit", (e) => {
    e.preventDefault();
    if (!myFormIsValid()) {
      if (e.agentInvoked) { e.respondWith(myFormValidationErrorPromise); }
      return;
    }
    if (e.agentInvoked) { e.respondWith(Promise.resolve("Search is done!")); }
  });
</script>

e.agentInvoked es booleano y e.respondWith(promise) devuelve el resultado al modelo. Para un formulario de contacto o una búsqueda, esto es todo el trabajo.

Seguridad: lo que hay que leer antes de registrar nada

WebMCP amplía la superficie de ataque de tu web de una forma nueva, y la especificación lo asume por escrito. Documenta tres vectores de prompt injection: por metadatos (nombre y descripción de la tool), por la salida de la tool y por la implementación misma.

Las defensas que trae la plataforma:

  • Aislamiento de origen obligatorio. WebMCP solo funciona en documentos con origin isolation. Si usas document.domain o mandas Origin-Agent-Cluster: ?0, queda deshabilitado.
  • Permissions Policy. Está detrás de la policy tools, con allowlist por defecto ['self']. Un iframe cross-origin necesita allow="tools" explícito.
  • exposedTo. Al registrar puedes limitar qué orígenes ven esa tool: { exposedTo: ['https://example.com'] }.
  • Anotaciones. readOnlyHint marca una tool que no muta nada y untrustedContentHint marca que lo que devuelve viene de contenido no confiable, para que el agente lo trate como datos y no como instrucciones.

La regla mental es la de siempre con agentes: la tool es una API pública más. Lo que no expondrías en un endpoint sin autorización, no lo expongas como tool. Si te interesa el lado feo de esto, escribí sobre servidores MCP maliciosos y sobre OAuth para agentes.

Buenas prácticas, según Chrome

La documentación de Chrome tiene una guía de best practices y hay cuatro ideas que valen más que el resto:

Nombra distinguiendo ejecutar de iniciar. create-event para lo que crea el evento; start-event-creation-process si lo que haces es llevar al usuario a un formulario. El agente decide por el verbo.

Describe en positivo. En vez de "no uses esta tool para el clima", escribe "esta tool crea un evento de calendario para una fecha y hora concretas". Las limitaciones se deducen de una buena descripción.

Una función por tool, y registro estático por defecto. Nada de tools que se solapan. El registro dinámico existe, pero úsalo con moderación.

Valida estricto en el código, laxo en el schema. El schema no está garantizado; lo que sí funciona es devolver un error descriptivo que el modelo pueda leer y corregir. Y acepta la entrada cruda del usuario en lugar de pedirle al modelo que haga cálculos previos.

Sobre pruebas, la recomendación es eval-driven: defines un baseline, un resultado ideal y evalúas cualitativamente, en lugar de tests unitarios rígidos.

Cómo probarlo hoy

Para desarrollo local basta el flag chrome://flags/#enable-webmcp-testing. Para servirlo a usuarios reales necesitas registrarte en el origin trial desde developer.chrome.com/origintrials, que corre hasta Chrome 156.

Para inspeccionar hay una extensión, el Model Context Tool Inspector, que lista las tools registradas, te deja ejecutarlas a mano, valida el JSON Schema y permite probar con lenguaje natural. Y en GoogleChromeLabs/webmcp-tools están las demos de referencia: un Pizza Maker y un viaje en React con la API imperativa, y Le Petit Bistro con la declarativa.

Lo que todavía no está decidido

El explainer mantiene una lista abierta que conviene mirar antes de construir encima: soporte multimodal (entradas y salidas binarias), qué pasa con la respuesta de una tool si la página navega, qué ven por defecto los agentes integrados del navegador, streaming de entradas y salidas grandes, validación de schemas, agrupación de tools en "skills", reporte de progreso en tareas largas e integración con service workers para descubrir tools sin abrir el sitio.

Esa última es la que más cambia el juego: hoy la única forma de descubrir tus tools es que alguien visite tu página. Mientras eso siga así, WebMCP mejora la sesión de un agente que ya llegó, pero no te trae agentes nuevos. Para lo otro están las vías de descubrimiento que ya existen, como content negotiation o DNS.

Preguntas frecuentes

¿WebMCP reemplaza a MCP?

No, y el explainer lo dice como no objetivo explícito. MCP conecta un modelo con servidores externos; WebMCP expone tools dentro de una página, con las reglas del navegador. Un producto puede tener los dos: un servidor MCP para integraciones de backend y tools WebMCP para la sesión del usuario en el navegador.

¿Funciona ya en producción?

Solo en Chrome y solo dentro del origin trial, que va de la versión 149 a la 156. Firefox y Safari no han dado señal. Regístralo detrás de una comprobación de capacidad y trata la UI humana como el camino principal.

¿Necesito escribir JavaScript para usar WebMCP?

No siempre. Si lo que quieres exponer es un formulario, la API declarativa te lo resuelve con toolname, tooldescription y toolparamdescription en el HTML. El JavaScript hace falta cuando la tool no es un envío de formulario o cuando quieres controlar el resultado que ve el modelo.

¿Cualquier página puede ver mis tools?

No. La policy tools tiene allowlist ['self'] por defecto, así que un iframe cross-origin necesita allow="tools", y al registrar puedes acotar con exposedTo. Además el documento tiene que estar aislado por origen: con document.domain o Origin-Agent-Cluster: ?0 la API queda deshabilitada.

¿WebMCP es un estándar del W3C?

Todavía no. Es un Draft Community Group Report del Web Machine Learning Community Group, que es la fase de incubación. Chrome lo tiene como "Proposed" y la review del TAG está pendiente.

¿Sirve para agentes headless o scraping?

No es su caso de uso. El explainer excluye explícitamente la navegación headless y los flujos totalmente autónomos: está diseñado para un navegador local con un humano supervisando.

Fuentes