Modern Angular
WebMCP in Angular: How to Expose Tools to the Browser's AI Agent (and When to Wait)
WebMCP lets your Angular app hand typed tools to a browser AI agent instead of letting it click through the DOM. Angular ships experimental support behind provideExperimentalWebMcpTools. As of 2026-06-01 the entry point moved from navigator to document.modelContext, so the live guide sits on next.angular.dev/ai/webmcp, not stable docs.
What WebMCP actually changes
An AI agent that automates your app usually drives the UI: it reads the DOM, finds a button, clicks it, fills a field, hopes the layout did not change. WebMCP flips that. The spec describes it as letting developers expose web application functionality, either JavaScript functions or HTML <form> elements, as tools with natural language descriptions and structured schemas for an agent to ingest. Your app declares a findOrder tool with an input schema; the agent calls it by name and gets structured data back. No selector guessing, no synthetic clicks.
Read the status before you read the API. WebMCP is a W3C Community Group draft from the Web Machine Learning CG. That is a draft report, not a W3C Standard and not on the Standards Track. The shape can still move under you.
This is a different layer from the Angular CLI MCP server, which feeds project context to an assistant while you write Angular at your desk. That one helps generate code. WebMCP is runtime: your shipped app exposing capabilities to whatever agent the end user is running. The blast radius is the production app, not the editor, which is exactly why the rest of this matters.
How Angular wires it
There are four places to register a tool, and they map to how long the tool should live. Pick the narrowest scope that covers the capability.
At bootstrap, provideExperimentalWebMcpTools registers tools for the whole app. Each tool carries a name, a description, an inputSchema in JSON Schema, and an execute callback that runs inside an injection context, so it can inject services directly.
At the route level you pass the same provider in a route's providers, and the tool only exists while that route is active. For that to be true you need withExperimentalAutoCleanupInjectors in provideRouter; without it the route's tools stay registered after you navigate away. One honest caveat: this exact recommended setup currently has an open bug. Route providers plus withExperimentalAutoCleanupInjectors can throw InvalidStateError: Duplicate tool name on repeated navigation back to the route (issue #68899, open as of 2026-06-02). If you spike the route path, navigate in and out a few times and watch for it.
Inside a service, declareExperimentalWebMcpTool registers a tool from within an injection context and auto-unregisters when that context is destroyed. This is the natural home when the tool belongs to a feature service rather than a route.
For forms, provideExperimentalWebMcpForms plus an experimentalWebMcpTool option on form() turns a Signal Form into a tool. Angular infers the JSON schema from the form model's initial values and wires validation and submission, so the agent sees field errors and can self-correct instead of submitting garbage. Three constraints come with that inference: the model needs concrete initial values ('', 0, false, never null or undefined), arrays need at least one element so the item shape is known, and async validators do not run during agent submission. I go deeper on forms-as-tools in Turning Signal Forms into AI agent tools.
import { Service, inject, provideExperimentalWebMcpTools } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppRoot } from './app-root';
import { Orders } from './orders';
bootstrapApplication(AppRoot, {
providers: [
provideExperimentalWebMcpTools([
{
name: 'findOrder',
description: 'Looks up an order by its number.',
inputSchema: {
type: 'object',
properties: { orderId: { type: 'string' } },
required: ['orderId'],
},
execute: ({ orderId }) => {
const orders = inject(Orders);
return { content: [{ type: 'text', text: orders.summary(orderId) }] };
},
},
]),
],
});// routes.ts
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: 'Export triggered.' }] }),
}]),
],
}];
// app.config.ts: without this, route tools stay live after navigation
provideRouter(routes, withExperimentalAutoCleanupInjectors());// provideExperimentalWebMcpForms() must be in your providers
readonly model = signal({ firstName: '', lastName: '' });
readonly userForm = form(this.model,
(f) => {
required(f.firstName, { message: 'First name is mandatory.' });
required(f.lastName, { message: 'Last name is mandatory.' });
},
{
experimentalWebMcpTool: { name: 'registerUser', description: 'Registers a new user.' },
submission: { action: async (formValue) => { /* ... */ } },
},
);Can you even run this today?
Before you design tools, check what status you are building on. WebMCP support shipped with v22, but it is still an experimental API, which is why its guide lives on next.angular.dev and not the stable angular.dev docs yet.
The entry point also just moved. It used to hang off navigator; as of 2026-06-01 it is document.modelContext, after issue #68947 was completed and PR #68961 merged. Until that has settled across consumers, feature-detect both: const modelContext = document.modelContext || navigator.modelContext;.
On the consumer side, be skeptical of demos. WebMCP is a CG draft, and I cannot point you at a shipping browser or agent that consumes document.modelContext from a primary source I would cite. So you test the registration without waiting for that: the guide suggests the @mcp-b/webmcp-polyfill package as a mock for unit tests, which lets you assert that your tools register and that execute returns what you expect.
The Angular team is direct about all of this. Their words: "The WebMCP spec is very early in its lifecycle and is undergoing frequent changes. As such, WebMCP support in Angular is currently experimental. APIs are subject to change even outside of major versions." Read that as: a minor bump can rename what you shipped.
The surface you're exposing
Every tool you register is a new thing an agent can trigger in your app without a human clicking. That sentence is the whole threat model. Treat the tool list the way you treat public API endpoints, because functionally that is what it is.
The current spec registers tools through document.modelContext.registerTool(tool, { signal, exposedTo }). The exposedTo option scopes which origins may call a tool, and an AbortSignal lets you tear a tool down. Default to the narrowest exposedTo you can justify, the same instinct you would bring to CORS.
Do not trust the input. The Angular docs are explicit: "Angular does not provide any implicit validation that the inputs provided by an agent actually match the defined JSON schema. Consider explicitly validating arguments to the execute function before using them to ensure reliability." The schema documents the tool; it does not enforce anything. An agent under prompt injection can hand your execute whatever a malicious page talked it into, so validate arguments at the top of execute and bail on anything you did not expect.
Tool names are a namespace, and a duplicate registration throws. That is also the mechanism behind the autocleanup bug above: a stale route tool that never unregistered collides with the fresh one on the next visit.
Where I land: I would refuse to register a tool that mutates real data behind no confirmation, and I would require every execute that touches money, identity, or destructive actions to validate its inputs and re-check authorization server-side, exactly as if the call came from an untrusted client. Because it does.
Where I would spike it, and where I would wait
I would spike WebMCP on an internal tool or a low-stakes route, behind a flag, to learn the shape of tool design and input schemas. That investment survives even if the API renames.
I would wait on anything user-facing in production, anything in a critical or regulated flow, and anything where a mid-cycle breaking change is expensive to chase. With the entry point still moving from navigator to document.modelContext and the recommended route setup carrying an open duplicate-tool bug, the cost of treating this as stable is real, and it is separate from whether the idea is good. The idea is good. The surface is not settled.
| Scenario | Spike or wait | Why |
|---|---|---|
| Internal admin tool, flagged | Spike | Learn tool and schema design where breakage is cheap. |
| Read-only lookup on a minor route | Spike | Low blast radius, easy to remove if the API moves. |
| Route-scoped tools today | Spike, carefully | Watch for the duplicate-tool race in issue #68899 on repeated navigation. |
| User-facing action in production | Wait | Spec changes outside majors can break shipped flows. |
| Regulated or payment flow | Wait | Exposed tools widen the surface an agent can trigger. |
Reusable artifact
WebMCP spike gate
- Scope the spike to an internal or low-stakes route, behind a flag.
- Feature-detect the entry point:
document.modelContext || navigator.modelContext. - If you scope tools to a route, expect the
withExperimentalAutoCleanupInjectorsduplicate-tool race (issue #68899) and test repeated navigation. - Set
exposedToto the narrowest set of origins the tool needs. - Validate every
executeinput at runtime; the JSON schema is documentation, not enforcement. - Write down what each exposed tool lets the agent trigger, and re-check authorization server-side for anything that mutates.
- Re-check status and API names on every Angular minor, not just majors.
FAQ
Is WebMCP stable in Angular?
No. Angular's support is experimental and the team warns that APIs "are subject to change even outside of major versions." The underlying spec is a W3C Community Group draft, not a standard.
navigator.modelContext or document.modelContext?
As of 2026-06-01 the entry point moved to document.modelContext (PR #68961). Feature-detect both with const modelContext = document.modelContext || navigator.modelContext; until the change has settled across consumers.
How is WebMCP different from the Angular CLI MCP server?
The Angular CLI MCP server is a development-time tool: it feeds project context to an assistant while you write code. WebMCP is runtime: your shipped app exposes tools to the end user's browser agent. Different layer, much larger surface. I compare the two head-on in WebMCP vs the Angular CLI MCP server.
Do I have to validate agent inputs myself?
Yes. The docs state Angular does "not provide any implicit validation that the inputs provided by an agent actually match the defined JSON schema." Validate arguments at the start of every execute, and re-check authorization server-side for anything that mutates data.
Which Angular version do I need?
WebMCP support shipped with v22, but it is still an experimental API. That is why the guide currently lives on next.angular.dev/ai/webmcp.
Can I test WebMCP tools without a browser that supports it?
Yes. The guide suggests the @mcp-b/webmcp-polyfill package as a mock for unit tests, so you can assert that tools register and that execute returns what you expect.
Sources checked
- https://next.angular.dev/ai/webmcp
- https://next.angular.dev/api/core/provideExperimentalWebMcpTools
- https://webmachinelearning.github.io/webmcp/
- https://github.com/webmachinelearning/webmcp
- https://angular.dev/ai/mcp
- https://angular.dev/guide/forms/signals/overview
- https://angular.dev/reference/releases
- https://github.com/angular/angular/issues/68899
- https://github.com/angular/angular/issues/68947
- https://github.com/angular/angular/pull/68961
Modern Angular Playbook
This article is one play.
The Modern Angular Playbook collects the diagnostic, the adoption matrix, eleven plays, and the 30-day plan. Free, in English and Portuguese.
You get both PDFs by email, through the Dojo IA list.
André Ramosavailable for remote roles, UTC−3Get in touch →