WebMCP Implementation Guide for React and Next.js
Add WebMCP to your React or Next.js app so AI agents can interact with your UI. Step-by-step guide with hooks, components, and real code examples.
WebMCP lets your React or Next.js application expose structured tools to AI agents visiting your site. Instead of agents scraping your DOM, guessing at button labels, or automating clicks through fragile selectors, they call typed functions with validated inputs and get structured responses back. Your web app becomes a first-class MCP server -- right in the browser.
This guide walks through both the declarative and imperative WebMCP APIs, shows how to build a reusable React hook, and covers the practical considerations for shipping WebMCP in a Next.js App Router project today.
Browser Support: Where Things Stand
WebMCP is a W3C Community Group report published on February 10, 2026. It is not a W3C standard yet. The only browser with an implementation is Chrome 146 Canary, and even there it requires manually enabling a flag. Firefox and Safari have no implementation. Formal rollout across stable browsers is expected mid-to-late 2026.
That means you should not rely on WebMCP as the sole interface to your app. But you can start adding it now as a progressive enhancement. The declarative API uses plain HTML attributes that browsers without WebMCP support simply ignore. The imperative API can be gated behind a feature detection check.
Here is the check:
const isWebMCPSupported = typeof navigator !== 'undefined'
&& 'modelContext' in navigator;
If navigator.modelContext exists, the browser supports the imperative API. If not, your app works exactly as it did before -- no errors, no fallbacks needed.
For development and testing before stable Chrome ships, the MCP-B project offers a practical alternative. Their @mcp-b/react-webmcp npm package and companion Chrome extension act as a polyfill, letting you register and test WebMCP tools in any Chromium browser today.
The useWebMCPTool Hook
The imperative WebMCP API centers on navigator.modelContext, which provides methods to register and unregister tools. Wrapping this in a React hook gives you a clean, reusable pattern that handles registration, cleanup, and feature detection.
import { useEffect } from 'react';
interface ToolConfig {
name: string;
description: string;
inputSchema: Record<string, unknown>;
handler: (input: any) => Promise<any>;
}
export function useWebMCPTool(config: ToolConfig) {
useEffect(() => {
if (!('modelContext' in navigator)) return;
const mc = (navigator as any).modelContext;
mc.registerTool({
name: config.name,
description: config.description,
inputSchema: config.inputSchema,
handler: config.handler,
});
return () => {
mc.unregisterTool(config.name);
};
}, [config.name]);
}
A few things to note. The hook checks for modelContext before attempting registration, so it is safe to call in any browser. The cleanup function in useEffect unregisters the tool when the component unmounts, which is critical for single-page apps where components mount and unmount as users navigate. And the dependency array uses config.name to avoid re-registering on every render while still updating if the tool identity changes.
Declarative Approach: Next.js Forms with WebMCP Attributes
The declarative API is the lowest-effort way to add WebMCP to your app. You add HTML attributes to existing <form> elements, and WebMCP-capable agents can discover and invoke them automatically. Browsers without WebMCP support ignore the attributes entirely.
This pairs naturally with Next.js Server Actions:
<form
action={searchAction}
toolname="search_docs"
tooldescription="Search documentation by keyword"
>
<input name="query" type="text" placeholder="Search..." />
<button type="submit">Search</button>
</form>
The toolname attribute gives the tool its identifier. The tooldescription tells the agent what the tool does in natural language. The agent reads these attributes, understands the form's purpose, and can fill in the fields and submit it programmatically.
There is a third attribute worth knowing: toolautosubmit. When present, it tells the agent that this form is safe to submit without asking the user for confirmation. Use it for read-only operations like search or filtering. Do not use it for actions that modify data.
<form
action={filterAction}
toolname="filter_products"
tooldescription="Filter product catalog by category and price range"
toolautosubmit
>
<select name="category">
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<input name="maxPrice" type="number" placeholder="Max price" />
<button type="submit">Filter</button>
</form>
The declarative approach is the safest starting point. These are standard HTML attributes. They have zero runtime cost, zero bundle impact, and zero risk of breaking anything. Ship them today.
Imperative Approach: Real Component Examples
The imperative API is more powerful. You can register tools that do things forms cannot -- reading application state, triggering UI changes, navigating programmatically, or pre-filling complex interfaces. Here are four practical components using the useWebMCPTool hook.
Dashboard Data Viewer
Expose read-only access to analytics data displayed on a dashboard:
'use client';
import { useWebMCPTool } from '@/hooks/use-webmcp-tool';
import { useDashboardData } from '@/hooks/use-dashboard-data';
export function DashboardToolProvider() {
const { metrics } = useDashboardData();
useWebMCPTool({
name: 'get_dashboard_metrics',
description: 'Returns current dashboard metrics including page views, active users, and conversion rate',
inputSchema: {
type: 'object',
properties: {
period: {
type: 'string',
enum: ['today', '7d', '30d'],
description: 'Time period for metrics',
},
},
required: ['period'],
},
handler: async (input) => {
return metrics[input.period] ?? metrics['7d'];
},
});
return null;
}
This component renders nothing visible. It exists solely to register a tool that lets agents read your dashboard data. Mount it inside your dashboard layout and agents can query your metrics without scraping tables or charts.
Theme Toggler
Let agents switch between light and dark mode:
'use client';
import { useWebMCPTool } from '@/hooks/use-webmcp-tool';
import { useTheme } from '@/hooks/use-theme';
export function ThemeToolProvider() {
const { theme, setTheme } = useTheme();
useWebMCPTool({
name: 'set_theme',
description: 'Switch the application theme between light and dark mode',
inputSchema: {
type: 'object',
properties: {
theme: { type: 'string', enum: ['light', 'dark'] },
},
required: ['theme'],
},
handler: async (input) => {
setTheme(input.theme);
return { success: true, currentTheme: input.theme };
},
});
return null;
}
Navigation Tool
Allow agents to navigate to specific pages in your app:
'use client';
import { useRouter } from 'next/navigation';
import { useWebMCPTool } from '@/hooks/use-webmcp-tool';
export function NavigationToolProvider() {
const router = useRouter();
useWebMCPTool({
name: 'navigate_to_page',
description: 'Navigate to a specific page within the application',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'The URL path to navigate to, e.g. /settings or /dashboard/reports',
},
},
required: ['path'],
},
handler: async (input) => {
router.push(input.path);
return { navigatedTo: input.path };
},
});
return null;
}
Form Pre-fill Tool
Pre-populate a complex form with structured data. This is especially useful for agents assisting users with multi-field forms:
'use client';
import { useWebMCPTool } from '@/hooks/use-webmcp-tool';
interface FormPrefillProps {
onPrefill: (data: Record<string, string>) => void;
}
export function FormPrefillToolProvider({ onPrefill }: FormPrefillProps) {
useWebMCPTool({
name: 'prefill_contact_form',
description: 'Pre-fill the contact form with provided information',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
subject: { type: 'string' },
message: { type: 'string' },
},
required: ['name', 'email'],
},
handler: async (input) => {
onPrefill(input);
return { prefilled: true, fields: Object.keys(input) };
},
});
return null;
}
Each of these components follows the same pattern: use the hook, define a schema, handle the call, render nothing. The tool providers sit alongside your visible UI components and make your app's capabilities discoverable to agents.
Next.js App Router Considerations
WebMCP tools use browser APIs, so they must be registered in Client Components. You cannot call navigator.modelContext in a Server Component. But that does not mean you need to make your entire app client-rendered.
The recommended pattern is a WebMCPProvider component at the layout level:
// app/layout.tsx
import { WebMCPProvider } from '@/components/webmcp-provider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<WebMCPProvider />
</body>
</html>
);
}
// components/webmcp-provider.tsx
'use client';
import { NavigationToolProvider } from './tools/navigation-tool';
import { ThemeToolProvider } from './tools/theme-tool';
export function WebMCPProvider() {
return (
<>
<NavigationToolProvider />
<ThemeToolProvider />
</>
);
}
Your Server Components render the HTML. Your Client Components register the tools. They coexist cleanly. Page-specific tools can live inside page-level Client Components and will register when the page mounts and unregister when the user navigates away, handled automatically by the useEffect cleanup.
One thing to watch for: if you are using the Next.js <Link> component for client-side navigation, tools registered in page-specific components will mount and unmount as expected. But if you do a full page reload (hard navigation), all tools re-register from scratch. The useWebMCPTool hook handles both cases correctly.
Testing with Chrome Canary
To test your WebMCP implementation locally:
- Download Chrome Canary from the official Chrome release channels
- Open Chrome Canary and navigate to
chrome://flags#webmcp - Set the WebMCP flag to Enabled and relaunch the browser
- Load your application and open DevTools
- Go to Application > WebMCP to see all registered tools, their schemas, and invoke them manually
The DevTools panel shows every tool currently registered on the page. You can expand each tool to see its input schema, call it with test data, and inspect the response. This is the fastest way to verify your tools work correctly before an actual AI agent interacts with them.
If you do not want to use Chrome Canary, install the MCP-B Chrome extension. It provides the same navigator.modelContext API as a polyfill and includes its own DevTools panel for inspecting registered tools.
Production Strategy
Ship the declarative attributes now. They are plain HTML. No JavaScript, no polyfill, no browser dependency. Every form you annotate with toolname and tooldescription is ready for agents the moment stable Chrome ships WebMCP support, and in the meantime, they have zero impact on your existing users.
For the imperative API, wrap everything behind the 'modelContext' in navigator check. Your tools only register in browsers that support them. No feature flags to manage, no conditional imports. The hook shown earlier handles this automatically.
When Chrome stable ships WebMCP (expected mid-to-late 2026), your app will be immediately agent-accessible without a deploy. That is the advantage of progressive enhancement -- you are building on the platform, not around it.
WebMCP Builds on MCP
WebMCP makes your app accessible to AI agents. But to build the app itself, you need MCP servers in your editor -- Playwright for testing, Context7 for up-to-date framework docs, and the rest of the frontend development stack. MCP servers help you write the code. WebMCP lets agents use the result. They are two sides of the same protocol.
Browse the curated stacks on stackmcp.dev to get your editor configured with the right MCP servers for your workflow.