Skip to main content
This package implements the W3C Web Model Context API specification, making navigator.modelContext available in your site. Instead of creating complex installation steps, it works out of the box with automatic detection of whether your app is running standalone or embedded in an iframe.
Quick Start: Use registerTool() to add tools. It’s simple, automatic, and works everywhere.

Prerequisites

  • Modern browser supporting ES2020+ (Chrome, Edge, Firefox, Safari)
  • MCP-B Extension for testing tools
  • Node.js 18+ and package manager (for NPM method) OR basic HTML knowledge (for script tag method)

Installation

Via IIFE Script Tag (Easiest - No Build Required)

The IIFE (Immediately Invoked Function Expression) version bundles everything into a single file and auto-initializes when loaded. Perfect for simple HTML pages or prototyping. Add the script to your HTML <head>:
<!DOCTYPE html>
<html>
<head>
  <!-- IIFE version - bundles all dependencies, auto-initializes -->
  <script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
</head>
<body>
  <h1>My AI-Powered App</h1>

  <script>
    // window.navigator.modelContext is already available!
    // Register tools for AI agents (recommended approach)
    window.navigator.modelContext.registerTool({
      name: "get-page-title",
      description: "Get the current page title",
      inputSchema: {
        type: "object",
        properties: {}
      },
      async execute() {
        return {
          content: [{
            type: "text",
            text: document.title
          }]
        };
      }
    });
  </script>
</body>
</html>
What you get:
  • Self-contained - All dependencies bundled (285KB minified)
  • Auto-initializes - window.navigator.modelContext ready immediately
  • No build step - Just drop it in your HTML
  • Works everywhere - Compatible with all modern browsers
  • Global access - Also exposes window.WebMCP for advanced usage

Via NPM

For applications using a bundler (Vite, Webpack, etc.):
npm install @mcp-b/global
import '@mcp-b/global';

// Register individual tools (recommended)
navigator.modelContext.registerTool({
  name: "my_tool",
  description: "Tool description",
  inputSchema: { type: "object", properties: {} },
  async execute(args) {
    return {
      content: [{ type: "text", text: "Result" }]
    };
  }
});

Overview

This package implements the W3C Web Model Context API specification, adding navigator.modelContext to your website. Your JavaScript functions become tools that AI agents can discover and call. Key Features:
  • Automatic dual-server mode - Tools accessible from both same-window clients AND parent pages (when in iframe)
  • Zero configuration - Works out of the box, detects iframe context automatically
  • Production-ready - Used in real-world examples like the TicTacToe mini-app

Tool Registration

The Default: registerTool()

For 99% of use cases, use registerTool() to add tools one at a time:
const registration = navigator.modelContext.registerTool({
  name: "add_to_cart",
  description: "Add a product to the shopping cart",
  inputSchema: {
    type: "object",
    properties: {
      productId: { type: "string" },
      quantity: { type: "number" }
    }
  },
  async execute({ productId, quantity }) {
    await addToCart(productId, quantity);
    return { content: [{ type: "text", text: `Added ${quantity} items` }] };
  }
});

// Optional: Unregister later
registration.unregister();
Why this works great:
  • ✅ Simple and intuitive
  • ✅ Automatic cleanup with unregister()
  • ✅ Perfect for React/Vue component-scoped tools
  • ✅ No need to worry about tool lifecycle management
Only use provideContext() when you need to define application-level base tools:
navigator.modelContext.provideContext({
  tools: [
    {
      name: "app_info",
      description: "Get app information",
      inputSchema: { type: "object", properties: {} },
      async execute() {
        return { content: [{ type: "text", text: "App v1.0.0" }] };
      }
    }
  ]
});
Important: provideContext() replaces all base tools each time it’s called. Most developers should use registerTool() instead.See Two-Bucket Tool Management below for details on how these work together.
WebMCP uses a two-bucket system: base tools (via provideContext()) and dynamic tools (via registerTool()). Dynamic tools persist independently, making them perfect for component lifecycle management. See Tool Registration for details.

API Reference

Register a single tool dynamically. This is the recommended approach for most use cases. Parameters:
  • tool - A single tool descriptor
Returns:
  • Object with unregister() function to remove the tool
Benefits:
  • ✅ Persist across provideContext() calls
  • ✅ Perfect for component lifecycle management
  • ✅ Fine-grained control over individual tools
  • ✅ Can be unregistered when no longer needed
Example:
// Register a tool (recommended approach)
const registration = navigator.modelContext.registerTool({
  name: "add-todo",
  description: "Add a new todo item to the list",
  inputSchema: {
    type: "object",
    properties: {
      text: {
        type: "string",
        description: "The todo item text"
      },
      priority: {
        type: "string",
        enum: ["low", "medium", "high"],
        description: "Priority level"
      }
    },
    required: ["text"]
  },
  async execute({ text, priority = "medium" }) {
    const todo = addTodoItem(text, priority);
    return {
      content: [{
        type: "text",
        text: `Added todo: "${text}" with ${priority} priority`
      }]
    };
  }
});

// Later, unregister the tool if needed
registration.unregister();
Register base/app-level tools. Use sparingly - only for top-level base tools. Parameters:
  • context.tools - Array of tool descriptors
Warning: This replaces all base tools each time it’s called. Tools registered via registerTool() are NOT affected. Example:
// Only use for top-level base tools
navigator.modelContext.provideContext({
  tools: [
    {
      name: "app-info",
      description: "Get application information",
      inputSchema: { type: "object", properties: {} },
      async execute() {
        return {
          content: [{
            type: "text",
            text: JSON.stringify({ name: "MyApp", version: "1.0.0" })
          }]
        };
      }
    }
  ]
});

Tool Descriptor

Each tool must have:
PropertyTypeDescription
namestringUnique identifier for the tool
descriptionstringNatural language description of what the tool does
inputSchemaobjectJSON Schema defining input parameters
executefunctionAsync function that implements the tool logic

Tool Response Format

Tools must return an object with:
{
  content: [
    {
      type: "text",      // or "image", "resource"
      text: "Result..."  // the response content
    }
  ],
  isError?: boolean     // optional error flag
}

Complete Examples

Simple Todo Example

let todos = [];

navigator.modelContext.registerTool({
  name: "add-todo",
  description: "Add a new todo item",
  inputSchema: {
    type: "object",
    properties: {
      text: { type: "string" }
    },
    required: ["text"]
  },
  async execute({ text }) {
    todos.push({ id: Date.now(), text, done: false });
    return {
      content: [{ type: "text", text: `Added: "${text}"` }]
    };
  }
});
For more examples, see the Examples page.

Dynamic Tool Registration (Component Lifecycle)

Perfect for managing tools tied to component lifecycle:
import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Register component-specific tool when component mounts (Bucket B)
    const registration = window.navigator.modelContext.registerTool({
      name: "component-action",
      description: "Action specific to this component",
      inputSchema: { type: "object", properties: {} },
      async execute() {
        // Access component state/methods here
        return {
          content: [{ type: "text", text: "Component action executed!" }]
        };
      }
    });

    // Cleanup: unregister when component unmounts
    return () => {
      registration.unregister();
    };
  }, []);

  return <div>My Component</div>;
}

Tool Persistence Example

// Base tools via provideContext()
navigator.modelContext.provideContext({
  tools: [{ name: "app-info", description: "App info", inputSchema: {}, async execute() {} }]
});

// Dynamic tool via registerTool() - persists independently
const reg = navigator.modelContext.registerTool({
  name: "component-action",
  description: "Component action",
  inputSchema: { type: "object", properties: {} },
  async execute() {
    return { content: [{ type: "text", text: "Done!" }] };
  }
});

// Later: unregister when no longer needed
reg.unregister();

Event-Based Tool Calls (Advanced)

For manifest-based or advanced scenarios, you can handle tool calls as events:
window.navigator.modelContext.addEventListener('toolcall', async (event) => {
  console.log(`Tool called: ${event.name}`, event.arguments);

  if (event.name === "custom-tool") {
    // Prevent default execution
    event.preventDefault();

    // Provide custom response
    event.respondWith({
      content: [{
        type: "text",
        text: "Custom response from event handler"
      }]
    });
  }

  // If not prevented, the tool's execute function will run normally
});

Advanced Configuration

Dual-Server Mode (Tab + Iframe)

By default, @mcp-b/global runs two MCP servers that share the same tool registry:
  1. Tab Server (TabServerTransport) - For same-window communication (e.g., browser extensions)
  2. Iframe Server (IframeChildTransport) - Auto-enabled when running in an iframe (window.parent !== window)
Both servers expose the same tools, allowing your tools to be accessed from:
  • Same-window clients (e.g., browser extension content scripts)
  • Parent pages (when your app runs in an iframe)
This dual-server architecture is automatic - no configuration needed for basic usage. The package detects when it’s running in an iframe and enables both servers automatically.

How It Works

When you register tools via registerTool() or provideContext(), they become available on both servers simultaneously:
import '@mcp-b/global';

// Register a tool - automatically available on both servers!
navigator.modelContext.registerTool({
  name: "get_data",
  description: "Get application data",
  inputSchema: { type: "object", properties: {} },
  async execute() {
    return {
      content: [{ type: "text", text: "Data from iframe!" }]
    };
  }
});

// This tool is now accessible via:
// 1. TabServerTransport (same-window clients)
// 2. IframeChildTransport (parent page via IframeParentTransport)

Usage in Iframes

When your application runs inside an iframe (like in the MCP-UI integration pattern), the iframe server automatically enables: In the iframe (your app):
import { initializeWebModelContext } from '@mcp-b/global';

// Initialize with default dual-server mode
initializeWebModelContext({
  transport: {
    tabServer: {
      allowedOrigins: ['*'], // Allow connections from same window
    },
    // iframeServer auto-enabled when window.parent !== window
  },
});

// Now register your tools
navigator.modelContext.registerTool({
  name: "tictactoe_get_state",
  description: "Get current game state",
  inputSchema: { type: "object", properties: {} },
  async execute() {
    // Your game logic here
    return {
      content: [{ type: "text", text: "Game state: X's turn" }]
    };
  }
});
In the parent page:
import { IframeParentTransport } from '@mcp-b/transports';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

const iframe = document.querySelector('iframe');

// Connect to iframe's MCP server
const client = new Client({ name: 'parent', version: '1.0.0' });
const transport = new IframeParentTransport({
  iframe: iframe,
  targetOrigin: 'https://iframe-app.com',
});

await client.connect(transport);

// Discover tools from iframe
const { tools } = await client.listTools();
console.log('Tools from iframe:', tools);
// Output: [{ name: 'tictactoe_get_state', ... }]

Configuration Options

Use initializeWebModelContext(options) to customize transport behavior:
import { initializeWebModelContext } from '@mcp-b/global';

initializeWebModelContext({
  transport: {
    // Tab server options (for same-window communication)
    tabServer: {
      allowedOrigins: ['https://your-app.com'],
      channelId: 'custom-channel', // Default: 'mcp-tab'
    },

    // Iframe server options (for parent-child communication)
    iframeServer: {
      allowedOrigins: ['https://parent-app.com'], // Only allow specific parent
      channelId: 'custom-iframe-channel', // Default: 'mcp-iframe'
    },
  },
});
Disable Tab Server (iframe-only mode):
initializeWebModelContext({
  transport: {
    tabServer: false, // Disable tab server
    iframeServer: {
      allowedOrigins: ['https://parent-app.com'],
    },
  },
});
Disable Iframe Server (tab-only mode):
initializeWebModelContext({
  transport: {
    tabServer: {
      allowedOrigins: ['*'],
    },
    iframeServer: false, // Disable iframe server
  },
});
Custom Transport (advanced):
import { CustomTransport } from './my-transport';

initializeWebModelContext({
  transport: {
    create: () => new CustomTransport(), // Replaces both servers
  },
});

Data Attribute Configuration

When using the IIFE script tag, configure via data attributes:
<script
  src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"
  data-webmcp-allowed-origins="https://trusted-parent.com"
  data-webmcp-channel-id="my-channel"
></script>
Or use JSON for advanced configuration:
<script
  src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"
  data-webmcp-options='{"transport":{"iframeServer":{"allowedOrigins":["https://parent.com"]}}}'
></script>

Real-World Example: TicTacToe Mini-App

The TicTacToe example demonstrates dual-server mode in production:
// mini-apps/tictactoe/main.tsx
import { initializeWebModelContext } from '@mcp-b/global';
import { TicTacToeWithWebMCP } from './TicTacToeWithWebMCP';

// CRITICAL: Initialize BEFORE rendering React
initializeWebModelContext({
  transport: {
    tabServer: {
      allowedOrigins: ['*'], // Allow parent to connect
    },
    // iframeServer auto-enabled when in iframe
  },
});

// Render app
createRoot(document.getElementById('root')!).render(
  <TicTacToeWithWebMCP />
);
The game component registers three tools that become available to both:
  • Tab clients (browser extensions in the same window)
  • Parent page (chat UI via IframeParentTransport)
See the complete TicTacToe example in the mcp-ui-webmcp repository for the full implementation.

Security Best Practices

Production Security:
  • Never use allowedOrigins: ['*'] in production for iframe server
  • Always specify explicit parent origins: allowedOrigins: ['https://trusted-parent.com']
  • Tab server can use ['*'] for same-window clients (less risky)
  • Validate all tool inputs regardless of origin
// ✅ Good - Explicit origins for iframe server
initializeWebModelContext({
  transport: {
    tabServer: {
      allowedOrigins: ['*'], // OK for same-window
    },
    iframeServer: {
      allowedOrigins: ['https://parent-app.com'], // Explicit for cross-origin
    },
  },
});

// ❌ Bad - Wildcard for iframe server
initializeWebModelContext({
  transport: {
    iframeServer: {
      allowedOrigins: ['*'], // DANGER: Any parent can connect!
    },
  },
});

Feature Detection

Check if the API is available:
if ("modelContext" in navigator) {
  // API is available
  navigator.modelContext.provideContext({ tools: [...] });
} else {
  console.warn("Web Model Context API not available");
}

Debugging

In development mode, access the internal bridge:
if (window.__mcpBridge) {
  console.log("MCP Server:", window.__mcpBridge.server);
  console.log("Registered tools:", window.__mcpBridge.tools);
}

What’s Included

  • Web Model Context API - Standard window.navigator.modelContext interface
  • Dynamic Tool Registration - registerTool() with unregister() function
  • MCP Bridge - Automatic bridging to Model Context Protocol
  • Dual-Server Mode - Tab + Iframe servers sharing the same tool registry
  • Tab Transport - Communication layer for same-window contexts
  • Iframe Transport - Cross-origin parent-child communication (auto-enabled in iframes)
  • Event System - Hybrid tool call handling
  • TypeScript Types - Full type definitions included

Security Considerations

Tool Validation

Always validate inputs in your tool implementations:
{
  name: "delete-item",
  description: "Delete an item",
  inputSchema: {
    type: "object",
    properties: {
      id: { type: "string", pattern: "^[a-zA-Z0-9]+$" }
    },
    required: ["id"]
  },
  async execute({ id }) {
    // Additional validation
    if (!isValidId(id)) {
      return {
        content: [{ type: "text", text: "Invalid ID" }],
        isError: true
      };
    }

    // Proceed with deletion
    await deleteItem(id);
    return {
      content: [{ type: "text", text: "Item deleted" }]
    };
  }
}

External Resources

License

MIT - see LICENSE for details

Support