Add WebMCP tools to a WordPress site without JavaScript
Kashish Hora
Co-founder of MCPcat
The Quick Answer
Add toolname and tooldescription attributes to any HTML form on your WordPress site. The browser registers it as an MCP tool that AI agents can discover and call — no JavaScript, no plugin, no build step:
<form toolname="search_site"
tooldescription="Search this WordPress site by keyword"
toolautosubmit
action="/"
method="get">
<input type="search" name="s" required
toolparamdescription="Search keywords" />
<button type="submit">Search</button>
</form>Paste this into a Custom HTML block in the WordPress block editor, or add the attributes to your theme's searchform.php. The browser reads the attributes, builds a JSON Schema from the form inputs, and registers the tool via navigator.modelContext internally. WordPress's existing form handling processes the submission — the agent just fills and submits it like a user would.
Prerequisites
- A WordPress site served over HTTPS (required for WebMCP's secure context)
- Admin access to edit pages, templates, or theme files
- A Chromium-based browser with
chrome://flags/#enable-webmcp-testingenabled - Familiarity with WebMCP concepts (see registering your first WebMCP tool)
How Declarative WebMCP Works
WebMCP's declarative API turns standard HTML forms into MCP tools using five attributes. When the browser encounters a <form> with a toolname attribute, it scans the form's inputs, builds a JSON Schema from their types and constraints, and registers the result as an MCP tool. No JavaScript runs — the browser handles everything.
The three form-level attributes control the tool definition:
toolname— The tool identifier agents use. Also the opt-in signal: notoolnamemeans the form stays a regular form.tooldescription— Natural language description agents read to decide when to call the tool. Bothtoolnameandtooldescriptionare required.toolautosubmit— When present, the agent can submit the form automatically. When absent, the browser populates the fields visually and waits for the user to click submit.
Two input-level attributes refine how individual fields appear in the schema:
toolparamdescription— Description for this parameter. Falls back to the<label>text if omitted.toolparamtitle— Overrides thenameattribute as the property key in the schema.
Standard HTML semantics do the rest. required marks mandatory fields, <select> options become enums, type="email" adds format hints, and min/max constraints map to their JSON Schema equivalents. For a complete reference of all attributes and input type mappings, see building declarative WebMCP tools with HTML form attributes.
This matters for WordPress specifically because most WordPress sites already have forms — search, comments, contact, WooCommerce checkout — and the declarative approach lets you expose them to agents by adding a few HTML attributes. No PHP changes, no JavaScript, no plugin dependencies.
Adding Tools via the Block Editor
The fastest way to add a WebMCP tool to any WordPress page is with a Custom HTML block in the Gutenberg editor. This approach works on any WordPress 5.0+ site, requires no theme modifications, and gives you full control over the HTML output.
Step 1: Add a Custom HTML Block
Open the page or post in the block editor. Click the + inserter and search for "Custom HTML". Add the block where you want the form to appear.
Step 2: Paste Your Form
Write your form with WebMCP attributes directly in the block. Here's a location-based search form for a restaurant site:
<form toolname="find_restaurant"
tooldescription="Find restaurants by cuisine type and location"
toolautosubmit
action="/restaurants/"
method="get">
<label for="cuisine">Cuisine</label>
<select id="cuisine" name="cuisine" required
toolparamdescription="Type of cuisine to search for">
<option value="italian">Italian</option>
<option value="japanese">Japanese</option>
<option value="mexican">Mexican</option>
<option value="indian">Indian</option>
<option value="thai">Thai</option>
</select>
<label for="location">Location</label>
<input type="text" id="location" name="location" required
toolparamdescription="City or neighborhood name" />
<button type="submit">Find Restaurants</button>
</form>The browser generates a JSON Schema with cuisine as a string enum (constrained to the five options) and location as a required string. An agent seeing this tool knows exactly what values are valid without any additional prompting.
Step 3: Add Styling (Optional)
Add a second Custom HTML block or use the Customizer's Additional CSS to style the form. WordPress's block editor previews the form visually, so you can adjust layout and spacing in the editor.
Since this is a standard HTML form, it also works for human visitors who interact with it normally. The WebMCP attributes are invisible to users and ignored by browsers that don't support the API — progressive enhancement with zero downside.
Adding Tools to the WordPress Search Form
Every WordPress theme includes a search form. Rather than creating a new form, you can add WebMCP attributes to the existing one. There are two approaches depending on how much control you need.
Option 1: Child Theme Template Override
Create a searchform.php file in your child theme. WordPress uses this template instead of the default search form wherever get_search_form() is called:
<!-- searchform.php in your child theme -->
<form role="search"
method="get"
action="<?php echo esc_url(home_url('/')); ?>"
toolname="search_site"
tooldescription="Search all posts and pages on this site by keyword"
toolautosubmit>
<label for="site-search" class="screen-reader-text">Search for:</label>
<input type="search"
id="site-search"
name="s"
required
placeholder="Search…"
value="<?php echo get_search_query(); ?>"
toolparamdescription="Search keywords or phrase" />
<button type="submit">Search</button>
</form>This replaces the search form site-wide. The action points to the home URL with method="get", which is how WordPress handles search queries — the s parameter triggers WordPress's built-in search. The toolautosubmit attribute is appropriate here because search is a read-only operation.
Option 2: Filter in functions.php
If you don't want to create a template file, use the get_search_form filter in your child theme's functions.php to replace the form HTML:
add_filter('get_search_form', function ($form) {
$home_url = esc_url(home_url('/'));
$query = get_search_query();
return <<<HTML
<form role="search" method="get" action="{$home_url}"
toolname="search_site"
tooldescription="Search all posts and pages on this site by keyword"
toolautosubmit>
<input type="search" name="s" required
placeholder="Search…"
value="{$query}"
toolparamdescription="Search keywords or phrase" />
<button type="submit">Search</button>
</form>
HTML;
});Both approaches produce the same result. The template override is easier to maintain if you're already working with a child theme. The filter approach keeps everything in functions.php and avoids creating an extra file.
Adding Tools to the Comment Form
WordPress's comment form is built with comment_form(), which generates the <form> tag and all fields internally. Adding WebMCP attributes to the form requires two pieces: toolparamdescription on each field (easy via filters) and toolname/tooldescription on the <form> element itself (requires a template override).
Adding Descriptions to Comment Fields
WordPress applies a comment_form_field_{$name} filter to each field, which lets you inject toolparamdescription attributes. Add this to your child theme's functions.php:
add_filter('comment_form_field_comment', function ($field) {
return str_replace(
'<textarea',
'<textarea toolparamdescription="The comment text"',
$field
);
});
add_filter('comment_form_field_author', function ($field) {
return str_replace(
'<input',
'<input toolparamdescription="Commenter full name"',
$field
);
});
add_filter('comment_form_field_email', function ($field) {
return str_replace(
'<input',
'<input toolparamdescription="Commenter email address"',
$field
);
});Each filter targets a specific comment field and injects the toolparamdescription attribute. The email field's type="email" (which WordPress sets by default) adds format: "email" to the schema automatically, and the required attribute carries through as well.
Adding toolname to the Comment Form Tag
WordPress's comment_form() outputs the <form> tag directly with no filter for custom attributes. The cleanest approach is to create a custom comments.php template in your child theme that replaces the default form with one that includes the WebMCP attributes:
<!-- comments.php in your child theme -->
<?php if (post_password_required()) return; ?>
<div id="comments" class="comments-area">
<?php if (have_comments()) : ?>
<h2 class="comments-title">Comments</h2>
<ol class="comment-list">
<?php wp_list_comments(); ?>
</ol>
<?php endif; ?>
<?php if (comments_open()) : ?>
<form action="<?php echo esc_url(site_url('/wp-comments-post.php')); ?>"
method="post" id="commentform" class="comment-form"
toolname="post_comment"
tooldescription="Post a comment on this page with author name, email, and message">
<?php wp_comment_form_unfiltered_html_nonce(); ?>
<input type="hidden" name="comment_post_ID" value="<?php echo get_the_ID(); ?>" />
<input type="hidden" name="comment_parent" value="0" />
<p><label for="author">Name</label>
<input type="text" name="author" id="author" required
toolparamdescription="Commenter full name" /></p>
<p><label for="email">Email</label>
<input type="email" name="email" id="email" required
toolparamdescription="Commenter email address" /></p>
<p><label for="comment">Comment</label>
<textarea name="comment" id="comment" rows="8" required
toolparamdescription="The comment text"></textarea></p>
<button type="submit">Post Comment</button>
</form>
<?php endif; ?>
</div>The comment form intentionally omits toolautosubmit. Posting a comment has side effects — it creates content, sends notification emails, and is publicly visible. Without toolautosubmit, the agent fills in the name, email, and comment fields, then the user reviews the populated form and clicks "Post Comment" manually. The hidden fields (comment_post_ID, comment_parent) are required for WordPress to process the comment correctly.
Working with Form Plugins
Form plugins like Contact Form 7, Gravity Forms, and WPForms generate their own <form> elements internally, which makes adding toolname and tooldescription attributes difficult. Each plugin has a different level of extensibility for custom attributes.
The most reliable approach across all form plugins is to replace the plugin's form with a Custom HTML block that submits to the same backend endpoint. This gives you full control over the markup while keeping the existing form processing logic. For a contact form, this means creating a plain HTML form that POSTs to your plugin's submission handler or to a custom REST API endpoint.
If you prefer to keep the form plugin for its admin UI and email routing, you can add toolparamdescription to individual inputs via the plugin's custom attributes feature (most popular form builders support this), and add the form-level toolname and tooldescription via a PHP output buffer filter:
add_action('wp_loaded', function () {
ob_start(function ($html) {
// Add WebMCP attributes to CF7 forms
$html = preg_replace(
'/<form([^>]*class="[^"]*wpcf7-form[^"]*"[^>]*)>/',
'<form$1 toolname="contact_us" tooldescription="Send a message with name, email, and message body">',
$html
);
return $html;
});
});This output buffer approach intercepts the final HTML before it's sent to the browser and adds the WebMCP attributes to any form with the wpcf7-form class. It works without JavaScript, but it's fragile — changes to the plugin's HTML output could break the regex. Adapt the class name for other plugins: gform_wrapper for Gravity Forms, wpforms-form for WPForms.
For production use, the Custom HTML block approach is more maintainable. Here's a contact form that handles the same use case as a typical CF7 setup:
<form toolname="contact_us"
tooldescription="Send a message with your name, email, subject, and message"
action="/contact-handler/"
method="post">
<input type="text" name="name" required
toolparamdescription="Your full name" />
<input type="email" name="email" required
toolparamdescription="Your email address" />
<select name="subject" required
toolparamdescription="Message subject">
<option value="general">General Inquiry</option>
<option value="support">Support</option>
<option value="billing">Billing</option>
</select>
<textarea name="message" required minlength="10"
toolparamdescription="Your message (minimum 10 characters)"></textarea>
<button type="submit">Send Message</button>
</form>No toolautosubmit — sending a message is a side-effect action. The agent fills the fields, and the user reviews the content before clicking Send. The <select> constrains the subject to valid options, and minlength="10" maps to minLength: 10 in the schema, prompting the agent to write a sufficiently detailed message.
Styling Agent Interactions
WebMCP adds two CSS pseudo-classes that let you style forms while an agent is interacting with them. Add these styles via Appearance > Customize > Additional CSS in the WordPress admin, or in your child theme's style.css:
/* Highlight the form while the agent is filling fields */
form:tool-form-active {
outline: 2px solid #6366f1;
outline-offset: 4px;
transition: outline 0.2s ease;
}
/* Animate the submit button while the agent is about to submit */
button[type="submit"]:tool-submit-active,
input[type="submit"]:tool-submit-active {
background: linear-gradient(110deg, #6366f1 30%, #a5b4fc 50%, #6366f1 70%);
background-size: 200% 100%;
animation: shimmer 2s infinite linear;
color: white;
}
@keyframes shimmer {
from { background-position: 200% 0; }
to { background-position: -200% 0; }
}:tool-form-active applies to the <form> element while the agent is populating fields, giving the user a visual cue that an AI agent is interacting with the page. :tool-submit-active applies to the submit button when the agent is about to submit, which is especially useful for forms without toolautosubmit where the user needs to know the form is ready for review.
WordPress themes style submit buttons with both <button type="submit"> and <input type="submit">, so target both selectors. The styles degrade gracefully — browsers that don't support WebMCP simply ignore the unknown pseudo-classes.
Common Issues
WordPress stripping custom attributes
WordPress's wp_kses_post() sanitizer strips non-standard HTML attributes when saving post content. If your WebMCP attributes disappear after saving, the content is being run through KSES filtering. Custom HTML blocks bypass this filtering for admin users, but some plugins or security configurations may still strip attributes.
If attributes are being removed, add them to the KSES allowed list in your theme's functions.php:
add_filter('wp_kses_allowed_html', function ($allowed, $context) {
if ($context === 'post') {
$allowed['form']['toolname'] = true;
$allowed['form']['tooldescription'] = true;
$allowed['form']['toolautosubmit'] = true;
$allowed['input']['toolparamdescription'] = true;
$allowed['input']['toolparamtitle'] = true;
$allowed['select']['toolparamdescription'] = true;
$allowed['select']['toolparamtitle'] = true;
$allowed['textarea']['toolparamdescription'] = true;
$allowed['textarea']['toolparamtitle'] = true;
}
return $allowed;
}, 10, 2);Site not on HTTPS
WebMCP requires a secure context. If your WordPress site uses plain HTTP, navigator.modelContext won't be available. Most WordPress hosts provide free SSL certificates — enable HTTPS and update your Site URL in Settings > General. For local development, localhost is treated as a secure context by default.
Form plugin generates the <form> element internally
Plugins like Contact Form 7, Gravity Forms, and WPForms generate their own <form> tags, making it difficult to add toolname and tooldescription attributes. The most reliable workaround is to create a plain HTML form in a Custom HTML block that submits to the same endpoint (your email handler, REST API, or admin-ajax.php). This gives you full control over the markup while keeping the same backend processing.
Search form not using the template override
Some themes hard-code their search forms instead of using get_search_form(). If your searchform.php override has no effect, search your theme's template files for <form elements with action pointing to the home URL and name="s". You may need to edit the theme's template directly (in a child theme) rather than relying on the template override.
navigator.modelContext is undefined
Navigate to chrome://flags/#enable-webmcp-testing and set it to "Enabled", then restart the browser. The API is currently available in Chrome 146+ behind this flag. Use if ("modelContext" in navigator) in any JavaScript to avoid errors on unsupported browsers. The declarative HTML approach handles this gracefully — the attributes are simply ignored by browsers without WebMCP support.
Examples
WooCommerce Product Search with Filters
A product search form that exposes WooCommerce's search and taxonomy filtering to AI agents. This uses WooCommerce's native query parameters, so the search results page works exactly as it would for a human visitor.
<form toolname="search_products"
tooldescription="Search the product catalog by keyword, category, and price range"
toolautosubmit
action="<?php echo esc_url(home_url('/')); ?>"
method="get">
<input type="hidden" name="post_type" value="product" />
<input type="search" name="s" required
toolparamdescription="Product search keywords" />
<select name="product_cat"
toolparamdescription="Product category to filter by">
<option value="">All Categories</option>
<option value="clothing">Clothing</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<input type="number" name="min_price" min="0"
toolparamdescription="Minimum price in USD" />
<input type="number" name="max_price" min="0"
toolparamdescription="Maximum price in USD" />
<button type="submit">Search Products</button>
</form>The post_type=product hidden input ensures WordPress routes the search to WooCommerce's product archive. The <select> becomes an enum in the schema, constraining the agent to valid categories. Price inputs use type="number" with min="0", which maps to { type: "number", minimum: 0 } in the JSON Schema. The toolautosubmit attribute is appropriate because product search is read-only.
Place this in a Custom HTML block on your shop page, or in your child theme's searchform.php with a conditional check for WooCommerce pages. For dynamic category lists, use a child theme template with get_terms('product_cat') to generate the <option> elements from your actual WooCommerce categories.
Event Booking Form with Human Review
A booking form for an events site where the agent fills in the details but the user must review and confirm before submission. This pattern works well for any form that creates a record or triggers a transaction.
<form toolname="book_event"
tooldescription="Book tickets for an upcoming event with attendee name, email, event selection, and ticket count"
action="/book/"
method="post">
<label for="attendee-name">Full Name</label>
<input type="text" id="attendee-name" name="attendee_name" required
toolparamdescription="Attendee's full name" />
<label for="attendee-email">Email</label>
<input type="email" id="attendee-email" name="attendee_email" required
toolparamdescription="Attendee's email for confirmation" />
<label for="event">Event</label>
<select id="event" name="event_id" required
toolparamdescription="The event to attend">
<option value="spring-gala">Spring Gala — March 28</option>
<option value="tech-meetup">Tech Meetup — April 5</option>
<option value="wine-tasting">Wine Tasting — April 12</option>
</select>
<label for="tickets">Number of Tickets</label>
<input type="number" id="tickets" name="ticket_count"
min="1" max="10" value="1" required
toolparamdescription="Number of tickets (1-10)" />
<button type="submit">Book Now</button>
</form>No toolautosubmit here — booking creates a commitment and potentially charges money. The agent fills all four fields based on the user's request, then the form is brought into focus with the populated values. The user sees the selected event, ticket count, and their details, and clicks "Book Now" only after verifying everything is correct. The type="email" on the email field adds format: "email" to the schema, and min="1" max="10" on tickets maps to minimum: 1, maximum: 10, guiding the agent to provide valid values.
Next Steps
This guide covered adding WebMCP tools to WordPress using declarative HTML attributes. For a complete reference of all form-level and input-level attributes, including the JSON Schema mapping table, see building declarative WebMCP tools with HTML form attributes. To understand the JavaScript API underneath the declarative approach, see registering your first WebMCP tool. To connect your WordPress tools to desktop AI clients like Claude Code or Cursor, see connecting WebMCP tools via the bridge extension.
Related Guides
Build declarative WebMCP tools with HTML form attributes
Turn existing HTML forms into MCP tools using declarative attributes — no JavaScript required.
Register your first WebMCP tool with navigator.modelContext
How to use navigator.modelContext to register tools that AI agents can call directly from your website.
Add WebMCP tools to a React app with webmcp-react
How to use the webmcp-react library to register WebMCP tools in React with Zod schemas and connect them to desktop clients.