Skip to main content
In this tutorial, we will create a minimal React app that registers one WebMCP tool using the useWebMCP hook. The hook handles registration on mount and cleanup on unmount automatically. By the end, you will have a React component that exposes a tool to AI agents and shows execution state in the UI.

Prerequisites

  • Node.js 22.12 or later
  • A text editor
  • A modern web browser

What we will build

A React app with a single component that:
  1. Initializes the WebMCP polyfill
  2. Registers a say_hello tool via the useWebMCP hook
  3. Shows execution count and results in the page
  4. Allows you to call the tool from both the UI and the browser console
1

Scaffold the project

Create a new React project with Vite:
npm create vite@latest my-webmcp-react -- --template react-ts
cd my-webmcp-react
Install the WebMCP dependencies:
npm install @mcp-b/webmcp-polyfill usewebmcp
@mcp-b/webmcp-polyfill provides the navigator.modelContext runtime. usewebmcp provides the React hook for tool registration.
2

Initialize the polyfill

Open src/main.tsx and add the polyfill initialization before createRoot:
src/main.tsx
import { initializeWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';

initializeWebMCPPolyfill();

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>
);
Calling initializeWebMCPPolyfill() once at the top level makes navigator.modelContext available before any component mounts.
3

Create the tool component

Replace the contents of src/App.tsx with:
src/App.tsx
import { useWebMCP } from 'usewebmcp';

const INPUT_SCHEMA = {
  type: 'object',
  properties: {
    name: { type: 'string' },
  },
} as const;

export function App() {
  const helloTool = useWebMCP({
    name: 'say_hello',
    description: 'Returns a hello message',
    inputSchema: INPUT_SCHEMA,
    execute: async (args) => ({
      content: [{ type: 'text', text: `Hello ${args?.name ?? 'world'}!` }],
    }),
  });

  return (
    <div>
      <h1>My First React WebMCP Tool</h1>
      <p>Tool "say_hello" registered.</p>
      <p>Executions: {helloTool.state.executionCount}</p>
      <p>Last result: {helloTool.state.lastResult
        ? JSON.stringify(helloTool.state.lastResult)
        : 'none'}</p>
      {helloTool.state.error && (
        <p style={{ color: 'red' }}>Error: {helloTool.state.error.message}</p>
      )}
      <button onClick={() => helloTool.execute({ name: 'React' })}>
        Run Tool Locally
      </button>
    </div>
  );
}
The useWebMCP hook registers the tool when the component mounts and unregisters it when the component unmounts. The input schema is defined outside the component to prevent unnecessary re-registration on every render.
4

Start the development server

npm run dev
Open http://localhost:5173 in your browser. You should see:
My First React WebMCP Tool
Tool "say_hello" registered.
Executions: 0
Last result: none
5

Call the tool from the UI

Click the Run Tool Locally button. Notice the page updates:
Executions: 1
Last result: {"content":[{"type":"text","text":"Hello React!"}]}
Click the button a few more times. The execution count increases with each click.
6

Verify the tool from the console

Open the browser console (F12) and verify the tool is registered:
const tools = navigator.modelContextTesting.listTools();
console.log(tools);
You should see an array containing your say_hello tool. Now call it through the testing API:
const result = await navigator.modelContextTesting.executeTool(
  "say_hello",
  JSON.stringify({ name: "Console" })
);
console.log(JSON.parse(result));
The output should be an object containing the tool response:
{
  "content": [{ "type": "text", "text": "Hello Console!" }]
}
Notice that the execution count in the UI also incremented, because the same underlying execute function ran.

The complete files

import { initializeWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';

initializeWebMCPPolyfill();

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>
);

What you learned

  • initializeWebMCPPolyfill() should be called once at app startup, before components mount
  • useWebMCP registers a tool on mount and unregisters on unmount
  • Define input schemas outside the component (or memoize them) to avoid re-registration on each render
  • The hook returns state (with executionCount, lastResult, error, isExecuting) and an execute function for local invocation
  • Tools registered via the hook are also callable through navigator.modelContextTesting in the console

Next steps