<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prasanna Vaidya</title>
    <description>The latest articles on DEV Community by Prasanna Vaidya (@prasannagyde).</description>
    <link>https://dev.to/prasannagyde</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3838189%2F79abb00c-3b75-48b4-8b40-73c3601fe1a1.png</url>
      <title>DEV Community: Prasanna Vaidya</title>
      <link>https://dev.to/prasannagyde</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prasannagyde"/>
    <language>en</language>
    <item>
      <title>auto-webmcp v0.3.0: React support, richer schemas, and WebMCP spec compliance</title>
      <dc:creator>Prasanna Vaidya</dc:creator>
      <pubDate>Wed, 25 Mar 2026 07:11:52 +0000</pubDate>
      <link>https://dev.to/prasannagyde/auto-webmcp-v030-react-support-richer-schemas-and-webmcp-spec-compliance-2eo9</link>
      <guid>https://dev.to/prasannagyde/auto-webmcp-v030-react-support-richer-schemas-and-webmcp-spec-compliance-2eo9</guid>
      <description>&lt;p&gt;When we shipped the first version of &lt;a href="https://autowebmcp.dev" rel="noopener noreferrer"&gt;auto-webmcp&lt;/a&gt;, the pitch was simple: drop one script tag and every form on your page becomes a callable tool for AI agents, no manual JSON Schema writing required.&lt;/p&gt;

&lt;p&gt;That core idea has not changed. But the past two days have been a sprint through every edge case the real web throws at you: React forms that fight you, inputs that live outside &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tags, select menus that lie about their options, and a new set of WebMCP spec fields that make your tools dramatically more useful to agents.&lt;/p&gt;

&lt;p&gt;Here is what shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  React and framework-managed forms
&lt;/h2&gt;

&lt;p&gt;Vanilla forms are easy. React forms are a different story.&lt;/p&gt;

&lt;p&gt;React intercepts input events using its own synthetic event system. Setting &lt;code&gt;input.value = 'foo'&lt;/code&gt; directly does nothing because React's state never updates. The DOM value changes, but React still thinks the field is empty. When the form submits, it sends blank data.&lt;/p&gt;

&lt;p&gt;auto-webmcp now fills React inputs by dispatching a native input event through &lt;code&gt;HTMLInputElement.prototype&lt;/code&gt; — bypassing the React wrapper and triggering &lt;code&gt;onChange&lt;/code&gt; as if the user typed the value themselves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What we do internally for React-controlled inputs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nativeSetter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOwnPropertyDescriptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;nativeSetter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bubbles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For frameworks that use &lt;code&gt;execCommand&lt;/code&gt; (older content-editable patterns), we fall back to &lt;code&gt;document.execCommand('insertText', false, value)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is also a subtler React problem: when an agent fills a form and triggers a re-render, React can reset the field values before the submit fires. We now capture a post-fill snapshot immediately after filling, and use that snapshot when constructing the response instead of re-querying the live DOM.&lt;/p&gt;

&lt;p&gt;MutationObserver re-analysis is debounced for forms that add inputs lazily (common in multi-step React flows). If a new field appears inside an already-registered form, auto-webmcp re-analyzes the schema without re-registering a duplicate tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  ARIA inputs and unnamed fields
&lt;/h2&gt;

&lt;p&gt;The web is full of inputs that are not &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; elements. Rich text editors, custom dropdowns, date pickers — most of them use &lt;code&gt;role="textbox"&lt;/code&gt;, &lt;code&gt;role="combobox"&lt;/code&gt;, or &lt;code&gt;role="spinbutton"&lt;/code&gt; to communicate their semantics to screen readers. auto-webmcp now reads the same signals.&lt;/p&gt;

&lt;p&gt;Any element with a recognized ARIA input role is included in the schema alongside native inputs. The field key is inferred from &lt;code&gt;aria-label&lt;/code&gt; or the associated &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;For inputs without a &lt;code&gt;name&lt;/code&gt; attribute (common in React where form state lives in JS, not the DOM), we now key the schema field by &lt;code&gt;id&lt;/code&gt; or &lt;code&gt;aria-label&lt;/code&gt; so agents can still reference and fill them by name.&lt;/p&gt;

&lt;p&gt;One specific fix: Ghost's newsletter signup forms use an &lt;code&gt;&amp;lt;input type="email"&amp;gt;&lt;/code&gt; with no &lt;code&gt;name&lt;/code&gt; or &lt;code&gt;id&lt;/code&gt; — only a &lt;code&gt;placeholder&lt;/code&gt;. Previously, these were silently excluded. Now &lt;code&gt;placeholder&lt;/code&gt; is used as the field key and title, making these forms fully callable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Schema improvements for selects and checkboxes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Optgroup labels&lt;/strong&gt; are now surfaced in the schema. If a select menu groups options under &lt;code&gt;&amp;lt;optgroup label="North America"&amp;gt;&lt;/code&gt;, the label appears as metadata on the option, giving agents clearer context for picking values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Datalist suggestions&lt;/strong&gt; from &lt;code&gt;&amp;lt;input list="..."&amp;gt;&lt;/code&gt; are collected and included in the schema as suggestions, not hard constraints — agents can still provide values outside the list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disabled options&lt;/strong&gt; are excluded. If an option is &lt;code&gt;disabled&lt;/code&gt;, agents should not try to select it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meaningful empty options&lt;/strong&gt; are now handled correctly. The old behavior was simple: skip any &lt;code&gt;&amp;lt;option value=""&amp;gt;&lt;/code&gt;. But that excluded legitimate choices like "No preference" or "All Categories". Now we only skip placeholder text that follows the pattern &lt;code&gt;"Select..."&lt;/code&gt;, &lt;code&gt;"Choose..."&lt;/code&gt;, &lt;code&gt;"Pick..."&lt;/code&gt;, or &lt;code&gt;"---"&lt;/code&gt;. Empty-value options with real labels are included in the schema with &lt;code&gt;const: ""&lt;/code&gt; so agents can express those choices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-select&lt;/strong&gt; fields produce &lt;code&gt;type: array&lt;/code&gt; with &lt;code&gt;items.enum&lt;/code&gt; instead of a single-value string. The fill handler accepts an array of values or a single string (treated as a one-element array), deselects all options first, then selects matching values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checkbox groups&lt;/strong&gt; produce &lt;code&gt;type: array&lt;/code&gt; with &lt;code&gt;items.enum&lt;/code&gt; listing the values of each checkbox in the group. Filling accepts an array of values to check.&lt;/p&gt;




&lt;h2&gt;
  
  
  Orphan inputs (forms without &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tags)
&lt;/h2&gt;

&lt;p&gt;A surprising number of real-world "forms" are not &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; elements. They are collections of inputs inside a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, with a submit button and some JavaScript. Single-page apps love this pattern.&lt;/p&gt;

&lt;p&gt;auto-webmcp now scans for inputs outside &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; elements, groups them by their nearest ancestor that contains a visible submit button, and registers each group as a tool. The same schema inference rules apply: field types, labels, enums, and descriptions are all collected.&lt;/p&gt;

&lt;p&gt;This means subscribe boxes, search widgets, and wizard steps that skip the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag are now automatically callable.&lt;/p&gt;




&lt;h2&gt;
  
  
  WebMCP spec compliance: annotations, field titles, defaults
&lt;/h2&gt;

&lt;p&gt;Version 0.3.0 adds three features that align auto-webmcp with the latest WebMCP spec.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool annotations
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ToolAnnotations&lt;/code&gt; is a set of hints that tell agents about the behavior of a tool before they call it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Annotation&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;readOnlyHint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool only reads data, does not modify anything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;destructiveHint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool deletes or irreversibly modifies data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idempotentHint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calling the tool multiple times has the same effect as calling it once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openWorldHint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool talks to external systems (email, payment, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;auto-webmcp infers these automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET&lt;/code&gt; forms get &lt;code&gt;readOnlyHint: true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Forms with delete/remove/cancel button text get &lt;code&gt;destructiveHint: true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Forms posting to URLs with &lt;code&gt;/search&lt;/code&gt; or &lt;code&gt;/filter&lt;/code&gt; patterns get &lt;code&gt;idempotentHint: true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Forms posting to external domains get &lt;code&gt;openWorldHint: true&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can override any inference with data attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;data-webmcp-destructive=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;data-webmcp-openworld=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- delete account form --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Field titles via &lt;code&gt;toolparamtitle&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The WebMCP spec added &lt;code&gt;toolparamtitle&lt;/code&gt; as a native attribute for setting the display title of a schema field. auto-webmcp now reads this attribute first, before falling back to &lt;code&gt;data-webmcp-title&lt;/code&gt; and label text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"q"&lt;/span&gt;
  &lt;span class="na"&gt;toolparamtitle=&lt;/span&gt;&lt;span class="s"&gt;"Search query"&lt;/span&gt;
  &lt;span class="na"&gt;toolparamdescription=&lt;/span&gt;&lt;span class="s"&gt;"What you want to search for"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Schema defaults from pre-filled values
&lt;/h3&gt;

&lt;p&gt;If a field has a value already set when the page loads — a text input with a default value, a select with a pre-selected option, a checked checkbox — that value is now exposed as &lt;code&gt;default&lt;/code&gt; in the JSON Schema.&lt;/p&gt;

&lt;p&gt;This gives agents a concrete starting point. Instead of guessing that "economy" is probably the default cabin class, the schema tells them directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cabin_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"economy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"first"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"economy"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  By the numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;v0.2.1 shipped with 32 tests&lt;/li&gt;
&lt;li&gt;v0.3.0 ships with 82 tests&lt;/li&gt;
&lt;li&gt;50 new integration tests covering React remounts, multi-select fill, orphan inputs, annotations, and defaults&lt;/li&gt;
&lt;li&gt;All tests run against real Chromium via Playwright&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/auto-webmcp@0.3.0/dist/auto-webmcp.iife.js"&lt;/span&gt;
  &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire integration. Every form on your page becomes a callable WebMCP tool. React forms, orphan inputs, multi-selects, checkbox groups — all handled.&lt;/p&gt;

&lt;p&gt;To test: Chrome 146+, enable &lt;code&gt;chrome://flags/#enable-webmcp-testing&lt;/code&gt;, install the Model Context Tool Inspector extension, and open any page with the script tag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://autowebmcp.dev" rel="noopener noreferrer"&gt;autowebmcp.dev&lt;/a&gt; · &lt;a href="https://github.com/prasanna-gyde/auto-webmcp" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · npm: auto-webmcp · MIT&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webmcp</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What is WebMCP? Chrome's browser-native API for AI agents</title>
      <dc:creator>Prasanna Vaidya</dc:creator>
      <pubDate>Sun, 22 Mar 2026 12:38:31 +0000</pubDate>
      <link>https://dev.to/prasannagyde/what-is-webmcp-chromes-browser-native-api-for-ai-agents-2mmc</link>
      <guid>https://dev.to/prasannagyde/what-is-webmcp-chromes-browser-native-api-for-ai-agents-2mmc</guid>
      <description>&lt;p&gt;AI agents are getting good at using the web. But the way they interact with it today is fragile: CSS selectors, XPath queries, visual parsing, and DOM scraping that breaks every time a designer renames a class.&lt;/p&gt;

&lt;p&gt;Chrome 146 ships an early preview of something that changes this: &lt;strong&gt;WebMCP&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with how agents use the web today
&lt;/h2&gt;

&lt;p&gt;When an AI agent needs to fill out a flight search form, it typically does something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take a screenshot or parse the DOM&lt;/li&gt;
&lt;li&gt;Guess which input is "origin" vs "destination"&lt;/li&gt;
&lt;li&gt;Figure out the date picker format&lt;/li&gt;
&lt;li&gt;Click submit and hope the page structure hasn't changed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This works poorly. It's slow, brittle, and requires constant maintenance as sites update their UI. The agent is essentially learning to use a UI designed for humans, not machines.&lt;/p&gt;

&lt;p&gt;WebMCP flips this model.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is WebMCP?
&lt;/h2&gt;

&lt;p&gt;WebMCP is a proposed web standard that lets websites publish structured tools directly to in-browser AI agents. Instead of the agent scraping the DOM to guess how a form works, the page explicitly tells the agent: here is what I can do, here are the inputs I expect, here is what I'll return.&lt;/p&gt;

&lt;p&gt;Think of it as MCP (Model Context Protocol) built into the browser itself.&lt;/p&gt;

&lt;p&gt;The key difference from server-side MCP: WebMCP tools are defined in the client, inside a web page, and are available only while that page is open in a browser tab. No server to deploy, no infrastructure to maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  How WebMCP works
&lt;/h2&gt;

&lt;p&gt;Chrome exposes a new JavaScript API: &lt;code&gt;navigator.modelContext&lt;/code&gt;. This object is the bridge between your web page and any AI agent running in the browser.&lt;/p&gt;

&lt;p&gt;WebMCP offers two ways to define tools: the &lt;strong&gt;Imperative API&lt;/strong&gt; (JavaScript) and the &lt;strong&gt;Declarative API&lt;/strong&gt; (HTML annotations).&lt;/p&gt;




&lt;h2&gt;
  
  
  The Imperative API
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;registerTool()&lt;/code&gt; to define a tool in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addTodo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Add a new item to the todo list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;addItemToList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Added todo: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;execute&lt;/code&gt; function is called by the agent with structured parameters matching your schema. You fill the UI, run the action, and return a result the agent can read.&lt;/p&gt;

&lt;p&gt;To remove a tool (for example, when the user navigates away from a state where it makes sense):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unregisterTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addTodo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dynamic registration is useful for single-page apps where available actions change based on UI state.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Declarative API
&lt;/h2&gt;

&lt;p&gt;For forms, you don't need JavaScript at all. Add a few HTML attributes and the browser generates the tool definition automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;toolname=&lt;/span&gt;&lt;span class="s"&gt;"search_flights"&lt;/span&gt;
  &lt;span class="na"&gt;tooldescription=&lt;/span&gt;&lt;span class="s"&gt;"Search for available flights between two airports"&lt;/span&gt;
  &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/search"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"origin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Departure airport&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"origin"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"origin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"destination"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Arrival airport&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"destination"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"destination"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cabin_class"&lt;/span&gt; &lt;span class="na"&gt;toolparamdescription=&lt;/span&gt;&lt;span class="s"&gt;"Cabin class preference"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"economy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Economy&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"business"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Business&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"first"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;First&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Search&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser reads the form structure and generates a full JSON Schema automatically, including field types, required fields, and enum options from &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;input type="radio"&amp;gt;&lt;/code&gt; elements.&lt;/p&gt;

&lt;p&gt;The three key attributes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toolname&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool identifier (snake_case, no spaces)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tooldescription&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plain-language description for the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toolautosubmit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If present, form submits automatically when agent invokes it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toolparamdescription&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;any field&lt;/td&gt;
&lt;td&gt;Overrides the label text for the JSON Schema description&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By default, when an agent invokes a declarative tool, Chrome fills in the form fields and waits for the user to click submit. Add &lt;code&gt;toolautosubmit&lt;/code&gt; to the form if you want the agent to submit without user confirmation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Responding to tool invocations
&lt;/h2&gt;

&lt;p&gt;The Declarative API integrates with the standard &lt;code&gt;submit&lt;/code&gt; event, extended with two new properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentInvoked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Agent called this tool, return structured result&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Search complete. 3 flights found.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Regular human submission, handle normally&lt;/span&gt;
    &lt;span class="nf"&gt;doRegularSubmit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;e.agentInvoked&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; when the submission came from an agent. &lt;code&gt;e.respondWith()&lt;/code&gt; lets you pass a promise that resolves to the result the agent receives.&lt;/p&gt;




&lt;h2&gt;
  
  
  Events and CSS pseudo-classes
&lt;/h2&gt;

&lt;p&gt;WebMCP fires two events on &lt;code&gt;window&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toolactivated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Agent has filled the form, waiting for submit&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was activated by an agent`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toolcancel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Agent cancelled or user reset the form&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was cancelled`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chrome also applies CSS pseudo-classes when a tool is active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="nd"&gt;:tool-form-active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt; &lt;span class="nb"&gt;dashed&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* default Chrome style */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:tool-submit-active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt; &lt;span class="nb"&gt;dashed&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can override these to match your design system and give users a clear visual indicator that an agent is operating.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the agent sees
&lt;/h2&gt;

&lt;p&gt;For a form with a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, Chrome generates a schema like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search_flights"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Search for available flights between two airports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Departure airport"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cabin_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"economy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"first"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"oneOf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"const"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"economy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Economy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"const"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Business"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"const"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"first"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"First"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cabin class preference"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;oneOf&lt;/code&gt; alongside &lt;code&gt;enum&lt;/code&gt;: this gives the agent both the raw values it needs to submit and the human-readable labels for each option, reducing the chance of the agent picking the wrong value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best practices for tool design
&lt;/h2&gt;

&lt;p&gt;A few things the Chrome team recommends:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name tools with verbs that describe what happens.&lt;/strong&gt; Use &lt;code&gt;create_event&lt;/code&gt; for immediate creation, but &lt;code&gt;start_event_creation&lt;/code&gt; if the tool just opens a form. The name is the agent's primary signal for when to use a tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Describe what the tool does, not what it doesn't do.&lt;/strong&gt; Avoid descriptions like "Do not use for weather queries." Instead, describe exactly what the tool is for. Limitations should be implicit in a clear, specific description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accept raw user input in your schema.&lt;/strong&gt; If a user says "11am to 3pm", your tool should accept strings like &lt;code&gt;"11:00"&lt;/code&gt; and &lt;code&gt;"15:00"&lt;/code&gt;, not ask the model to convert to minutes since midnight. The model is not a calculator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Return results after the UI updates.&lt;/strong&gt; Agents may use UI state to verify an action succeeded before planning next steps. Make sure your &lt;code&gt;execute&lt;/code&gt; or &lt;code&gt;respondWith&lt;/code&gt; call happens after the UI reflects the new state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Register tools dynamically as UI state changes.&lt;/strong&gt; For single-page apps, register tools when they are available and unregister when they are not. An agent should never be offered a tool that does nothing or is currently disabled.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to try it today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install Chrome 146 or later&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;chrome://flags/#enable-webmcp-testing&lt;/code&gt; and enable the flag, then relaunch&lt;/li&gt;
&lt;li&gt;Install the &lt;strong&gt;Model Context Tool Inspector&lt;/strong&gt; extension from the Chrome Web Store&lt;/li&gt;
&lt;li&gt;Visit any WebMCP-enabled page, open the extension, and you will see all registered tools with their names, descriptions, and schemas&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The extension also lets you manually call tools with custom parameters and test them with natural language via the Gemini API, without needing a full AI agent setup.&lt;/p&gt;

&lt;p&gt;Google's official demo: &lt;code&gt;https://googlechromelabs.github.io/webmcp-tools/demos/react-flightsearch/&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Progressive enhancement by default
&lt;/h2&gt;

&lt;p&gt;A critical design choice: &lt;code&gt;navigator.modelContext&lt;/code&gt; simply does not exist in browsers that don't support WebMCP. Your code should check for it or use a library that handles this gracefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sites that adopt WebMCP are not broken for regular users. The tools are only visible to agents in WebMCP-capable browsers.&lt;/p&gt;




&lt;h2&gt;
  
  
  WebMCP vs MCP
&lt;/h2&gt;

&lt;p&gt;A common question: how does this differ from the Model Context Protocol (MCP) already used by tools like Claude Desktop?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;th&gt;WebMCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where tools run&lt;/td&gt;
&lt;td&gt;Server-side&lt;/td&gt;
&lt;td&gt;Client-side, in the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who deploys them&lt;/td&gt;
&lt;td&gt;Developer runs a server&lt;/td&gt;
&lt;td&gt;Any web page can define tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires a server&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works with&lt;/td&gt;
&lt;td&gt;Desktop AI apps&lt;/td&gt;
&lt;td&gt;In-browser AI agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spec status&lt;/td&gt;
&lt;td&gt;Established (Anthropic)&lt;/td&gt;
&lt;td&gt;Proposed (Google, early preview)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They are complementary. MCP gives desktop agents access to tools and data. WebMCP gives browser agents a structured interface to web pages. A future agent might use both: MCP to access your calendar, WebMCP to book a flight on an airline's website.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;WebMCP is in early preview, which means the API will change and the Chrome team is actively seeking feedback. But the direction is clear: the web is gaining a first-class interface for AI agents.&lt;/p&gt;

&lt;p&gt;Forms are the most natural starting point. They already represent the actions a site wants users to take: search, submit, book, register. WebMCP makes that intent machine-readable.&lt;/p&gt;

&lt;p&gt;If you want to make your existing forms WebMCP-ready without manually adding attributes to every one, &lt;a href="https://autowebmcp.dev" rel="noopener noreferrer"&gt;auto-webmcp&lt;/a&gt; is an open-source library that does this automatically with one script tag. But even without a library, you can start today: add &lt;code&gt;toolname&lt;/code&gt; and &lt;code&gt;tooldescription&lt;/code&gt; to your most important forms and see them appear in the inspector.&lt;/p&gt;




&lt;p&gt;The agentic web is not a distant future. It is in Chrome 146, behind a flag, right now.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow along with the WebMCP spec on GitHub and join the early preview at &lt;a href="https://developer.chrome.com/blog/webmcp-epp" rel="noopener noreferrer"&gt;developer.chrome.com/blog/webmcp-epp&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webmcp</category>
      <category>javascript</category>
      <category>ai</category>
      <category>chrome</category>
    </item>
    <item>
      <title>Every web form should be callable by AI agents (and yours can be today)</title>
      <dc:creator>Prasanna Vaidya</dc:creator>
      <pubDate>Sun, 22 Mar 2026 10:04:05 +0000</pubDate>
      <link>https://dev.to/prasannagyde/every-web-form-should-be-callable-by-ai-agents-and-yours-can-be-today-228</link>
      <guid>https://dev.to/prasannagyde/every-web-form-should-be-callable-by-ai-agents-and-yours-can-be-today-228</guid>
      <description>&lt;p&gt;AI agents are getting good at using computers. They can browse the web, click buttons, read emails, write code. But when it comes to filling out web forms, they still do it the hard way: CSS selectors, fragile XPath queries, DOM scraping that breaks every time a designer tweaks a class name.&lt;/p&gt;

&lt;p&gt;Chrome is about to change that.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebMCP: structured tool registration, built into the browser
&lt;/h2&gt;

&lt;p&gt;Chrome 146 shipped an Early Preview of &lt;strong&gt;WebMCP&lt;/strong&gt;, a browser-native API that lets websites register their interactive elements as structured tools. Think of it as MCP (Model Context Protocol) for desktop AI, but built directly into the browser itself.&lt;/p&gt;

&lt;p&gt;When a page registers a tool via WebMCP, an AI agent can see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What the tool is called&lt;/strong&gt; (e.g. &lt;code&gt;submit_flight_search&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What it does&lt;/strong&gt; (e.g. "Search for available flights between two cities")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What inputs it expects&lt;/strong&gt;: a full JSON Schema with field types, constraints, and descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No more guessing from HTML. No more brittle selectors. The agent just calls the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: adopting WebMCP manually is tedious
&lt;/h2&gt;

&lt;p&gt;Here is what it takes to register a single form manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_flights&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search for available flights between two airports on a given date.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IATA code of departure airport&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IATA code of arrival airport&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;departure_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Departure date (YYYY-MM-DD)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;passengers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;integer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;cabin_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;economy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;business&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;first&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;destination&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;departure_date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// fill the form, submit, return result&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's for &lt;em&gt;one&lt;/em&gt; form. A typical site has dozens.&lt;/p&gt;

&lt;p&gt;Then you have to maintain it every time a field changes. And write it for every platform your users are on: WordPress, Shopify, HubSpot, Zendesk, ServiceNow...&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: one script tag
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://autowebmcp.dev" rel="noopener noreferrer"&gt;auto-webmcp&lt;/a&gt;&lt;/strong&gt; is an open-source library that does all of this automatically.&lt;/p&gt;

&lt;p&gt;Drop one script tag into your page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/auto-webmcp@0.2.1/dist/auto-webmcp.iife.js"&lt;/span&gt;
  &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Every &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; on the page is automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scanned&lt;/strong&gt; — MutationObserver catches forms added by JavaScript too&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analyzed&lt;/strong&gt; — name inferred from submit button, heading, or form ID; description from legend or aria-label&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema-generated&lt;/strong&gt; — HTML input types mapped to JSON Schema with constraints, enums, and field titles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registered&lt;/strong&gt; — via &lt;code&gt;navigator.modelContext.registerTool()&lt;/code&gt; with full spec compliance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No manual JSON schema writing. No annotations required. No backend changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  It reads your existing HTML intelligently
&lt;/h2&gt;

&lt;p&gt;auto-webmcp infers everything it needs from your existing markup:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What it needs&lt;/th&gt;
&lt;th&gt;Where it looks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool name&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;toolname&lt;/code&gt; attr, then submit button text, then nearest heading, then form &lt;code&gt;id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tooldescription&lt;/code&gt; attr, then &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; text, then &lt;code&gt;aria-label&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field type&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;input type&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; mapped to JSON Schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field title&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; text, then humanized field name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Options&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; elements collected as &lt;code&gt;enum&lt;/code&gt; + &lt;code&gt;oneOf&lt;/code&gt; with display labels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want to override anything, you can add native spec attributes directly to the form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;toolname=&lt;/span&gt;&lt;span class="s"&gt;"submit_flight_search"&lt;/span&gt;
  &lt;span class="na"&gt;tooldescription=&lt;/span&gt;&lt;span class="s"&gt;"Search for available flights. Requires origin, destination, and date."&lt;/span&gt;
  &lt;span class="na"&gt;toolautosubmit=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use &lt;code&gt;data-webmcp-*&lt;/code&gt; attributes, which are useful for platforms where you cannot touch the form element directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe to ship on any site today
&lt;/h2&gt;

&lt;p&gt;auto-webmcp is built around &lt;strong&gt;progressive enhancement&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No &lt;code&gt;navigator.modelContext&lt;/code&gt;? The script loads and does nothing, silently&lt;/li&gt;
&lt;li&gt;Password, hidden, and file inputs are never exposed to agents&lt;/li&gt;
&lt;li&gt;No data leaves the browser: completely client-side, MIT licensed, no API key required&lt;/li&gt;
&lt;li&gt;Works with React, Vue, Angular, and any framework (MutationObserver handles dynamic forms)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can drop it into production today and regular users will not notice anything. Only WebMCP-capable agents will see the registered tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Works on every platform
&lt;/h2&gt;

&lt;p&gt;Because it is just a script tag, it works anywhere custom JavaScript is allowed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WordPress&lt;/strong&gt;: install the &lt;a href="https://autowebmcp.dev/platforms/wordpress" rel="noopener noreferrer"&gt;auto-webmcp plugin&lt;/a&gt; (currently in wp.org review) or add one line to &lt;code&gt;functions.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify&lt;/strong&gt;: paste into &lt;code&gt;theme.liquid&lt;/code&gt; before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wix&lt;/strong&gt;: Settings, Custom Code, Body end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HubSpot&lt;/strong&gt;: Footer HTML tracking code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zendesk&lt;/strong&gt;: Zendesk Apps Framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ServiceNow&lt;/strong&gt;: UI Scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://autowebmcp.dev/platforms" rel="noopener noreferrer"&gt;Full platform guides&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open Chrome 146+ and enable &lt;code&gt;chrome://flags/#enable-webmcp-testing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install the Model Context Tool Inspector extension from the Chrome Web Store&lt;/li&gt;
&lt;li&gt;Visit &lt;a href="https://autowebmcp.dev/demo" rel="noopener noreferrer"&gt;autowebmcp.dev/demo&lt;/a&gt; and watch the registered tools appear in the inspector panel in real time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agentic web is arriving. WebMCP is the infrastructure layer. auto-webmcp is the one-line on-ramp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://autowebmcp.dev" rel="noopener noreferrer"&gt;autowebmcp.dev&lt;/a&gt; · &lt;a href="https://github.com/prasanna-gyde/auto-webmcp" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · MIT · open source&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webmcp</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
