In my previous post on WebMCP, I covered what WebMCP is and why it matters. The response told me something important: developers do not want another think piece about the future of the web. They want to know how to actually build with it.
So this is the practical companion. No philosophy. No speculation about whether this will matter. Just code, setup instructions, and patterns you can apply to a real website today.
The W3C specification was updated on February 12, 2026. Chrome 146 ships with a working DevTrial behind a feature flag. The MCP-B polyfill provides navigator.modelContext for browsers that do not support it natively yet. There are already framework integrations for React, vanilla JavaScript, and Ruby on Rails. The tooling is real, the documentation is solid, and there are working reference implementations you can study.
Here is everything you need to make your website agent-ready.
Setting Up Your Environment
Before writing any WebMCP code, you need a browser that supports it. Right now, that means Chrome 146 with a feature flag enabled.
Open Chrome 146 (Canary or later). Navigate to chrome://flags. Search for “Experimental Web Platform features” or “WebMCP for testing.” Enable the flag and relaunch Chrome.
That is it. Your browser now understands the navigator.modelContext API.
For testing and debugging, install the Model Context Tool Inspector extension from the Chrome Web Store. This extension lets you view all registered tools on any page, test tool invocations manually, validate schemas, and monitor agent calls in real time. It is the DevTools equivalent for WebMCP, and you will want it open constantly while developing.
If you want your tools to work across browsers that do not yet have native WebMCP support, use the MCP-B polyfill. One line, no dependencies:
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>Code language: HTML, XML (xml)
The polyfill automatically detects whether native browser support exists and only activates when needed. When Chrome ships native support, the polyfill steps aside. Your code does not change.
The Declarative API: Agent-Ready Forms in Two Attributes
The fastest way to make your site agent-ready is the declarative API. If you have an HTML form, you can make it a WebMCP tool by adding two attributes. That is not a simplification. It is literally two attributes.
Here is a standard search form:
<form action="/search" method="GET">
<input name="query" type="text" required />
<select name="category">
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<input name="max_price" type="number" />
<button type="submit">Search</button>
</form>Code language: HTML, XML (xml)
Here is the same form, now agent-ready:
<form action="/search" method="GET"
toolname="search_products"
tooldescription="Search the product catalog by keyword, category, and price">
<input name="query" type="text" required
toolparamdescription="Search keywords for product name or description" />
<select name="category"
toolparamdescription="Product category to filter results">
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<input name="max_price" type="number"
toolparamdescription="Maximum price in USD" />
<button type="submit">Search</button>
</form>Code language: HTML, XML (xml)
The browser reads toolname and tooldescription and automatically generates a JSON schema from the form’s input fields. Their names, types, validation rules, and required attributes all become part of the tool’s input definition. The agent sees a structured tool called search_products, understands what it does from the description, and knows exactly what parameters to provide.
When the agent invokes the tool, the browser brings the form into focus, populates the fields visually so the user can see what is happening, and waits for the user to click submit. The human stays in the loop by default.
If you want to allow automatic submission without user confirmation, add toolautosubmit="true". Think carefully before doing this. Any action that modifies data, initiates a transaction, or has side effects should require explicit user confirmation.
This is the kind of progressive enhancement the web was built for. Your form works exactly the same for human visitors. It just also works for AI agents now.
WebMCP in Ruby on Rails
If you are building with Rails, there is already a gem that integrates WebMCP directly into Rails form helpers. The webmcp-rails gem by Jesse Waites adds a webmcp: option to form_with, form_for, and all standard input helpers. No JavaScript. No separate configuration. Just idiomatic Rails.
Install the gem:
gem "webmcp-rails"Code language: JavaScript (javascript)
Then use it in your views exactly the way you would expect:
<%= form_with model: @product, webmcp: {
tool: "create_product",
description: "Create a new product listing",
autosubmit: false
} do |f| %>
<%= f.text_field :name,
webmcp: { param_description: "Product name" } %>
<%= f.number_field :price,
webmcp: { param_description: "Price in USD" } %>
<%= f.text_area :description,
webmcp: { param_description: "Detailed product description" } %>
<%= f.select :category,
["Electronics", "Clothing", "Home"],
webmcp: { param_description: "Product category" } %>
<%= f.date_field :available_from,
webmcp: { param_description: "Date when product becomes available",
param_title: "Availability Date" } %>
<%= f.submit "Create Product" %>
<% end %>Code language: JavaScript (javascript)
This renders standard HTML with the proper WebMCP attributes:
<form toolname="create_product"
tooldescription="Create a new product listing"
action="/products" method="post">
<input type="text" name="product[name]"
toolparamdescription="Product name" />
<input type="number" name="product[price]"
toolparamdescription="Price in USD" />
<textarea name="product[description]"
toolparamdescription="Detailed product description"></textarea>
<select name="product[category]"
toolparamdescription="Product category">
<option value="Electronics">Electronics</option>
<option value="Clothing">Clothing</option>
<option value="Home">Home</option>
</select>
<input type="date" name="product[available_from]"
toolparamdescription="Date when product becomes available"
toolparamtitle="Availability Date" />
<input type="submit" value="Create Product" />
</form>Code language: HTML, XML (xml)
The gem supports all standard Rails form field helpers: text_field, email_field, password_field, number_field, date_field, datetime_field, time_field, url_field, telephone_field, search_field, text_area, color_field, range_field, hidden_field, and select. It requires Ruby 3.1+ and Rails 7.0+.
What I particularly appreciate about this approach is that it stays completely within Rails conventions. You are not learning a new framework or adding a JavaScript layer. You are adding a hash option to helpers you already use every day. If you later decide WebMCP is not for you, removing the webmcp: options leaves your forms exactly as they were. Zero risk, zero lock-in.
For a typical Rails application, the implementation strategy is straightforward. Start with your most trafficked forms: search, contact, signup, checkout. Add the webmcp: options. Deploy. Monitor. Then expand to more complex interactions using the imperative JavaScript API for actions that go beyond simple form submissions.
Here is a more complete Rails example showing a todo application with search and creation forms:
<%# Search form - read-only, safe for autosubmit %>
<%= form_with url: todos_path, method: :get, webmcp: {
tool: "search_todos",
description: "Search todo items by keyword and status",
autosubmit: true
} do |f| %>
<%= f.search_field :q,
webmcp: { param_description: "Search keywords" } %>
<%= f.select :status, ["all", "pending", "completed"],
webmcp: { param_description: "Filter by status" } %>
<%= f.submit "Search" %>
<% end %>
<%# Create form - requires user confirmation %>
<%= form_with model: @todo, webmcp: {
tool: "add_todo",
description: "Add a new todo item"
} do |f| %>
<%= f.text_field :title,
webmcp: { param_description: "Title of the todo" } %>
<%= f.date_field :due_date,
webmcp: { param_description: "Due date",
param_title: "Due Date" } %>
<%= f.select :priority, ["low", "medium", "high"],
webmcp: { param_description: "Task priority level" } %>
<%= f.submit "Add Todo" %>
<% end %>Code language: PHP (php)
Notice the distinction: the search form has autosubmit: true because it is a read-only operation. The create form omits it, requiring the user to confirm before the agent can submit. This pattern of read versus write permissions maps directly to how you should think about every WebMCP tool you expose.
The Imperative API: Full JavaScript Control
The declarative API covers straightforward form submissions. For anything more complex, stateful interactions, multi-step workflows, or dynamic logic, you need the imperative API. This gives you full programmatic control through navigator.modelContext.registerTool().
Here is the API shape from the February 12, 2026 W3C draft:
if ('modelContext' in navigator) {
navigator.modelContext.registerTool({
name: 'search_inventory',
description: 'Search product inventory with filters',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search keywords'
},
category: {
type: 'string',
description: 'Product category filter'
},
maxPrice: {
type: 'number',
description: 'Maximum price in USD'
},
inStock: {
type: 'boolean',
description: 'Only show items currently in stock'
}
},
required: ['query']
},
annotations: {
readOnlyHint: true
},
async execute(input, client) {
const results = await fetch('/api/products?' +
new URLSearchParams(input));
const data = await results.json();
return {
content: [{
type: 'text',
text: JSON.stringify(data)
}]
};
}
});
}Code language: JavaScript (javascript)
The execute function receives two arguments: input (the parameters the agent provided) and client (a ModelContextClient object). The client object gives you access to requestUserInteraction(), which lets you pause execution and ask for human confirmation before sensitive actions:
navigator.modelContext.registerTool({
name: 'place_order',
description: 'Place an order for items in the cart',
inputSchema: {
type: 'object',
properties: {
shippingMethod: {
type: 'string',
description: 'Shipping speed: standard, express, or overnight'
}
},
required: ['shippingMethod']
},
async execute(input, client) {
const cart = await cartService.getCart();
const total = cart.calculateTotal(input.shippingMethod);
const confirmed = await client.requestUserInteraction(async () => {
return showConfirmationDialog({
items: cart.items,
total: total,
shipping: input.shippingMethod
});
});
if (!confirmed) {
return {
content: [{ type: 'text',
text: JSON.stringify({ status: 'cancelled' }) }]
};
}
const order = await orderService.place(cart, input);
return {
content: [{ type: 'text',
text: JSON.stringify({ status: 'placed', orderId: order.id }) }]
};
}
});Code language: JavaScript (javascript)
For managing multiple tools at once, use provideContext() to register a batch and clearContext() to reset. You can also remove individual tools with unregisterTool('tool_name') when context changes, for example when a user navigates to a different section of your application.
WebMCP in React
For React applications, the @mcp-b/react-webmcp package provides a useWebMCP hook that handles tool registration and cleanup automatically through React’s lifecycle:
npm install @mcp-b/react-webmcp @mcp-b/global zodCode language: CSS (css)
'use client';
import { useWebMCP } from '@mcp-b/react-webmcp';
import { z } from 'zod';
function ProductSearch() {
const [results, setResults] = useState([]);
useWebMCP({
name: 'search_products',
description: 'Search the product catalog',
inputSchema: {
query: z.string().describe('Search keywords'),
category: z.string().optional().describe('Category filter'),
maxPrice: z.number().optional().describe('Maximum price')
},
handler: async ({ query, category, maxPrice }) => {
const data = await productAPI.search({ query, category, maxPrice });
setResults(data.products);
return {
success: true,
count: data.products.length,
products: data.products.map(p => ({
id: p.id, name: p.name, price: p.price
}))
};
}
});
return (
<div>
{results.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}Code language: JavaScript (javascript)
The hook automatically registers the tool when the component mounts and unregisters it when the component unmounts. No manual cleanup. The tool exists exactly as long as the component that provides it.
For Next.js with the App Router, remember that WebMCP hooks require 'use client' because they depend on browser APIs. Import the @mcp-b/global polyfill in a client component before any tools are registered, but do not make your root layout a client component.
Writing Good Tool Descriptions
This is the part most developers will underestimate, and it is arguably the most important part of the entire implementation.
Your tool descriptions are how agents decide whether to use your service. They function like meta descriptions in SEO, except the stakes are higher because the agent does not just rank you, it either invokes you or ignores you entirely.
Compare these two descriptions for the same tool:
Weak: "Search stuff"
Strong: "Search the product catalog by keyword. Returns matching products with name, price, availability, and thumbnail URL. Supports filtering by category and price range."
The strong description tells the agent exactly what the tool does, what parameters are useful, and what to expect in the response. For parameter descriptions, be specific about formats: "Start date in YYYY-MM-DD format" is far more useful than "The date".
The specification recommends keeping your tool count below 50 per page. Focus on your highest-value interactions rather than exposing everything your application can do.
Testing and Debugging
The Model Context Tool Inspector extension is your primary debugging tool. Install it, navigate to your WebMCP-enabled page, and you will see every registered tool, its schema, and description. You can invoke tools manually, pass test parameters, and inspect return values.
Google has also published a WebMCP Evals CLI for automated testing and a live reference demo at travel-demo.bandarra.me that demonstrates both APIs. Study the demo. It covers flight search, hotel filtering, and itinerary building and serves as an excellent reference implementation.
What to Implement First
Start with read-only tools. Product search, store locators, pricing lookups. They do not modify state, so security concerns are minimal. Set readOnlyHint: true in annotations.
Next, add WebMCP attributes to your most common forms. Contact, search, signup, support tickets. You are adding two HTML attributes or one Rails hash option.
Then build imperative tools for complex workflows. Multi-step checkout, booking flows, configuration wizards. Use requestUserInteraction() for anything that modifies data or spends money.
Finally, monitor everything. Track which agents call your tools, success rates, error patterns, and execution times.
What to Watch Out For
WebMCP is still a DevTrial. The API surface will change. Build prototypes. Do not ship mission-critical functionality on an unstable API.
The security model is incomplete. The MCP ecosystem has already seen real-world prompt injection attacks, tool poisoning, and data exfiltration. Never expose administrative actions without requestUserInteraction(). Validate all inputs as untrusted data. Treat agent-provided parameters with the same skepticism you would apply to any public API endpoint.
Tool discovery remains an unsolved problem. Agents can only find your tools after navigating to your page. There is no manifest-based discovery yet. This will be addressed in future versions of the specification.
The Ecosystem Is Moving Fast
Within two weeks of the February 10 announcement, there are framework integrations for Rails, React, and vanilla JavaScript. A polyfill with full API compatibility. A Chrome debugging extension. A CLI testing tool. Reference demos. An active community building examples from todo apps to travel insurance platforms.
The webmcp-rails gem, the @mcp-b/react-webmcp package, and the @mcp-b/global polyfill all work today. The W3C working group includes engineers from Microsoft and Google. The awesome-webmcp repository on GitHub catalogs a growing list of resources.
The developers who build familiarity with navigator.modelContext now will be ready when Chrome 146 hits stable around March 2026. Everyone else will be reading tutorials later, wondering why their competitors’ sites show up in agent workflows and theirs do not.
Final Thoughts
If this kind of hands-on engineering content is useful to you, consider following me for more. I write about AI, software architecture, and the real-world engineering decisions that do not make it into marketing slides, drawing from over two decades of building systems in fintech, blockchain, and high-traffic environments.
You can find me at ivanturkovic.com, on LinkedIn, and on Threads. If you are implementing WebMCP and want to compare notes, or if you have found something I missed, I would genuinely like to hear from you.
What tools are you planning to expose first? Have you already started experimenting with the DevTrial? Let me know in the comments or reach out directly. The best engineering insights come from practitioners comparing real implementation experiences.