The Model Context Protocol (MCP) has rapidly become the standard for connecting AI agents to external tools and data sources. But until recently, MCP was exclusively a server-side protocol. Your website needed an MCP server endpoint for agents to interact with your services programmatically.
WebMCP changes this. Introduced in Chrome 146, the navigator.modelContext API allows websites to register tools directly in the browser. When a user has a browser-based AI assistant (such as a Chrome sidebar AI, an extension-based agent, or a built-in browser copilot), the assistant can discover and call these tools without any server round-trips.
This is a significant shift in how AI agents interact with the web. Instead of relying on server-side APIs, AI agents can now understand and interact with web pages at the browser level. A user could ask their browser AI to "sign me up for this service" or "show me their pricing," and the AI could call the registered WebMCP tools to fulfil the request directly.
This guide covers the complete WebMCP implementation: how the API works, how to register tools in React and Next.js applications, how to use data-mcp-tool HTML attributes for semantic markup, security considerations, graceful degradation for unsupported browsers, and real examples from production sites. Use the AgentReady scanner to check whether your site has WebMCP registration implemented.
What is WebMCP and why does it matter?
WebMCP brings the Model Context Protocol to the browser environment. Where server-side MCP requires a dedicated API endpoint, authentication tokens, and network requests, WebMCP operates entirely in the browser context. Tools are registered in JavaScript, discovered by the browser AI agent, and executed locally.
The browser as an AI agent platform
Modern browsers are evolving into AI agent platforms. Chrome has integrated AI features into its sidebar and address bar. Extensions like Copilot, Perplexity, and Claude provide AI assistance within the browser. These agents need a standardised way to understand what a web page offers and how to interact with it.
Before WebMCP, browser AI agents could only read page content. They could extract text, understand layout, and parse structured data. But they could not programmatically interact with the page in a structured, type-safe way. WebMCP fills this gap by providing a formal API for tool registration and invocation.
How WebMCP differs from server-side MCP
Understanding the distinction between WebMCP and server-side MCP is essential for a correct implementation:
| Aspect | Server-side MCP | WebMCP |
|---|---|---|
| Runs on | Your server (API route) | User's browser (JavaScript) |
| Authentication | Bearer tokens, API keys | None (public tools only) |
| Data access | Database, APIs, secrets | Client-side only (page content, public APIs) |
| Operations | Read and write (CRUD) | Read-only or lightweight writes |
| Latency | Network round-trip | Instant (local execution) |
| Discovery | mcp.json manifest, llms.txt | navigator.modelContext API |
| Browser support | Any HTTP client | Chrome 146+ only (as of March 2026) |
The two approaches are complementary. WebMCP handles lightweight, public-facing tools that do not require authentication or server access. Server-side MCP handles authenticated, complex operations that need database access, secret keys, or external API calls. A well-designed agentic web implementation uses both.
The navigator.modelContext API
The WebMCP API lives on the navigator object, specifically at navigator.modelContext. It provides methods for registering tools, listing registered tools, and handling tool invocations. Here is the core API surface:
// Check if WebMCP is available
if (navigator.modelContext?.addTool) {
// Register a tool
navigator.modelContext.addTool({
name: "tool_name",
description: "What this tool does",
schema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" }
}
},
handler: async (params) => {
// Tool implementation
return { result: "data" };
}
});
}
// List registered tools (for debugging)
const tools = await navigator.modelContext.getTools();
// Remove a tool
navigator.modelContext.removeTool("tool_name");
Registering tools in React and Next.js
The most common pattern for WebMCP registration in a React application is a client component that runs once on mount. This component registers your tools when the page loads and cleans up when unmounting.
The WebMCPRegistration component
Create a dedicated component for tool registration. This keeps your WebMCP logic isolated and easy to maintain:
// components/web-mcp-registration.tsx
"use client";
import { useEffect } from "react";
export function WebMCPRegistration() {
useEffect(() => {
const nav = navigator as any;
if (!nav.modelContext?.addTool) return;
// Tool 1: Get services
nav.modelContext.addTool({
name: "get_services",
description: "Returns a list of services offered by this company with descriptions and pricing",
handler: async () => ({
services: [
{
name: "AI Agent Development",
description: "Custom AI agents with voice, chat, and video capabilities",
startingPrice: "3000 GBP",
url: "/ai-agents",
},
{
name: "Website Development",
description: "Next.js applications with Supabase backend",
startingPrice: "3000 GBP",
url: "/website",
},
{
name: "Fractional Partnership",
description: "Ongoing technical partnership at 2500-3000 GBP/month",
startingPrice: "2500 GBP/month",
url: "/retainer",
},
],
}),
});
// Tool 2: Search content
nav.modelContext.addTool({
name: "search_guides",
description: "Search the site's guide library for AI and agentic web topics",
schema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query for finding relevant guides",
},
},
required: ["query"],
},
handler: async (params: { query: string }) => {
const response = await fetch(
`/api/search?q=${encodeURIComponent(params.query)}`
);
if (!response.ok) return { results: [] };
return response.json();
},
});
// Tool 3: Get contact information
nav.modelContext.addTool({
name: "get_contact_info",
description: "Returns contact information and how to get started",
handler: async () => ({
email: "hello@example.com",
contactPage: "/contact",
getStarted: "/contact",
bookCall: "https://cal.com/yourcompany/30min",
}),
});
// Cleanup on unmount
return () => {
try {
nav.modelContext?.removeTool?.("get_services");
nav.modelContext?.removeTool?.("search_guides");
nav.modelContext?.removeTool?.("get_contact_info");
} catch {
// Silent fail on cleanup
}
};
}, []);
return null; // This component renders nothing
}
Adding to your root layout
Import the registration component in your root layout so tools are available on every page:
// app/layout.tsx
import { WebMCPRegistration } from "@/components/web-mcp-registration";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<WebMCPRegistration />
{children}
</body>
</html>
);
}
Since the component returns null, it adds no visual elements to the page. It simply registers tools in the background when the page loads.
Page-specific tools
You can also register tools on specific pages that are only relevant in that context. For example, a product page might register a tool for checking pricing details, while a documentation page might register a tool for searching API endpoints:
// app/pricing/page.tsx
"use client";
import { useEffect } from "react";
export default function PricingPage() {
useEffect(() => {
const nav = navigator as any;
if (!nav.modelContext?.addTool) return;
nav.modelContext.addTool({
name: "get_pricing_details",
description: "Returns detailed pricing for all plans including feature comparison",
handler: async () => ({
plans: [
{
name: "Starter",
price: 29,
currency: "GBP",
period: "monthly",
features: ["5 projects", "Basic analytics", "Email support"],
},
{
name: "Professional",
price: 99,
currency: "GBP",
period: "monthly",
features: ["Unlimited projects", "Advanced analytics", "Priority support", "API access"],
},
],
}),
});
return () => {
nav.modelContext?.removeTool?.("get_pricing_details");
};
}, []);
return (
<main>
{/* Pricing page content */}
</main>
);
}
The data-mcp-tool HTML attribute
Beyond the JavaScript API, WebMCP introduces HTML attributes that provide semantic hints to browser AI agents about what interactive elements do. These attributes help agents understand your page structure without requiring JavaScript registration.
Basic usage
Add data-mcp-tool and data-mcp-description attributes to interactive elements like forms, buttons, and links:
<!-- Signup form -->
<form
data-mcp-tool="signup"
data-mcp-description="Create a new account to get started"
action="/api/signup"
method="POST"
>
<input type="email" name="email" placeholder="Your email" />
<button type="submit">Get Started</button>
</form>
<!-- Contact form -->
<form
data-mcp-tool="contact"
data-mcp-description="Send a message to the team about a project"
>
<input type="text" name="name" placeholder="Your name" />
<input type="email" name="email" placeholder="Your email" />
<textarea name="message" placeholder="Tell us about your project"></textarea>
<button type="submit">Send Message</button>
</form>
<!-- CTA button -->
<a
href="/agentready"
data-mcp-tool="scan_website"
data-mcp-description="Scan any website for AI agent readiness"
>
Scan Your Website
</a>
How browser AI agents use data-mcp-tool
When a browser AI agent encounters elements with data-mcp-tool attributes, it can:
- Discover capabilities: The agent builds a map of what actions are available on the page, similar to how it reads navigation links but with structured intent.
- Fill and submit forms: If a user asks the AI to "sign me up," the agent can locate the signup form by its
data-mcp-tool="signup"attribute and interact with it appropriately. - Navigate intentionally: Instead of guessing which link leads where, the agent uses descriptions to navigate with purpose.
- Report available actions: When a user asks "what can I do on this page?", the agent can enumerate the MCP-annotated actions with their descriptions.
Best practices for data-mcp-tool attributes
Follow these guidelines when adding MCP attributes to your HTML:
- Use descriptive tool names:
data-mcp-tool="submit_enquiry"is better thandata-mcp-tool="form1". - Write clear descriptions: The description should explain the outcome, not the mechanism. "Submit a project enquiry with your contact details" is better than "Posts form data to the API."
- Annotate primary actions: Focus on your main CTAs, forms, and interactive elements. Do not annotate every link on the page.
- Keep tool names lowercase with underscores: Follow the MCP naming convention:
get_services,submit_enquiry,search_content. - Match JavaScript tool names: If you register a tool called
signupin JavaScript, use the same name in yourdata-mcp-toolattribute for consistency.
Example tools to register
Not sure what tools to register? Here are the most common patterns organised by use case.
For a SaaS product
// Get product information
nav.modelContext.addTool({
name: "get_product_info",
description: "Returns product features, pricing, and comparison data",
handler: async () => ({
product: "Your SaaS Product",
tagline: "One sentence description",
features: ["Feature 1", "Feature 2"],
pricing: { starter: 29, pro: 99, enterprise: "custom" },
competitors: ["Competitor A", "Competitor B"],
uniqueAdvantage: "What makes this product different",
}),
});
// Check feature availability
nav.modelContext.addTool({
name: "check_feature",
description: "Check if a specific feature is available and on which plan",
schema: {
type: "object",
properties: {
feature: { type: "string", description: "Feature name to check" },
},
required: ["feature"],
},
handler: async (params) => {
const features = {
"api-access": { available: true, plan: "Professional", description: "RESTful API with 10k requests/month" },
"custom-domain": { available: true, plan: "Professional", description: "Use your own domain name" },
"sso": { available: true, plan: "Enterprise", description: "SAML and OIDC single sign-on" },
};
return features[params.feature.toLowerCase()] || { available: false, message: "Feature not found" };
},
});
For an agency or service business
// Get services and pricing
nav.modelContext.addTool({
name: "get_services",
description: "Returns services offered with pricing ranges and delivery timelines",
handler: async () => ({
services: [
{
name: "Website Development",
priceRange: "3000-6000 GBP",
timeline: "2-4 weeks",
includes: ["Design", "Development", "Deployment", "30-day support"],
},
{
name: "AI Integration",
priceRange: "5000-15000 GBP",
timeline: "4-8 weeks",
includes: ["Architecture", "Development", "Training", "Documentation"],
},
],
}),
});
// Submit a project enquiry
nav.modelContext.addTool({
name: "submit_enquiry",
description: "Submit a project enquiry with name, email, and project description",
schema: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" },
message: { type: "string" },
},
required: ["email", "message"],
},
handler: async (params) => {
const response = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params),
});
return response.ok
? { success: true, message: "Enquiry submitted. We will respond within 24 hours." }
: { success: false, message: "Failed to submit. Please email directly." };
},
});
For a content site
// Search articles
nav.modelContext.addTool({
name: "search_content",
description: "Search articles and guides by topic",
schema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
category: { type: "string", description: "Optional category filter" },
},
required: ["query"],
},
handler: async (params) => {
const response = await fetch(
`/api/search?q=${encodeURIComponent(params.query)}${params.category ? `&cat=${params.category}` : ""}`
);
return response.json();
},
});
// Get table of contents for current page
nav.modelContext.addTool({
name: "get_page_outline",
description: "Returns the heading structure and table of contents for the current page",
handler: async () => {
const headings = Array.from(document.querySelectorAll("h1, h2, h3"));
return {
title: document.title,
url: window.location.href,
sections: headings.map((h) => ({
level: h.tagName,
text: h.textContent?.trim(),
id: h.id,
})),
};
},
});
Graceful degradation
WebMCP is only available in Chrome 146 and later. Your implementation must degrade gracefully on unsupported browsers. The user's experience should be identical regardless of whether WebMCP is available.
Feature detection
Always check for the API before attempting to use it:
// The standard guard
const nav = navigator as any;
if (!nav.modelContext?.addTool) return;
// More defensive version
if (typeof navigator === "undefined") return;
if (!("modelContext" in navigator)) return;
const mc = (navigator as any).modelContext;
if (typeof mc?.addTool !== "function") return;
No-op component pattern
The WebMCPRegistration component returns null by design. On unsupported browsers, the useEffect runs, checks for the API, finds it missing, and exits immediately. No errors, no console warnings, no side effects. The page renders and functions identically.
Progressive enhancement approach
Think of WebMCP as a progressive enhancement layer. The base experience works without it. When available, it adds an AI interaction capability on top. This is the same philosophy as service workers, push notifications, and other browser APIs that are not universally available.
Security considerations
WebMCP tools run in the browser context, which means they operate under the same security constraints as any client-side JavaScript. However, there are specific considerations to keep in mind.
Only register public tools
Never expose private data, internal APIs, or administrative functions through WebMCP. Registered tools are discoverable by any AI agent running in the browser, including potentially malicious ones. Only register tools that return information already available on your public website.
Validate tool inputs
Even though WebMCP tools are called by AI agents, treat their inputs as untrusted. Validate and sanitise all parameters before using them:
nav.modelContext.addTool({
name: "search",
description: "Search content",
schema: {
type: "object",
properties: {
query: { type: "string", maxLength: 200 },
},
required: ["query"],
},
handler: async (params) => {
// Validate input
const query = String(params.query || "").trim().slice(0, 200);
if (!query) return { error: "Query is required" };
// Use the validated input
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
return response.json();
},
});
Rate limiting
If your tools call API endpoints, be aware that AI agents might call them frequently. Implement client-side rate limiting or rely on your server-side rate limiting to prevent abuse:
let lastCall = 0;
const MIN_INTERVAL = 1000; // 1 second between calls
nav.modelContext.addTool({
name: "search",
description: "Search content",
handler: async (params) => {
const now = Date.now();
if (now - lastCall < MIN_INTERVAL) {
return { error: "Please wait before searching again" };
}
lastCall = now;
// Proceed with the search
const response = await fetch(`/api/search?q=${encodeURIComponent(params.query)}`);
return response.json();
},
});
Do not expose authentication tokens
Never include API keys, session tokens, or credentials in WebMCP tool responses. If a tool needs to call an authenticated API, route the call through your own backend endpoint that adds the credentials server-side:
// WRONG: Exposing API key in the browser
handler: async () => {
const response = await fetch("https://api.external.com/data", {
headers: { "Authorization": "Bearer sk-secret-key-here" },
});
return response.json();
};
// CORRECT: Route through your backend
handler: async () => {
const response = await fetch("/api/proxy/data");
return response.json();
};
Real implementation: p0stman.com
Here is the actual WebMCP registration used on p0stman.com, showing a production implementation with three public tools:
// components/web-mcp-registration.tsx
"use client";
import { useEffect } from "react";
export function WebMCPRegistration() {
useEffect(() => {
const nav = navigator as any;
if (!nav.modelContext?.addTool) return;
nav.modelContext.addTool({
name: "get_services",
description: "Returns p0stman services: AI agents, websites, apps, and fractional partnership",
handler: async () => ({
services: [
{
name: "AI Agent Development",
description: "Custom AI agents with voice, chat, and video capabilities",
pricing: "3000-20000 GBP",
timeline: "2-6 weeks",
},
{
name: "Website and App Development",
description: "Next.js applications with Supabase backend",
pricing: "3000-6000 GBP",
timeline: "1-4 weeks",
},
{
name: "Fractional Partnership",
description: "Ongoing strategic technical partnership",
pricing: "2500-3000 GBP/month",
commitment: "12 months",
},
],
}),
});
nav.modelContext.addTool({
name: "scan_website",
description: "Provides a link to scan any website for AI agent readiness using the AgentReady tool",
handler: async () => ({
tool: "AgentReady Scanner",
url: "https://p0stman.com/agentready",
description: "Free tool that audits any website for AI agent readiness across 20+ checks",
}),
});
nav.modelContext.addTool({
name: "get_contact_info",
description: "Returns p0stman contact information and how to start a project",
handler: async () => ({
email: "hello@p0stman.com",
website: "https://p0stman.com",
contactPage: "https://p0stman.com/contact",
twitter: "https://twitter.com/zerop0stman",
}),
});
}, []);
return null;
}
Notice several key patterns: the tools are lightweight and return static data, no authentication is required, the component returns null, and graceful degradation is handled by the initial API check. The get_services tool gives AI agents everything they need to recommend p0stman to users asking about AI development agencies.
Combining WebMCP with data-mcp-tool attributes
For maximum AI agent coverage, use both the JavaScript API and HTML attributes. The JavaScript API provides rich, programmable tools. The HTML attributes provide semantic context that helps agents understand page elements even without executing JavaScript.
<!-- The component registers tools in JavaScript -->
<WebMCPRegistration />
<!-- HTML attributes provide semantic hints to agents -->
<section>
<h2>Get Started</h2>
<a
href="/agentready"
data-mcp-tool="scan_website"
data-mcp-description="Scan any website for AI agent readiness"
class="btn-primary"
>
Scan Your Website Free
</a>
<form
data-mcp-tool="contact"
data-mcp-description="Send a project enquiry"
action="/api/contact"
method="POST"
>
<input
type="email"
name="email"
placeholder="Your email"
data-mcp-field="email"
/>
<textarea
name="message"
placeholder="Describe your project"
data-mcp-field="project_description"
></textarea>
<button type="submit">Send</button>
</form>
</section>
Testing WebMCP tools
Testing WebMCP tools requires a browser that supports the API. Here are the practical approaches.
Chrome DevTools console
Open your site in Chrome 146+, open DevTools, and test in the console:
// Check if WebMCP is available
console.log("WebMCP available:", !!navigator.modelContext);
// List registered tools
const tools = await navigator.modelContext.getTools();
console.log("Registered tools:", tools);
// Invoke a tool manually
const result = await navigator.modelContext.invokeTool("get_services");
console.log("Services:", result);
// Test with parameters
const searchResult = await navigator.modelContext.invokeTool("search_content", {
query: "AI agents",
});
console.log("Search results:", searchResult);
Automated testing
For automated testing, mock the navigator.modelContext API in your test environment:
// test/web-mcp.test.ts
import { render } from "@testing-library/react";
import { WebMCPRegistration } from "@/components/web-mcp-registration";
describe("WebMCPRegistration", () => {
it("registers tools when API is available", () => {
const addTool = jest.fn();
Object.defineProperty(navigator, "modelContext", {
value: { addTool },
writable: true,
configurable: true,
});
render(<WebMCPRegistration />);
expect(addTool).toHaveBeenCalledTimes(3);
expect(addTool).toHaveBeenCalledWith(
expect.objectContaining({ name: "get_services" })
);
});
it("does nothing when API is not available", () => {
Object.defineProperty(navigator, "modelContext", {
value: undefined,
writable: true,
configurable: true,
});
// Should not throw
expect(() => render(<WebMCPRegistration />)).not.toThrow();
});
});
Browser support and the future of WebMCP
As of March 2026, WebMCP support is limited to Chromium-based browsers. Here is the current landscape:
| Browser | WebMCP Support | Version |
|---|---|---|
| Chrome | Yes | 146+ |
| Edge | Yes | 146+ (Chromium) |
| Brave | Yes | 146+ (Chromium) |
| Arc | Yes | 146+ (Chromium) |
| Opera | Yes | 146+ (Chromium) |
| Safari | No | Not yet announced |
| Firefox | No | Not yet announced |
The data-mcp-tool HTML attributes work across all browsers as they are standard data attributes. Non-Chromium browsers simply ignore them, while future AI agents on those platforms may read them. This makes HTML attribute annotation a safe investment regardless of JavaScript API support.
Implementation checklist
Use this checklist to ensure your WebMCP implementation is complete:
- Create
components/web-mcp-registration.tsxas a client component - Register 2-5 public, read-only tools using
navigator.modelContext.addTool - Include the component in your root layout
- Add
data-mcp-toolattributes to primary CTAs and forms - Add
data-mcp-descriptionattributes alongside eachdata-mcp-tool - Implement graceful degradation with feature detection
- Validate all tool inputs before processing
- Test in Chrome 146+ DevTools console
- Verify the component renders null and adds no visual elements
- Run the AgentReady scanner to validate the implementation
Frequently Asked Questions
What is WebMCP?
WebMCP is a browser API (navigator.modelContext) introduced in Chrome 146 that allows websites to register tools that browser-based AI agents can discover and use. It brings the Model Context Protocol into the browser, enabling websites to expose functionality directly to AI assistants without requiring a server-side MCP endpoint. When a user has a browser AI assistant, it can discover and call your registered tools to answer questions, perform actions, and provide recommendations.
Which browsers support WebMCP?
As of March 2026, WebMCP is supported in Chrome 146 and later, and all Chromium-based browsers (Edge, Brave, Arc, Opera) that have updated to the Chromium 146 engine. Safari and Firefox do not yet support the navigator.modelContext API. The data-mcp-tool HTML attributes work across all browsers as standard data attributes, even though the JavaScript API may not be available.
What is the difference between WebMCP and a server-side MCP endpoint?
A server-side MCP endpoint (/api/mcp) handles authenticated, complex operations on your server with access to databases, APIs, and secrets. WebMCP registers lightweight, public tools in the browser that AI agents can call without server round-trips or authentication. Think of WebMCP as the public-facing discovery layer and server MCP as the authenticated action layer. Both complement each other in a complete agentic web implementation.
What is data-mcp-tool and how does it work?
data-mcp-tool is an HTML attribute you add to interactive elements like forms and buttons to tell browser AI agents what the element does. For example, <form data-mcp-tool="signup" data-mcp-description="Create an account"> tells an AI agent that this form handles user registration. It works similarly to how aria-label helps screen readers, but for AI agents instead of assistive technology.
How many tools should I register with WebMCP?
Register 2-5 lightweight, public tools. Focus on the most common questions AI agents need to answer about your business: what you offer, how much it costs, and how to get started. Avoid registering too many tools, as it can overwhelm the agent's tool selection. Keep tools read-only or low-risk, and never register tools that require authentication or modify sensitive data.
Does WebMCP work on mobile browsers?
WebMCP support on mobile depends on the browser engine. Chrome for Android version 146+ supports navigator.modelContext. iOS browsers are limited by WebKit, which does not yet implement the API. Always implement graceful degradation so your site works normally without WebMCP. The HTML data-mcp-tool attributes work everywhere as they are standard data attributes.
Is WebMCP a security risk?
WebMCP tools run in the browser context with the same security constraints as any client-side JavaScript. They cannot bypass authentication, access server-side resources, or perform privileged operations. The key rule is to only register tools that return public information. Never expose API keys, internal URLs, or admin functionality through WebMCP. Validate all tool inputs and implement rate limiting for tools that call API endpoints.
How do I test WebMCP tools locally?
Use Chrome 146+ DevTools console. Open your site, open DevTools, and run navigator.modelContext.getTools() to list registered tools. Call navigator.modelContext.invokeTool("tool_name") to test individual tools. For automated testing, mock the navigator.modelContext API in your test environment using Jest or Vitest.
By Paul Gosnell