You read the concept pieces. You understand why agents should call tools instead of guessing which button is "Checkout." Now you need the implementation path — file by file, API by API, with code you can paste into a real project today.
This is that guide. We walk through enabling WebMCP in Chrome, registering your first imperative tool, converting an HTML form with the declarative API, wiring it into a Next.js or React app, testing with Google's inspector extension, and shipping without breaking users on browsers that do not support the API yet.
If WebMCP is new to you, start with our overview: WebMCP Explained — How to Make Your Website Usable by AI Agents. That article covers the why, WebMCP vs server-side MCP, and when implementation is worth the effort. This post assumes you are ready to write code.
What You Are Building
By the end of this tutorial, your site will:
- Detect whether
document.modelContextexists before calling it - Register at least one imperative JavaScript tool with JSON Schema inputs
- Expose at least one HTML form as a declarative WebMCP tool
- Return human-readable strings agents can relay to users
- Handle agent-initiated form submission differently from human clicks
- Pass a basic inspection test in Chrome's Model Context Tool Inspector
We use vanilla JavaScript for clarity, then show how the same patterns map to React and Next.js — the stack we use at Let Start Design for agent-ready client projects.
Prerequisites
- Chrome 149+ for origin trial support, or Chrome with the local testing flag enabled
- A site served over HTTPS (or localhost for development)
- Origin isolation — WebMCP is disabled if
document.domainis set or origin-agent-cluster is broken. Standard modern deployments are fine. - Basic comfort with JSON Schema and async JavaScript
Official reference: WebMCP documentation on Chrome for Developers.
Step 1: Enable WebMCP in Chrome
For local development, enable the testing flag:
- Open
chrome://flags/#enable-webmcp-testing - Set Enable WebMCP testing to Enabled
- Relaunch Chrome
For staging or production experiments, join the WebMCP origin trial (Chrome 149+). Track API changes on GitHub and the API proposal.
Step 2: Add Feature Detection (Non-Negotiable)
WebMCP is progressive enhancement. Your site must work when the API is absent.
function supportsWebMCP() {
return typeof document !== 'undefined'
&& 'modelContext' in document
&& typeof document.modelContext.registerTool === 'function';
}
async function initWebMCP() {
if (!supportsWebMCP()) {
console.info('[WebMCP] API not available — skipping tool registration.');
return;
}
await registerSiteTools();
}
Call initWebMCP() after your app shell mounts. In Next.js App Router, use a client component — never register tools during SSR, because document does not exist on the server.
Step 3: Install TypeScript Types (Optional but Recommended)
npm install webmcp-types --save-dev
Add a reference in your global types or tsconfig so document.modelContext autocompletes correctly. Validate JSON Schema payloads during development with our free JSON Formatter & Validator — mistyped schemas are the number-one reason tools fail inspection.
Step 4: Register Your First Imperative Tool
Start with one read-only tool. Read-only tools are safer to ship first — they cannot accidentally mutate state while you are learning the API.
async function registerSiteTools() {
await document.modelContext.registerTool({
name: 'search_help_articles',
description:
'Search the help center by keyword. Use when the user asks how to do something on the site. Returns article titles and URLs.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search phrase, e.g. "reset password" or "billing invoice"',
},
limit: {
type: 'integer',
minimum: 1,
maximum: 10,
description: 'Max results to return. Defaults to 5.',
},
},
required: ['query'],
},
annotations: {
readOnlyHint: true,
},
execute: async ({ query, limit = 5 }, { signal }) => {
const res = await fetch(
`/api/help-search?q=${encodeURIComponent(query)}&limit=${limit}`,
{ signal }
);
if (!res.ok) throw new Error(`Search failed: ${res.status}`);
const articles = await res.json();
if (!articles.length) {
return `No help articles matched "${query}". Suggest the user contact support.`;
}
const lines = articles.map((a) => `- ${a.title}: ${a.url}`);
return `Found ${articles.length} article(s) for "${query}":\n${lines.join('\n')}`;
},
});
}
Patterns that matter here — straight from Google's WebMCP best practices:
- One job per tool. Do not combine search + delete + export in one callback.
- Verb-led names.
search_help_articlesbeatshelpTool. - Positive descriptions. Say what the tool does, not "don't use for weather."
- Specific return strings. Agents quote your output back to users.
- Pass
signalto fetch. Cancels in-flight requests when the user aborts.

Step 5: Add a Write Tool With Security Annotations
Once read-only tools pass testing, add a tool that changes state — but annotate it honestly.
await document.modelContext.registerTool({
name: 'add_item_to_quote',
description:
'Add a catalog SKU to the user quote cart. Use after confirming product and quantity with the user.',
inputSchema: {
type: 'object',
properties: {
sku: { type: 'string', description: 'Product SKU, e.g. WP-HOST-01' },
quantity: { type: 'integer', minimum: 1, maximum: 99 },
},
required: ['sku', 'quantity'],
},
annotations: {
readOnlyHint: false,
consequentialHint: true,
},
execute: async ({ sku, quantity }) => {
const item = await addToQuoteCart(sku, quantity);
updateQuoteDrawerUI(item);
return `Added ${quantity}× ${item.name} (${sku}). Quote subtotal: $${item.subtotal}.`;
},
});
consequentialHint: true tells the browser this action may need explicit user confirmation before execution — similar to how payment flows should never run silently. Read Google's WebMCP security guidance before exposing account, billing, or deletion tools.
Step 6: Declarative API — Annotate an HTML Form
Contact forms, support tickets, and newsletter signups are ideal declarative candidates. You already have the markup — add three attributes.
<form
id="contact-form"
toolname="submit_contact_inquiry"
tooldescription="Send a contact message to the Let Start Design team. Use when the user wants a quote, demo, or general inquiry."
action="/api/contact"
method="post"
>
<label for="name">Name</label>
<input id="name" name="name" type="text" required
toolparamdescription="Full name of the person submitting the inquiry." />
<label for="email">Email</label>
<input id="email" name="email" type="email" required
toolparamdescription="Reply-to email address." />
<label for="service">Service needed</label>
<select id="service" name="service" required
toolparamdescription="Primary service the user is interested in.">
<option value="web-design">Web design</option>
<option value="wordpress">WordPress development</option>
<option value="seo">SEO services</option>
<option value="white-label">White label partnership</option>
</select>
<label for="message">Message</label>
<textarea id="message" name="message" required
toolparamdescription="Project details, timeline, and budget if known."></textarea>
<button type="submit">Send message</button>
</form>
Handle agent vs human submission in JavaScript:
document.getElementById('contact-form').addEventListener('submit', async (e) => {
if (!e.agentInvoked) return; // normal human submit — browser default
e.preventDefault();
if (!validateContactForm()) {
e.respondWith(Promise.resolve('Validation failed: email and message are required.'));
return;
}
const formData = new FormData(e.target);
e.respondWith(
fetch('/api/contact', { method: 'POST', body: formData })
.then((r) => r.json())
.then((data) => `Inquiry sent. Reference ID: ${data.id}. Team will reply within 1 business day.`)
.catch(() => 'Submission failed. Ask the user to try again or email hello@letstartdesign.com directly.')
);
});
Listen for lifecycle events so your UI reflects agent activity:
window.addEventListener('toolactivated', ({ toolName }) => {
document.body.dataset.activeTool = toolName;
});
window.addEventListener('toolcancel', ({ toolName }) => {
delete document.body.dataset.activeTool;
showToast(`Agent cancelled: ${toolName}`);
});
Style agent focus with :tool-form-active and :tool-submit-active pseudo-classes so users see which form the agent is operating — details in the Declarative API docs.
Step 7: Next.js / React Integration
In App Router, isolate WebMCP in a client component:
'use client';
import { useEffect } from 'react';
export function WebMCPProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
initWebMCP().catch(console.error);
}, []);
return <>{children}</>;
}
Mount WebMCPProvider in your root layout. For route-specific tools (e.g. catalog page vs checkout), register on mount and unregister on unmount:
useEffect(() => {
if (!supportsWebMCP()) return;
const controller = new AbortController();
document.modelContext.registerTool(catalogTool, { signal: controller.signal });
return () => controller.abort(); // clean unregister
}, []);
React teams can also try the experimental usewebmcp package — it ties registration to component lifecycle with schema-driven types. Angular has similar experimental support. Our web development team evaluates these bindings per project; vanilla registration stays closest to the spec and ages better while the API is in flux.
Rebuilding a legacy site? If you are migrating platforms while adding WebMCP, coordinate with our SEO-safe migration guide so redirects and agent tools launch together.
Step 8: SPA State — When Tools Change by Route
Single-page apps expose different actions on different views. Do not register checkout tools on the homepage — it confuses agents and wastes context window tokens.
Two approaches:
- AbortController per route — register tools in
useEffect, abort on leave (shown above). - Full context refresh — call
provideContext({ tools: [...] })when route state changes (resets all tools at once).
Listen for toolchange on document.modelContext if your agent UI needs to refresh its tool list when the page updates registrations dynamically.
Step 9: Cross-Origin Iframes and Widgets
If tools live inside an embedded widget (booking calendar, payment frame), you need both:
<iframe src="https://partner.example/widget" allow="tools"></iframe>
And on the iframe origin, expose tools explicitly:
await document.modelContext.registerTool(widgetTool, {
exposedTo: ['https://yourdomain.com'],
});
Then the parent page discovers them with:
const tools = await document.modelContext.getTools({
fromOrigins: ['https://partner.example'],
});
Treat exposedTo like CORS — only list origins you trust.
Step 10: Test With the Model Context Tool Inspector
Install Chrome's Model Context Tool Inspector extension (linked from the WebMCP docs). It lets you:
- See registered tools and their JSON Schema on the live page
- Manually invoke
executeToolwith test arguments - Chat in natural language and verify the agent picks the right tool
- Read error messages when schema validation fails
Minimum test suite before merge:
- Call each tool with only required fields
- Call with an invalid enum value — confirm your execute handler or browser rejects it cleanly
- Cancel mid-fetch — confirm
AbortSignalstops the request - Navigate away in an SPA — confirm tools unregister (no stale tools in inspector)
- Submit declarative form as agent — confirm
agentInvokedbranch runs and UI updates
Google recommends evaluation-driven testing for agent outputs — define expected input/output contracts rather than brittle string matches. That aligns with how we test structured data: clear schemas, observable results. See our guide on schema markup for SEO for the same philosophy applied to search.
Step 11: WordPress and Existing CMS Sites
WordPress sites can add WebMCP without rebuilding the theme:
- Declarative first — add
toolname/tooldescriptionto Contact Form 7 or native forms in template files. - Imperative via child theme — enqueue a small
webmcp-tools.jsthat registers WooCommerce cart/search tools calling existing AJAX handlers. - Avoid duplicate logic — tool callbacks should call the same functions your buttons already use.
Our WordPress development team typically ships a must-use plugin with tool definitions separated from theme CSS — so agent support survives theme updates.
Common Errors and Fixes
Tools not appearing in inspector
Check origin isolation, HTTPS, and the Chrome flag. Confirm you called registerTool after hydration, not during SSR.
Schema validation failures
Enums must match exactly. Required arrays must list every mandatory property. Paste your schema into a validator — our JSON Formatter catches trailing commas and type mistakes fast.
Agent picks the wrong tool
Tool overlap. Merge similar tools or tighten descriptions. Google explicitly warns overlapping tools confuse model selection.
UI does not update after tool runs
Your execute callback mutated backend state but not DOM. Agents may call again, thinking the first attempt failed. Always mirror human-click code paths.
Cross-origin iframe tools missing
Missing allow="tools" on iframe or missing exposedTo / fromOrigins pairing.
Production Checklist
- ☐ Feature detection — site works without WebMCP
- ☐ Tool strategy doc — one function per tool, no overlap
- ☐ Read-only tools tested before write tools
- ☐
consequentialHinton purchases, deletes, sends - ☐
untrustedContentHinton user-generated tool output - ☐ SPA routes register/unregister tools correctly
- ☐ Agent form styling (
:tool-form-active) visible to users - ☐ Analytics event for
agentInvokedsubmissions - ☐ Origin trial token on staging (if applicable)
- ☐ Internal runbook for support team — what agents can and cannot do
WebMCP and SEO — What Developers Should Know
WebMCP does not replace crawl SEO. Agents still discover your site through search, links, and brand — same as humans. But sites with clean information architecture, fast Core Web Vitals, and valid structured data tend to implement WebMCP more cleanly because they already think in schemas.
Keep publishing helpful content, maintain your XML sitemap, and use FAQ schema where appropriate — our FAQ Schema Generator helps. For the strategic picture on agent-ready sites vs ranking, read WebMCP Explained.
When to Hire Help
This guide covers a single-site implementation. Enterprise scenarios — multi-tenant SaaS, complex auth, PCI-adjacent checkout, white-label partner embeds — need architecture review before tool registration.
Let Start Design implements WebMCP on Next.js, WordPress, and headless commerce stacks: tool mapping workshops, secure annotation review, inspector-based QA, and launch monitoring. View recent work or request a scoping call.
Related Reading
- WebMCP Explained: How to Make Your Website Usable by AI Agents
- How to Boost Your SEO by Using Schema Markup
- How to Change Your Website Platform Without Losing Google Rankings
- All AI Agents articles
Web development · WordPress development · Free developer tools




