Saltar al contenido
schedule 12 min JavaScript

WebMCP: JS para agentes de IA

En la sección de HTML aprendiste a hacer tus formularios compatibles con agentes de IA usando atributos como toolname y tooldescription. Eso era el enfoque declarativo: describes la herramienta directamente en el HTML y el agente interpreta los campos del formulario.

Pero, ¿qué pasa cuando necesitas algo más potente? ¿Cuando la herramienta que quieres exponer no es un simple formulario, sino lógica compleja que filtra datos, calcula rutas, consulta un inventario en memoria o manipula el DOM? Para eso existe la API imperativa de WebMCP, y se controla enteramente con JavaScript.

Estado actual: WebMCP es una tecnología experimental disponible en Chrome Canary (versión 146+). No es un estándar consolidado del W3C todavía — Google lo está desarrollando activamente y la API puede cambiar. Pero aprenderlo ahora te coloca en la vanguardia absoluta del desarrollo web en 2026.

La API navigator.modelContext

Toda la API imperativa de WebMCP vive en navigator.modelContext. Es el punto de entrada para registrar herramientas que los agentes de IA pueden descubrir y ejecutar cuando visitan tu página.

Lo primero que debes hacer antes de usarla es comprobar que el navegador la soporta:

app.js
if ("modelContext" in navigator) {
    console.log("WebMCP disponible — puedes registrar herramientas");
} else {
    console.log("WebMCP no disponible en este navegador");
}

Este patrón te resultará familiar: es la misma idea de feature detection que usarías para comprobar si el navegador soporta geolocalización (navigator.geolocation) o notificaciones. Si modelContext no existe, tu página sigue funcionando con normalidad — simplemente los agentes de IA no podrán usar tus herramientas imperativas.

registerTool(): registrar una herramienta

El método principal de la API es navigator.modelContext.registerTool(). Recibe un objeto con la definición de la herramienta: su nombre, descripción, los parámetros que acepta (usando JSON Schema) y una función handler que se ejecuta cuando el agente llama a la herramienta.

Veamos la estructura básica:

app.js
navigator.modelContext.registerTool({
    name: "nombre-de-la-herramienta",
    description: "Qué hace esta herramienta — el agente lee esto para decidir si usarla",
    parameters: {
        type: "object",
        properties: {
            parametro1: {
                type: "string",
                description: "Qué es este parámetro"
            },
            parametro2: {
                type: "number",
                description: "Qué es este otro parámetro"
            }
        },
        required: ["parametro1"]
    },
    handler: async ({ parametro1, parametro2 }) => {
        // Aquí va la lógica que se ejecuta cuando el agente llama la herramienta
        // Debe devolver un resultado que el agente pueda interpretar
        return { resultado: "datos que el agente recibe" };
    }
});

Vamos a desglosar cada parte:

  • name: un identificador único para la herramienta. Usa kebab-case descriptivo, como "buscar-productos" o "añadir-al-carrito".
  • description: la descripción en lenguaje natural. El agente la lee para decidir si tu herramienta sirve para lo que el usuario le ha pedido. Cuanto más clara y específica, mejor.
  • parameters: un esquema JSON Schema que define qué datos acepta la herramienta. El agente genera estos datos automáticamente a partir de lo que el usuario le pide.
  • handler: la función que se ejecuta. Recibe los parámetros ya validados y devuelve un resultado que el agente interpreta para responderle al usuario.

JSON Schema para los parámetros

Los parámetros usan JSON Schema, el mismo estándar que usan las APIs REST, OpenAPI y los modelos de lenguaje. Aquí van los tipos más comunes que usarás:

schemas.js
// String con opciones limitadas (enum)
{
    type: "string",
    description: "Categoría del producto",
    enum: ["mochilas", "organizadores", "fundas", "accesorios"]
}

// Número con rango
{
    type: "number",
    description: "Precio máximo en euros",
    minimum: 0,
    maximum: 1000
}

// Booleano
{
    type: "boolean",
    description: "Solo mostrar productos en oferta"
}

// Array de strings
{
    type: "array",
    description: "Colores preferidos",
    items: { type: "string" }
}

El campo required en el objeto parameters indica qué parámetros son obligatorios. Los que no estén en el array son opcionales — el agente puede omitirlos.

Ejemplo real: buscador de productos

Imagina que tienes una tienda online de equipo para nómadas digitales. En tu página tienes un catálogo de productos cargado en memoria (tal vez lo obtuviste de una API al cargar la página). Quieres que un agente de IA pueda buscar en ese catálogo.

tienda.js
// Catálogo de productos (en una app real, esto vendría de una API)
const productos = [
    { id: 1, nombre: "Mochila Urban Explorer", categoria: "mochilas", precio: 89.99, enOferta: false },
    { id: 2, nombre: "Organizador de cables TechPouch", categoria: "organizadores", precio: 24.99, enOferta: true },
    { id: 3, nombre: "Funda portátil SlimShield 15\"", categoria: "fundas", precio: 39.99, enOferta: false },
    { id: 4, nombre: "Mochila Nomad Pro 40L", categoria: "mochilas", precio: 149.99, enOferta: true },
    { id: 5, nombre: "Hub USB-C 7 en 1", categoria: "accesorios", precio: 34.99, enOferta: false },
    { id: 6, nombre: "Funda tablet AquaGuard", categoria: "fundas", precio: 19.99, enOferta: true },
    { id: 7, nombre: "Organizador viaje PackMaster", categoria: "organizadores", precio: 44.99, enOferta: false },
    { id: 8, nombre: "Candado TSA SmartLock", categoria: "accesorios", precio: 15.99, enOferta: false },
];

// Registrar herramienta de búsqueda
if ("modelContext" in navigator) {
    navigator.modelContext.registerTool({
        name: "buscar-productos",
        description: "Busca productos en el catálogo de NomadGear por texto, categoría, precio máximo y disponibilidad de oferta",
        parameters: {
            type: "object",
            properties: {
                busqueda: {
                    type: "string",
                    description: "Texto de búsqueda para filtrar por nombre del producto"
                },
                categoria: {
                    type: "string",
                    description: "Categoría del producto",
                    enum: ["mochilas", "organizadores", "fundas", "accesorios"]
                },
                precioMaximo: {
                    type: "number",
                    description: "Precio máximo en euros",
                    minimum: 0
                },
                soloOfertas: {
                    type: "boolean",
                    description: "Si es true, solo muestra productos en oferta"
                }
            },
            required: []
        },
        handler: async ({ busqueda, categoria, precioMaximo, soloOfertas }) => {
            let resultados = [...productos];

            if (busqueda) {
                const termino = busqueda.toLowerCase();
                resultados = resultados.filter(p =>
                    p.nombre.toLowerCase().includes(termino)
                );
            }

            if (categoria) {
                resultados = resultados.filter(p => p.categoria === categoria);
            }

            if (precioMaximo !== undefined) {
                resultados = resultados.filter(p => p.precio <= precioMaximo);
            }

            if (soloOfertas) {
                resultados = resultados.filter(p => p.enOferta);
            }

            return {
                total: resultados.length,
                productos: resultados.map(({ id, nombre, precio, enOferta }) => ({
                    id,
                    nombre,
                    precio: `${precio.toFixed(2)} €`,
                    enOferta
                }))
            };
        }
    });
}

Ahora un agente de IA que visite tu página puede entender: "Esta web tiene una herramienta para buscar productos. Acepta texto de búsqueda, categoría, precio máximo y si solo quieres ofertas". Si un usuario le dice al agente "Busca mochilas de menos de 100 euros", el agente llama a tu herramienta con {{ categoria: "mochilas", precioMaximo: 100 }} y recibe los resultados filtrados.

Ejemplo real: añadir al carrito

Buscar productos es solo la mitad. El usuario también podría decirle al agente: "Añade la mochila Urban Explorer al carrito". Para eso necesitas una segunda herramienta:

carrito.js
// Estado del carrito
const carrito = [];

if ("modelContext" in navigator) {
    navigator.modelContext.registerTool({
        name: "añadir-al-carrito",
        description: "Añade un producto al carrito de compra por su ID. El usuario debe confirmar antes de proceder al pago.",
        parameters: {
            type: "object",
            properties: {
                productoId: {
                    type: "number",
                    description: "ID del producto a añadir"
                },
                cantidad: {
                    type: "number",
                    description: "Cantidad de unidades a añadir",
                    minimum: 1,
                    maximum: 10
                }
            },
            required: ["productoId"]
        },
        handler: async ({ productoId, cantidad = 1 }) => {
            const producto = productos.find(p => p.id === productoId);

            if (!producto) {
                return {
                    exito: false,
                    error: `No se encontró ningún producto con ID ${productoId}`
                };
            }

            // Añadir al carrito
            const itemExistente = carrito.find(item => item.productoId === productoId);

            if (itemExistente) {
                itemExistente.cantidad += cantidad;
            } else {
                carrito.push({
                    productoId,
                    nombre: producto.nombre,
                    precioUnitario: producto.precio,
                    cantidad
                });
            }

            // Actualizar el DOM (mostrar el carrito actualizado)
            actualizarUICarrito();

            const total = carrito.reduce(
                (sum, item) => sum + item.precioUnitario * item.cantidad, 0
            );

            return {
                exito: true,
                mensaje: `${cantidad}x ${producto.nombre} añadido al carrito`,
                carrito: {
                    items: carrito.length,
                    total: `${total.toFixed(2)} €`
                }
            };
        }
    });
}

function actualizarUICarrito() {
    const badge = document.querySelector(".carrito-badge");
    if (badge) {
        const totalItems = carrito.reduce((sum, item) => sum + item.cantidad, 0);
        badge.textContent = totalItems;
        badge.hidden = totalItems === 0;
    }
}

Fíjate en varios detalles importantes:

  • La herramienta valida la entrada: comprueba que el producto existe antes de añadirlo.
  • Devuelve un objeto con exito: true/false para que el agente sepa si la operación funcionó.
  • Actualiza el DOM: el handler no solo modifica datos, también refleja el cambio en la interfaz. Así el usuario ve que algo pasó.
  • La description menciona que el usuario debe confirmar antes de pagar — esto orienta al agente sobre cómo comportarse.

Declarativo vs. imperativo: cuándo usar cada uno

Ahora que conoces ambos enfoques, la pregunta lógica es: ¿cuándo uso cada uno?

Situación Enfoque Por qué
Formulario de contacto Declarativo (HTML) Los campos del formulario ya describen los parámetros.
Buscador con filtros Declarativo (HTML) Un <form> con toolname y toolautosubmit es suficiente.
Filtrar datos en memoria Imperativo (JS) No hay formulario de por medio — es lógica pura de JavaScript.
Añadir items a un carrito Imperativo (JS) Requiere lógica: buscar producto, validar stock, actualizar estado.
Calcular precio con descuentos Imperativo (JS) Cálculos complejos que no se expresan con un formulario.
Controlar un reproductor multimedia Imperativo (JS) Interactúa con APIs del navegador (play, pause, seek).

La regla general: si la interacción es un formulario que se envía, usa el enfoque declarativo (HTML). Si es lógica que vive en JavaScript (filtros, cálculos, manipulación del DOM, estado en memoria), usa el enfoque imperativo.

Y nada te impide usar ambos en la misma página. Una tienda podría tener un formulario declarativo de búsqueda y herramientas imperativas para el carrito y las recomendaciones.

Consideraciones de seguridad

Cuando expones herramientas a agentes de IA, estás abriendo una puerta. Asegúrate de que sea una puerta controlada:

  • Valida siempre los inputs: no confíes en que el agente enviará datos correctos. Comprueba tipos, rangos y valores permitidos dentro del handler.
  • No expongas datos sensibles: tu herramienta no debería devolver tokens, contraseñas, datos personales de otros usuarios ni información interna del sistema.
  • Limita las acciones destructivas: si una herramienta puede eliminar datos o realizar pagos, tu descripción debe indicar claramente que requiere confirmación del usuario.
  • No registres herramientas que ejecuten código arbitrario: nunca crees una herramienta que acepte código JavaScript como parámetro y lo ejecute con eval().
  • Sanitiza lo que insertes en el DOM: si el handler actualiza la interfaz con datos que vienen del agente, usa textContent en lugar de innerHTML para evitar inyección de HTML.
seguridad.js
// MAL: insertar datos del agente directamente en el DOM
handler: async ({ busqueda }) => {
    document.querySelector(".resultados").innerHTML = `
        <p>Resultados para: ${busqueda}</p>
    `;
    // Si busqueda contiene HTML malicioso, se ejecutaría
}

// BIEN: usar textContent o sanitizar
handler: async ({ busqueda }) => {
    const titulo = document.querySelector(".resultados-titulo");
    titulo.textContent = `Resultados para: ${busqueda}`;
    // textContent escapa el HTML automáticamente
}

El handler puede ser asíncrono

El handler es una función async, lo que significa que puedes hacer peticiones a APIs externas, leer de localStorage, o ejecutar cualquier operación asíncrona antes de devolver el resultado:

herramienta-api.js
navigator.modelContext.registerTool({
    name: "consultar-stock",
    description: "Consulta el stock actual de un producto en tiempo real",
    parameters: {
        type: "object",
        properties: {
            productoId: {
                type: "number",
                description: "ID del producto"
            }
        },
        required: ["productoId"]
    },
    handler: async ({ productoId }) => {
        try {
            const response = await fetch(`/api/productos/${productoId}/stock`);

            if (!response.ok) {
                return { error: "No se pudo consultar el stock" };
            }

            const { stock, ultimaActualizacion } = await response.json();

            return {
                productoId,
                stock,
                disponible: stock > 0,
                ultimaActualizacion
            };
        } catch (error) {
            return { error: "Error de conexión al consultar el stock" };
        }
    }
});

El try/catch aquí es fundamental. Si el handler lanza un error no capturado, el agente no recibirá una respuesta útil. Siempre devuelve un objeto con información clara, incluso cuando algo falla.

El futuro: agentes navegando la web

WebMCP es una pieza de un cambio mucho más grande. En 2025-2026, los agentes de IA están empezando a navegar la web como lo hacen los humanos: visitan páginas, leen contenido, interactúan con formularios y ejecutan tareas.

Sin WebMCP, los agentes tienen que "adivinar" cómo funciona tu web analizando el DOM visualmente. Con WebMCP, les das un contrato claro: "Estas son las herramientas disponibles, esto es lo que acepta cada una, esto es lo que devuelven".

Es el mismo salto que supuso pasar de web scraping a APIs REST. Antes, si querías datos de una web, tenías que parsear el HTML y rezar para que no cambiara. Con una API, tienes un contrato estable. WebMCP es ese contrato, pero para agentes de IA que navegan la web.

Estamos hablando de tecnología de Chrome 146+ en 2026. No está en todos los navegadores todavía. Pero si la historia de la web nos enseña algo, es que las buenas ideas se estandarizan. Y exponer herramientas a agentes de IA de forma estructurada es una muy buena idea.

code

Registra herramientas WebMCP con JavaScript

Media schedule 20 min

Crea una página de una cafetería llamada "Café Nómada" que tenga dos herramientas WebMCP registradas con la API imperativa:

  1. buscar-menu: recibe un parámetro categoria (café, té, repostería, bocadillos) y opcionalmente precioMaximo. Filtra un array de productos en memoria y devuelve los resultados.
  2. hacer-pedido: recibe un productoId y una cantidad. Añade el item a un array de pedido, calcula el total y devuelve un resumen del pedido actual.

Asegúrate de:

  • Comprobar que navigator.modelContext existe antes de registrar
  • Validar los inputs dentro del handler (¿existe el producto? ¿la cantidad es positiva?)
  • Devolver objetos claros con exito: true/false
  • Actualizar el DOM cuando se añada un item al pedido
lightbulb Pistas

Empieza definiendo el array de productos del menú con id, nombre, categoría y precio. Luego registra las dos herramientas una por una. Para actualizar el DOM, crea una función actualizarPedido() que recorra el array del pedido y muestre los items en una lista. No olvides el try/catch en los handlers por si algo falla.

Newsletter

Recibe nuevos cursos, actualizaciones, artículos del blog y promociones en tu correo.