StackMCP
Blog
·10 min read

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.

webmcpreactnextjsai-agentstutorial

WebMCP lets your React or Next.js application expose structured tools to AI agents visiting your site. Instead of agents scraping your DOM or automating clicks through fragile selectors, they call typed functions with validated inputs and get structured responses back.

TL;DR: WebMCP has two APIs. The declarative API uses HTML attributes on forms (ship today, zero risk). The imperative API uses navigator.modelContext to register tools via JavaScript (ship behind feature detection). Build a useWebMCPTool React hook for clean registration/cleanup. Mount tool providers in your layout as Client Components alongside your Server Components. Test in Chrome Canary or with the MCP-B polyfill.

graph TB
    subgraph App["Next.js App"]
        L[Layout - Server Component]
        L --> P[Page - Server Component]
        L --> WP[WebMCPProvider - Client Component]
        WP --> T1[NavigationTool]
        WP --> T2[ThemeTool]
        P --> F["Forms with toolname<br/>attributes (Declarative)"]
        P --> PT[PageToolProvider - Client Component]
        PT --> T3[Page-specific tool]
    end

    subgraph Agent["AI Agent"]
        AG[Agent] -->|Discovers tools| App
        AG -->|Calls tools| App
    end

Browser Support: Where Things Stand

WebMCP is a W3C Community Group report published February 10, 2026. The only browser with an implementation is Chrome 146 Canary, behind a flag. Firefox and Safari have no implementation. Stable rollout expected mid-to-late 2026.

You should not rely on WebMCP as the sole interface. But you can start adding it now as a progressive enhancement. The declarative API uses plain HTML attributes that unsupported browsers ignore. The imperative API can be gated behind feature detection:

const isWebMCPSupported = typeof navigator !== 'undefined'
  && 'modelContext' in navigator;

For development before stable Chrome ships, the MCP-B project's @mcp-b/react-webmcp package and Chrome extension act as a polyfill.

The useWebMCPTool Hook

The imperative API centers on navigator.modelContext. Wrapping it in a React hook gives you clean 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]);
}

The hook checks for modelContext before registration (safe in any browser), unregisters on unmount (critical for SPAs), and uses config.name as the dependency to avoid re-registering on every render.

Declarative Approach: Next.js Forms with WebMCP Attributes

The lowest-effort way to add WebMCP. Add HTML attributes to existing <form> elements. Browsers without WebMCP ignore them 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 toolautosubmit attribute tells agents a form is safe to submit without user confirmation. Use it for read-only operations like search:

<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>

Zero runtime cost, zero bundle impact, zero risk. Ship them today. For ecommerce-specific form patterns (product search, add-to-cart, checkout), see WebMCP for ecommerce.

Imperative Approach: Real Component Examples

Dashboard Data Viewer

Expose read-only access to analytics data:

'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 to register a tool that lets agents read your dashboard data.

Theme Toggler

'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;
}
'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

'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 component follows the same pattern: use the hook, define a schema, handle the call, render nothing.

Next.js App Router Considerations

WebMCP tools use browser APIs, so they must be registered in Client Components. But you do not need to make your entire app client-rendered.

The recommended pattern is a WebMCPProvider 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 />
    </>
  );
}

Server Components render the HTML. Client Components register the tools. They coexist cleanly. Page-specific tools live in page-level Client Components and register/unregister automatically as users navigate, handled by the useEffect cleanup.

Testing with Chrome Canary

  1. Download Chrome Canary
  2. Navigate to chrome://flags#webmcp
  3. Set the flag to Enabled and relaunch
  4. Load your app and open DevTools
  5. Go to Application > WebMCP to see registered tools, schemas, and invoke them manually

If you do not want Chrome Canary, install the MCP-B Chrome extension for the same navigator.modelContext API as a polyfill.

Production Strategy

Ship declarative attributes now. No JavaScript, no polyfill, no browser dependency.

For the imperative API, wrap behind 'modelContext' in navigator. Tools only register in supported browsers. The hook handles this automatically.

When Chrome stable ships WebMCP (expected mid-to-late 2026), your app will be immediately agent-accessible without a deploy.

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. See our frontend developer stack for a complete setup.

To understand how MCP and WebMCP fit together in the development lifecycle, read From MCP to WebMCP. For the fundamentals of what WebMCP is, see our introduction to WebMCP. And for a side-by-side protocol comparison, see WebMCP vs MCP.

Browse the curated stacks on stackmcp.dev to get your editor configured for your workflow.

Related Stacks

Related Servers