Skip to main content
Register WebMCP tools in any frontend framework by following its native lifecycle patterns. For cross-cutting patterns like conditional registration, error handling, and annotations, see Add Tools to an Existing App.

Install the runtime

React users also need a hook package. Install @mcp-b/react-webmcp (recommended) or usewebmcp alongside your chosen runtime. See Choose a hook package below.

Initialize at your entry point

Import @mcp-b/global once before any component mounts. The import is a side effect that installs navigator.modelContext.
import '@mcp-b/global';
import { createRoot } from 'react-dom/client';
import { App } from './App';

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

Register a tool

Each framework has its own lifecycle hooks for mount and unmount. Register tools on mount, unregister on unmount.
Two hook packages are available:
PackageUse when
@mcp-b/react-webmcpYou want the full MCP-B surface: Zod schemas, prompts, resources, sampling, elicitation
usewebmcpYou want strict-core navigator.modelContext tools only
Both handle registration on mount and cleanup on unmount automatically.
import { useWebMCP } from '@mcp-b/react-webmcp';
import { z } from 'zod';

export function LikeTool() {
  const likeTool = useWebMCP({
    name: 'posts_like',
    description: 'Like a post by ID. Increments the like count.',
    inputSchema: {
      postId: z.string().uuid().describe('The post ID to like'),
    },
    annotations: {
      title: 'Like Post',
      readOnlyHint: false,
      idempotentHint: true,
    },
    handler: async (input) => {
      await api.posts.like(input.postId);
      return { success: true, postId: input.postId };
    },
  });

  return (
    <div>
      {likeTool.state.isExecuting && <p>Liking...</p>}
      {likeTool.state.error && <p>Error: {likeTool.state.error.message}</p>}
    </div>
  );
}

Create a reusable abstraction

React already has dedicated hook packages (@mcp-b/react-webmcp and usewebmcp), so no custom abstraction is needed. For other frameworks, extract the register/unregister lifecycle into a reusable pattern.
import { onMounted, onUnmounted } from 'vue';

export function useWebMCPTool(
  tool: Parameters<typeof navigator.modelContext.registerTool>[0]
) {
  onMounted(() => {
    navigator.modelContext.registerTool(tool);
  });

  onUnmounted(() => {
    navigator.modelContext.unregisterTool(tool.name);
  });
}
<script setup lang="ts">
import { useWebMCPTool } from '@/composables/useWebMCPTool';

useWebMCPTool({
  name: 'get_greeting',
  description: 'Get a greeting message',
  inputSchema: { type: 'object', properties: {} },
  async execute() {
    return { content: [{ type: 'text', text: 'Hello from Vue!' }] };
  },
});
</script>

Handle SSR

@mcp-b/global accesses browser APIs on import, so SSR frameworks need client-side guards. If you use @mcp-b/webmcp-polyfill instead, it is SSR-safe out of the box and these guards are not needed.
Mark components with 'use client'. For components that access window or document directly, use dynamic imports:
import dynamic from 'next/dynamic';
const BrowserOnly = dynamic(() => import('./BrowserOnly'), { ssr: false });

Verify registration

Open the browser console and run:
navigator.modelContextTesting?.listTools();
This returns an array of all registered tools with their names, descriptions, and input schemas. For a richer inspection experience, see Debug and Troubleshoot.