Saltar al contenido
Angular•9 min de lectura

WebMCP en Angular: tu app como herramienta para agentes

Tu aplicación Angular puede ahora registrar herramientas que un agente de IA descubre y ejecuta sin salir de la página. La feature se llama WebMCP, llegó como experimental en Angular v22 y convierte tu app en un pequeño servidor MCP que corre dentro del navegador.

Hoy los agentes "usan" tu app a la vieja usanza: leen el DOM, simulan clicks y rezan para que el selector no cambie. WebMCP propone algo más sano: tú declaras herramientas con nombre, descripción y schema JSON, y el agente las llama directamente. - Fuente: WebMCP Explainer

En este post veremos qué es WebMCP, las APIs que Angular v22 trae para usarlo, y los detalles que te van a ahorrar horas de debugging.

Qué es WebMCP

WebMCP es una propuesta de estándar en el W3C Web Machine Learning Community Group. El borrador más reciente es del 16 de octubre de 2026 y define una nueva propiedad: document.modelContext. - Fuente: WebMCP Draft Community Group Report

La idea en una frase: la página expone herramientas JavaScript con descripciones en lenguaje natural y JSON Schemas, y los agentes las descubren e invocan. Una página con WebMCP funciona como un servidor MCP que implementa sus tools en script del cliente, no en el backend.

El soporte de los navegadores va en camino:

  • Chrome 149 lo tiene en Origin Trial (desde junio de 2026). Para desarrollo local: chrome://flags/#enable-webmcp-testing.
  • Edge 150 también está en Origin Trial.
  • Firefox y Safari tienen issues de standards-position abiertos; nada implementado todavía.
  • ChatGPT Desktop ya invoca herramientas WebMCP, y Meta anunció soporte en Ray-Ban Display.
  • Fuente: WebMCP implementation status, Chrome Origin Trial

El API de plataforma es directo. Esto, en JavaScript puro, registra una herramienta:

// browser-console.js
await document.modelContext.registerTool({
  name: "add-todo",
  description: "Add a new item to the user's todo list",
  inputSchema: {
    type: "object",
    properties: { text: { type: "string" } },
    required: ["text"]
  },
  async execute({ text }) {
    // tu lógica de app, la misma que usa tu UI
  }
});

registerTool, getTools y executeTool son los tres métodos del objeto modelContext. - Fuente: Chrome WebMCP docs

El problema que resuelve

Imagina que le pides a un agente: "compra una camiseta talla M en esta tienda". Sin WebMCP, el agente abre la página, scrapea el HTML, adivina qué <div> es el filtro de tallas y simula clicks. Funciona hasta que cambias una clase de CSS. Es como pedirle direcciones a alguien describiéndole los edificios por su color: a la primera lluvia, pierde.

Con WebMCP, la tienda registra una tool get-dresses(size, color) y otra filter-products(ids). El agente las ve, las llama con argumentos tipados y la UI se actualiza. El usuario ve todo lo que pasa en su pantalla y confirma las acciones delicadas. Human-in-the-loop de verdad, con el contexto compartido entre agente, página y usuario. - Fuente: WebMCP Explainer

Y un detalle importante: WebMCP no sustituye a los servidores MCP de backend. Lo complementa. El backend sigue siendo tu fuente de verdad; WebMCP expone las acciones de la interfaz que ya tienes.

Las APIs de Angular v22

Angular v22 trae soporte experimental con dos funciones que importas desde @angular/core. Ojo con los nombres: son largos por algo.

provideExperimentalWebMcpTools registra herramientas a nivel de app o de ruta:

// main.ts
import {Service, inject, provideExperimentalWebMcpTools} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {AppRoot} from './app-root';

@Service()
class Greeter {
  sayHello(): string {
    return 'Hello agent!';
  }
}

bootstrapApplication(AppRoot, {
  providers: [
    provideExperimentalWebMcpTools([
      {
        name: 'greet',
        description: 'Greets the agent.',
        inputSchema: {type: 'object', properties: {}},
        execute: () => {
          const greeter = inject(Greeter);
          return {content: [{type: 'text', text: greeter.sayHello()}]};
        },
      },
    ]),
  ],
});

Nota que el execute corre dentro del injection context del Injector asociado: puedes usar inject() dentro sin más. Las herramientas se registran cuando el injector se inicializa y se desregistran cuando se destruye. - Fuente: provideExperimentalWebMcpTools

Los tipos de los argumentos se infieren del JSON Schema. Con required: ['query'] el tipo deja de ser string | undefined, y con additionalProperties: false el objeto de args queda cerrado:

// search-tool.ts
import {provideExperimentalWebMcpTools} from '@angular/core';

provideExperimentalWebMcpTools([
  {
    name: 'searchCatalog',
    description: 'Searches the store catalog for products matching a query.',
    inputSchema: {
      type: 'object',
      properties: {
        query: {type: 'string', description: 'The search keywords.'},
        maxResults: {type: 'number', description: 'Maximum number of results.'},
      },
      required: ['query'],
      additionalProperties: false,
    },
    execute: ({query, maxResults}) => {
      // query es string, maxResults es number | undefined
      const limit = maxResults ?? 5;
      return {
        content: [{type: 'text', text: `Returning up to ${limit} results for "${query}".`}],
      };
    },
  },
]);

declareExperimentalWebMcpTool es la variante imperativa, para registrar una herramienta dinámicamente desde cualquier contexto de inyección. Combinada con signals, te da tools que leen el estado vivo de tu app:

// counter.ts
import {Service, declareExperimentalWebMcpTool, signal} from '@angular/core';

@Service()
export class Counter {
  readonly count = signal(0);

  constructor() {
    declareExperimentalWebMcpTool({
      name: 'getCounter',
      description: 'Reads the global counter.',
      inputSchema: {type: 'object', properties: {}},
      execute: () => ({
        content: [{type: 'text', text: `The count is: ${this.count()}.`}],
      }),
    });
  }
}

El agente pregunta por el contador y siempre recibe el valor actual. Sin polling, sin scrapes. - Fuente: declareExperimentalWebMcpTool

Herramientas por ruta, con limpieza automática

El patrón que más me gusta: registrar tools solo donde tienen sentido. Un dashboard expone exportDashboardReports; el checkout expone las tools del carrito. Todo vía providers de la ruta:

// routes.ts
import {provideExperimentalWebMcpTools} from '@angular/core';
import {Routes} from '@angular/router';

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard').then((m) => m.Dashboard),
    providers: [
      provideExperimentalWebMcpTools([
        {
          name: 'exportDashboardReports',
          description: 'Exports the current dashboard analytics.',
          inputSchema: {type: 'object', properties: {}},
          execute: () => ({
            content: [{type: 'text', text: 'Dashboard export successfully triggered.'}],
          }),
        },
      ]),
    ],
  },
];

Pero hay truco: si sales de la ruta, esas tools siguen visibles para el agente, salvo que actives la limpieza automática de injectors:

// app.config.ts
import {provideRouter, withAutoCleanupInjectors} from '@angular/router';

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes, withAutoCleanupInjectors())],
};

Sin withAutoCleanupInjectors, la tool exportDashboardReports queda registrada aunque el usuario haya navegado a otro lado. El agente la llamaría y el export ya no tiene contexto. Fallo silencioso garantizado.

Signal Forms: herramientas gratis

Aquí es donde el diseño de Angular se nota pensado. Con provideExperimentalWebMcpForms, cada Signal Form se registra automáticamente como tool WebMCP, con el schema inferido del modelo y los validadores incluidos. - Fuente: provideExperimentalWebMcpForms

// user-registration.ts
import {Component, signal} from '@angular/core';
import {form, required} from '@angular/forms/signals';

@Component({
  selector: 'app-user-registration',
  templateUrl: './user-registration.html',
})
export class UserRegistration {
  private readonly model = signal({
    firstName: '',
    lastName: '',
    age: 0,
    hobbies: ['Web Development'],
  });

  readonly userForm = form(
    this.model,
    (f) => {
      required(f.firstName, {message: 'First name is mandatory.'});
      required(f.lastName, {message: 'Last name is mandatory.'});
    },
    {
      // Registra una tool `registerUser` con los parámetros del modelo
      experimentalWebMcpTool: {
        name: 'registerUser',
        description: 'Registers a new user.',
      },
      submission: {
        action: async (formValue) => {
          await submitRegistration(formValue().value());
        },
      },
    },
  );
}

Angular infiere el schema del valor inicial del modelo: firstName y lastName como strings requeridos, age como número, hobbies como array<string>. Y si el agente envía datos inválidos, recibe los errores de validación y puede corregirse solo. Si ya migraste a Signal Forms, esto es un dos por uno. Escribe tus formularios una vez y obtén UI y tool al tiempo.

Cómo probarlo sin esperar a un agente

Tres vías, de la más rápida a la más formal:

  1. Chrome DevTools: el panel Application tiene una sección WebMCP donde ves las tools disponibles tal como las ve el agente, con contadores de invocación, y puedes ejecutarlas a mano sin agente. - Fuente: Chrome DevTools WebMCP pane
  2. El polyfill: para tests unitarios y navegadores sin soporte, @mcp-b/webmcp-polyfill simula document.modelContext. Los tests de tus tools corren en cualquier CI.
  3. Tu agente favorito: ChatGPT Desktop ya invoca tools WebMCP, y las extensiones de agente para Chrome pueden consultarlas vía content scripts.

Seguridad: lo que nadie te va a perdonar

Tres cosas que debes saber antes de registrar tu primera tool:

Primera: Angular no valida los inputs. El agente puede mandar lo que sea; el schema es documentación, no guardia de seguridad. Valida en execute:

// validation.ts
execute: ({query, maxResults}) => {
  if (typeof query !== 'string') throw new Error(`Bad query: ${query}`);
  if (typeof maxResults !== 'number' && maxResults !== undefined) {
    throw new Error(`Bad maxResults: ${maxResults}`);
  }
  // ...
}

Segunda: los nombres chocan y explotan. Registrar dos tools con el mismo nombre lanza un error. declareExperimentalWebMcpTool dentro de un componente se ejecuta cada vez que el componente se instancia; si ese componente puede renderizar más de una vez a la vez, tienes una bomba de relojería. Prefiere providers de app o de ruta, o servicios root. - Fuente: Angular WebMCP guide

Tercera: prompt injection existe en este mundo también. El spec dedica una sección entera: descripciones engañosas que inducen compras no deseadas, respuestas de tools con instrucciones inyectadas, y sites que piden parámetros de más para perfilar usuarios. Las mitigaciones están en camino (anotaciones como consequentialHint para exigir confirmación del usuario en acciones de alto riesgo), pero hoy la responsabilidad es tuya. - Fuente: WebMCP spec, sección Security

WebMCP vs el MCP server del CLI

No confundas las dos capas. Angular ya trae un MCP server para tu agente de código (npx @angular/cli mcp): compila, corre tests, busca en la documentación y te enseña best practices mientras programas. Eso es dev-time.

WebMCP es runtime: tu app desplegada exponiendo sus funcionalidades a los agentes que la visitan. Uno ayuda a quien escribe el código; el otro ayuda a quien usa tu producto. Si quieres profundizar en la capa dev-time, ya escribí sobre Angular Agent Skills y sobre construir apps con Genkit y Gemini.

Tips para producción

  1. Cuida el presupuesto de tokens. Cada tool consume contexto del agente con su nombre, descripción y schema. Diez tools bien descritas rinden más que treinta genéricas. - Fuente: Chrome WebMCP docs
  2. Una tool, una responsabilidad. searchProducts y addToCart separadas, no una mega-tool shop(action: string) que el agente tiene que adivinar cómo usar.
  3. Sincroniza la tool con la UI visible. Si la tool filter-products filtra la grilla que el usuario está viendo, el usuario ve lo que el agente hace. Si la tool opera en paralelo a una UI que no cambia, generas desconfianza.
  4. Registra y desregistra dinámicamente. Las tools del carrito solo cuando hay un carrito. Menos ruido para el agente, menos superficie de ataque para ti.
  5. Valida fuerte en código, suelto en schema. El schema le enseña al agente; el código decide quién entra.

La próxima vez que un agente visite tu app, ¿va a scrapearte el DOM o va a llamar tus herramientas? Esa diferencia la decides tú ahora. ¿Ya jugaste con WebMCP en Chrome 149? Cuéntame en los comentarios.


> Más posts