Skip to main content
Prerequisites: Node.js 18+, MCP-B Chrome Extension

Quick Setup

1. Install Dependencies

  • React
  • Vanilla JS
  • Script Tag (No Build)
pnpm add @mcp-b/react-webmcp @mcp-b/global zod

2. Start Your Dev Server

pnpm dev
# or
npm run dev

3. Open in Chrome with MCP-B Extension

  1. Navigate to your local dev server (e.g., http://localhost:5173)
  2. Click the MCP-B extension icon
  3. Check the “Tools” tab to see your registered tools

Development Workflow

React Development

import '@mcp-b/global';
import { useWebMCP } from '@mcp-b/react-webmcp';
import { z } from 'zod';
import { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([]);

  // Tools auto-register when component mounts
  // and auto-cleanup when component unmounts
  useWebMCP({
    name: 'add_todo',
    description: 'Add a new todo item',
    inputSchema: {
      text: z.string().min(1),
      priority: z.enum(['low', 'medium', 'high']).optional()
    },
    handler: async ({ text, priority = 'medium' }) => {
      const newTodo = { id: Date.now(), text, priority, done: false };
      setTodos(prev => [...prev, newTodo]);
      return { success: true, todo: newTodo };
    }
  });

  useWebMCP({
    name: 'list_todos',
    description: 'Get all todos',
    handler: async () => {
      return { todos };
    }
  });

  return <div>{/* Your UI */}</div>;
}

Vanilla JavaScript Development

import '@mcp-b/global';

let todos = [];

// Register tools individually
const addTodoRegistration = navigator.modelContext.registerTool({
  name: 'add_todo',
  description: 'Add a new todo item',
  inputSchema: {
    type: 'object',
    properties: {
      text: { type: 'string' },
      priority: { type: 'string', enum: ['low', 'medium', 'high'] }
    },
    required: ['text']
  },
  async execute({ text, priority = 'medium' }) {
    const newTodo = { id: Date.now(), text, priority, done: false };
    todos.push(newTodo);
    updateUI();
    return {
      content: [{
        type: 'text',
        text: JSON.stringify({ success: true, todo: newTodo })
      }]
    };
  }
});

// Unregister when needed
// addTodoRegistration.unregister();

Hot Reload Support

WebMCP works seamlessly with hot module replacement (HMR):

Vite

Tools registered with useWebMCP() automatically handle HMR - they’ll re-register when your component code changes.

Webpack

For vanilla JS with Webpack HMR:
if (module.hot) {
  module.hot.dispose(() => {
    // Clean up registrations
    registration.unregister();
  });
}

Debugging Tools

Browser DevTools

Access the MCP bridge in the console:
// Check if WebMCP is loaded
console.log(window.navigator.modelContext);

// In development, access the bridge
if (window.__mcpBridge) {
  console.log('Registered tools:', window.__mcpBridge.tools);
  console.log('MCP server:', window.__mcpBridge.server);
}

MCP-B Extension Inspector

  1. Click the MCP-B extension icon
  2. Go to the “Tools” tab
  3. See all registered tools from your page
  4. Test tools directly from the inspector

React DevTools

The useWebMCP hook exposes execution state:
const tool = useWebMCP({
  name: 'my_tool',
  description: 'My tool',
  handler: async () => { /* ... */ }
});

// Access in your component for debugging
console.log(tool.state.isExecuting);
console.log(tool.state.lastResult);
console.log(tool.state.error);

Testing

Unit Testing Tools

import { describe, it, expect, beforeEach } from 'vitest';

describe('Todo Tools', () => {
  beforeEach(() => {
    // Reset state before each test
    todos = [];
  });

  it('should add a todo', async () => {
    const result = await execute({
      text: 'Test todo',
      priority: 'high'
    });

    expect(result.success).toBe(true);
    expect(result.todo.text).toBe('Test todo');
    expect(todos.length).toBe(1);
  });
});

E2E Testing with Playwright

import { test, expect } from '@playwright/test';

test('MCP tools are registered', async ({ page }) => {
  await page.goto('http://localhost:5173');

  // Wait for WebMCP to load
  await page.waitForFunction(() => 'modelContext' in navigator);

  // Check tools are registered (requires expose)
  const hasTools = await page.evaluate(() => {
    return window.__mcpBridge?.tools?.length > 0;
  });

  expect(hasTools).toBe(true);
});

Environment Configuration

Development vs Production

const isDev = import.meta.env.DEV;

if (isDev) {
  // Development-only tools
  navigator.modelContext.registerTool({
    name: 'debug_state',
    description: 'Get current app state (dev only)',
    async execute() {
      return {
        content: [{ type: 'text', text: JSON.stringify(appState) }]
      };
    }
  });
}

Troubleshooting

  1. Ensure @mcp-b/global is imported
  2. Check browser console for errors
  3. Verify extension is installed and enabled
  4. Refresh the page after starting dev server
  5. Check extension popup “Tools” tab
  1. Check tool handler for errors
  2. Verify inputSchema matches arguments
  3. Ensure async handler returns proper format
  4. Check browser console for exceptions
This is normal in React StrictMode (development only). useWebMCP() handles this correctly and deduplicates registrations.
Use useWebMCP() in React - it handles HMR automatically. For vanilla JS, unregister tools in HMR disposal hooks.

Next Steps