> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcp-b.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Build your first React tool

> Register a WebMCP tool from a React component using the useWebMCP hook, and verify it from the browser console.

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 browser-side WebMCP consumers and shows execution state in the UI.

## Prerequisites

* Node.js 22.12 or later
* pnpm
* A text editor
* Chrome or Edge 126+, Firefox 126+, or Safari 18+

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

<Steps>
  <Step title="Scaffold the project">
    Create a new React project with Vite:

    ```bash title="Create the React app" theme={null}
    pnpm create vite my-webmcp-react --template react-ts
    cd my-webmcp-react
    ```

    Install the WebMCP dependencies:

    ```bash title="Install the hook and polyfill" theme={null}
    pnpm add @mcp-b/webmcp-polyfill usewebmcp
    ```

    `@mcp-b/webmcp-polyfill` provides the `document.modelContext` runtime. `usewebmcp` provides the React hook for tool registration.
  </Step>

  <Step title="Initialize the polyfill">
    Open `src/main.tsx` and add the polyfill initialization before `createRoot`:

    ```tsx title="src/main.tsx" theme={null}
    import { installWebMCP } from '@mcp-b/webmcp-polyfill';
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import { App } from './App';

    installWebMCP();

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

    Calling `installWebMCP()` once at the top level makes `document.modelContext` available before any component mounts.
  </Step>

  <Step title="Create the tool component">
    Replace the contents of `src/App.tsx` with:

    ```tsx title="src/App.tsx" theme={null}
    'use client';

    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) => ({
          message: `Hello ${args.name ?? 'world'}!`,
        }),
      });

      return (
        <div>
          <h1>My First React WebMCP Tool</h1>
          <p>Browser tool: "say_hello"</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 named schema also keeps the component easy to scan; inline schema objects do not cause re-registration by reference.
  </Step>

  <Step title="Start the development server">
    ```bash title="Start Vite" theme={null}
    pnpm dev
    ```

    Open `http://localhost:5173` in your browser. You should see:

    ```text title="Expected page" theme={null}
    My First React WebMCP Tool
    Browser tool: "say_hello"
    Executions: 0
    Last result: none
    ```
  </Step>

  <Step title="Call the tool from the UI">
    Click the **Run Tool Locally** button. Notice the page updates:

    ```text title="Expected result" theme={null}
    Executions: 1
    Last result: {"message":"Hello React!"}
    ```

    Click the button a few more times. The execution count increases with each click.
  </Step>

  <Step title="Verify the tool from the console">
    Open the browser console (F12) and verify the tool is registered:

    ```javascript title="Find the registered tool" theme={null}
    const modelContext = document.modelContext;
    if (!modelContext) throw new Error('WebMCP is unavailable');
    const tools = await modelContext.getTools();
    console.log(tools);
    ```

    You should see an array containing your `say_hello` tool. Now call it with an input object:

    ```javascript title="Call the tool through the polyfill" theme={null}
    const tool = tools.find((candidate) => candidate.name === 'say_hello');
    if (!tool) throw new Error('say_hello is not registered');

    const result = await modelContext.executeTool(tool, { name: 'Console' });
    console.log(JSON.parse(result));
    ```

    The output should be an object containing the tool response:

    ```json title="Expected agent result" theme={null}
    {
      "message": "Hello Console!"
    }
    ```

    Notice that the execution count in the UI also incremented, because the same underlying `execute` function ran.
  </Step>
</Steps>

## What you learned

* `installWebMCP()` should be called once at app startup, before components mount
* `useWebMCP` registers a tool on mount and unregisters on unmount
* Metadata changes update registration automatically; equivalent inline schemas do not cause re-registration
* The hook returns `state` (with `executionCount`, `lastResult`, `error`, `isExecuting`) and an `execute` function for local invocation
* Tools registered via the hook are discoverable with `document.modelContext.getTools()` and callable with `executeTool(tool, inputObject)`

## Next steps

* [WebMCP resources and status](/explanation/design/spec-status-and-limitations) for current native browser guidance
* [Connect a Desktop Agent](/tutorials/desktop-agent-relay) to let Claude or Cursor call your tools
* See the [`usewebmcp` reference](/packages/usewebmcp/reference) for the core hook API and cancellation; use [`@mcp-b/react-webmcp`](/packages/react-webmcp/reference) for Standard Schema validation


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.