WebMCP for WordPress: plugins, custom tools and pitfalls

WordPress sites can register WebMCP tools today using a plugin or around 50 lines of custom PHP and JavaScript. As of September 24, 2026, the main consumer product calling these tools is the built-in browser in ChatGPT's desktop app, with GPT-5.6 Sol and Terra models. For clinics, service businesses, and WooCommerce stores, an agent can then search services, request an appointment, or browse products without parsing HTML.
What agents see and who they are
WebMCP (W3C Web Machine Learning Community Group draft, September 17, 2026) lets a page register tools with document.modelContext.registerTool(). The agent calls those tools as structured functions without reading the DOM.
ChatGPT's "Site tools" (launched August 25, 2026) is the main consumer product reading these tools today. It runs in the ChatGPT desktop app's built-in browser (not in Chrome), reads only tools registered with JavaScript on the top-level page (not iframes or declarative HTML form tools), and is limited to GPT-5.6 Sol and Terra models. Enterprise and Edu ChatGPT plans do not support Site tools. Safari and Firefox have not shipped support.
Chrome 157 (targeted around November 3, 2026) is the stated goal for on-by-default support; this date is not committed. The standard is in a Chrome origin trial covering versions 149–156.
Plugins available on WordPress.org
A search for "webmcp" on WordPress.org returned 49 plugins as of September 24, 2026. Several are for server-side MCP (a different protocol that connects AI clients over HTTP rather than registering in-browser tools). The plugins below specifically describe browser-native WebMCP tool registration.
| Plugin | Active installs | Last updated | What it exposes |
|---|---|---|---|
| WebMCP Bridge (by Mescio) | 600+ | Sep 17, 2026 | Core: search_posts, get_post, get_menu, get_categories, get_site_info, submit_contact_form. WooCommerce: product search, cart management, coupons, checkout fields. With Mescio for Agents also active: retrieve, get_markdown_content, get_llms_txt |
| IBS WebMCP Gateway (by ibsofts) | 10+ | Sep 2026 | Content, products, navigation (per listing) |
| Agentgate for WebMCP | 10+ | Sep 2026 | Read-only posts and categories (per listing) |
A note on the browser registration API. The plugin's changelog shows version 1.6.0 migrated to navigator.modelContext.provideContext(). navigator.modelContext is the deprecated entry point and is being removed in Chrome 150–152, and provideContext was removed from the spec in March 2026. The current API is document.modelContext.registerTool(). The plugin also exposes a REST fallback (/wp-json/webmcp-bridge/v1/call/{tool}) that works in any browser. Check the plugin changelog for an update to the browser registration path before relying on it for native tool registration.
Read-only tools are publicly accessible by default. Write-action tools require a WordPress REST nonce from the same browsing session. Rate limiting on the execute endpoint is configurable in Settings.
We found no announcement of built-in WebMCP support in WordPress core as of September 24, 2026.
Registering custom tools
If no plugin fits your use case, register your own tools in a small plugin or your theme's functions.php. The two examples below are a read-only service search and a consequential appointment-request tool.
Enqueue the script early
Tools must register within roughly the first three seconds of load. Scripts deferred or moved to the footer by a caching plugin arrive too late. Use in_footer => false and exclude the handle from your caching plugin's defer or combine rules.
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script(
'my-webmcp-tools',
get_template_directory_uri() . '/assets/webmcp-tools.js',
[], '1.0.0',
[ 'in_footer' => false ] // <head>, not footer
);
wp_localize_script( 'my-webmcp-tools', 'myWebMCP', [
'nonce' => wp_create_nonce( 'wp_rest' ),
'restBase' => rest_url(),
] );
} );
Register tools in JavaScript
Check for document.modelContext before calling it: the API is not present in Firefox or Safari.
if ( document.modelContext ) {
const ac = new AbortController();
document.modelContext.registerTool(
{
name: 'search_services',
description: 'Search available services or pages on this site. Returns titles and URLs.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search term' }
},
required: [ 'query' ]
},
annotations: { readOnlyHint: true },
execute: async ( { query }, { signal } ) => {
const url = myWebMCP.restBase
+ 'wp/v2/pages?search=' + encodeURIComponent( query )
+ '&per_page=5&_fields=id,title,link';
const res = await fetch( url, { signal } );
if ( ! res.ok ) return { error: 'Search failed.' };
const pages = await res.json();
return pages.map( p => ( { title: p.title.rendered, url: p.link } ) );
}
},
{ signal: ac.signal }
).catch( ( e ) => console.warn( 'search_services not registered', e ) );
document.modelContext.registerTool(
{
name: 'request_appointment',
description: 'Submit an appointment request. Returns a confirmation message.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Full name' },
email: { type: 'string', format: 'email' },
date: { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}$', description: 'Preferred date YYYY-MM-DD' }
},
required: [ 'name', 'email', 'date' ]
},
annotations: { consequentialHint: true },
execute: async ( input, { signal } ) => {
const res = await fetch( myWebMCP.restBase + 'my-site/v1/appointment', {
method: 'POST', signal,
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': myWebMCP.nonce,
},
body: JSON.stringify( input ),
} );
const data = await res.json();
return { success: res.ok, message: data.message ?? '' };
}
},
{ signal: ac.signal }
).catch( ( e ) => console.warn( 'request_appointment not registered', e ) );
}
consequentialHint: true signals real-world side effects; supporting agents are expected to confirm with the user before calling the tool. To unregister, abort the controller: ac.abort(). The older unregisterTool method was removed from the spec in 2026.
Register the REST route in PHP
Validate and sanitize server-side regardless of the JavaScript schema. Input arrives over HTTP and cannot be trusted from the client.
add_action( 'rest_api_init', function () {
register_rest_route( 'my-site/v1', '/appointment', [
'methods' => 'POST',
'permission_callback' => fn( $r ) => wp_verify_nonce(
$r->get_header( 'X-WP-Nonce' ), 'wp_rest'
),
'callback' => function ( $request ) {
$name = sanitize_text_field( $request->get_param( 'name' ) );
$email = sanitize_email( $request->get_param( 'email' ) );
$date = sanitize_text_field( $request->get_param( 'date' ) );
if ( ! is_email( $email ) || ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) {
return new WP_Error( 'invalid_input', 'Invalid input.', [ 'status' => 400 ] );
}
// booking logic: save to DB, send email, etc.
return rest_ensure_response( [ 'message' => 'Appointment request received.' ] );
},
'args' => [
'name' => [ 'required' => true, 'type' => 'string' ],
'email' => [ 'required' => true, 'type' => 'string' ],
'date' => [ 'required' => true, 'type' => 'string' ],
],
] );
} );
Add rate limiting with WP transients if the endpoint is publicly reachable. Attackers can call a REST route directly, outside the WebMCP path.
WooCommerce: Store API
WooCommerce's Store API exposes product search at /wp-json/wc/store/v1/products?search=keyword: publicly readable, no nonce required.
Cart write endpoints are different. All POST requests to /wc/store/v1/cart/* and all /checkout routes require a Nonce header (generated with wp_create_nonce( 'wc_store_api' )) or a Cart-Token header. Print the nonce to the page via wp_localize_script. After each successful cart request, the response returns an updated Nonce header that the tool must store and reuse. Never expose a public endpoint that generates nonces: that lets anyone pre-authenticate cart actions outside the browsing session.
Pitfalls
Registration timing. Ora's 2026 audit of reebok.com found its checkout tool registering at 3.6 seconds, after the roughly 3 seconds agents typically spend reading a page. Optimisation plugins such as WP Rocket, Autoptimize, LiteSpeed Cache, W3 Total Cache and SG Optimizer can push your script past that window if they defer or combine it. Enqueue with in_footer => false and add the handle to your caching plugin's defer-exclusion list.
Cookie and consent banners. A full-page consent overlay blocks agents the same way it blocks human interaction. Agents can't be relied on to dismiss popups. If your consent layer covers the viewport on first load, an agent may see nothing until it is cleared.
Deprecated APIs. Code that calls navigator.modelContext, provideContext() or unregisterTool() targets a removed or deprecated API. Use document.modelContext.registerTool() and guard the call with an existence check.
Security. Never register a WebMCP tool that performs privileged WordPress actions: writing options, modifying users, activating plugins. Treat every tool endpoint as publicly callable. Add server-side validation, sanitization, and rate limiting to any route that writes data.
How to test
- In Chrome, open
chrome://flags/#enable-webmcp-testing, enable the flag, and relaunch. - Install the "Model Context Tool Inspector" extension from the Chrome Web Store.
- Load your page. The Inspector lists registered tools with timestamps, and lets you invoke them manually.
- If a tool appears late in the timeline, move its script to
<head>and check your caching plugin's defer exclusions.
The chrome://flags path works on localhost without an origin trial token.
Sources
- WebMCP Bridge: WordPress plugin | WordPress.org
- Search results for 'webmcp' | WordPress.org
- WebMCP Bridge: WP-Rankings
- WebMCP: W3C Web Machine Learning Community Group draft
- ChatGPT Adds WebMCP Support: Search Engine Journal
- WebMCP audit: reebok.com: Ora
- Nonce Tokens: WooCommerce Store API
- Cart API: WooCommerce Store API
