<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Agentic-Web on Damian Galarza | Production AI Consulting</title><link>https://www.damiangalarza.com/tags/agentic-web/</link><description>Recent posts from Damian Galarza | Production AI Consulting</description><generator>Hugo</generator><language>en-us</language><managingEditor>Damian Galarza</managingEditor><atom:link href="https://www.damiangalarza.com/tags/agentic-web/feed.xml" rel="self" type="application/rss+xml"/><item><title>WebMCP: Make Your Site Agent-Ready Without a Separate MCP Server</title><link>https://www.damiangalarza.com/posts/2026-09-02-webmcp-agent-ready-without-separate-mcp-server/</link><pubDate>Wed, 02 Sep 2026 00:00:00 -0400</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2026-09-02-webmcp-agent-ready-without-separate-mcp-server/</guid><description>WebMCP lets your web app register typed tools for browser agents with document.modelContext.registerTool. No separate MCP server. Here's how it works, and when you still need one.</description><content:encoded><![CDATA[<p>A browser agent landing on your web app today has two ways to get anything done. It can parse the DOM and guess what your markup means, or it can drive the page visually from screenshots. Both are slow, brittle, and expensive in tokens, and both break the day you ship a redesign. The agent is reverse-engineering your UI on every visit.</p>
<p>The standard fix has been to build a separate integration surface for agents, usually an MCP server. That works, but it&rsquo;s real infrastructure. You host it, you version it, you build OAuth for it, and you re-implement session state that your frontend already has. For a lot of web apps, that&rsquo;s a meaningful cost to pay for functionality that already exists in the page&rsquo;s JavaScript.</p>
<p>WebMCP is a proposal that removes that middle layer. The page itself registers typed tools with the browser, browser-integrated agents discover and call them, and each tool&rsquo;s implementation is a function that runs code your app already has.</p>
<div class="not-prose my-8 callout-accent">
  <p class="text-[var(--color-text-secondary)]">
    <span class="font-medium text-[var(--color-accent)]">Prefer video?</span> Watch <a href="https://youtu.be/HAsz_4epET8" class="text-[var(--color-accent)] underline hover:opacity-80">WebMCP: How to Make Your Site Agent-Ready Without the Middleware on YouTube →</a>
  </p>
</div>
<p>In the video I walk through wiring this into a production Rails app. This post is the written companion, covering what WebMCP is, the anatomy of a tool, the security boundary that does not move, and the decision framework for when WebMCP is enough and when you still want a dedicated MCP server.</p>
<h2 id="what-webmcp-is-and-what-it-isnt">What WebMCP Is (and What It Isn&rsquo;t)</h2>
<p>WebMCP is a proposed browser API that lets a web page expose tools to an AI agent built into the browser. Everything hangs off <code>document.modelContext</code>. A page calls <code>registerTool</code> with a name, a description, a JSON Schema for inputs, and an <code>execute</code> callback. The browser holds the registry, tags each tool with its origin, and surfaces the tools to whatever agent it hosts. When the agent calls a tool, your callback runs in the page, in the user&rsquo;s existing session, and the user watches the same page update.</p>
<figure class="post-diagram" style="margin:2rem 0 2.5rem;">
<svg viewBox="0 0 400 304" role="img" aria-label="WebMCP at runtime. Your page registers tools with the browser by calling registerTool with a name, description, input schema, and execute callback. The browser holds the registry, tags each tool with its origin, and surfaces the tools to the browser agent. When the agent calls a tool, the browser mediates the call and runs the page's execute callback in the user's existing session." style="max-width:400px;width:100%;height:auto;display:block;margin:0 auto;font-family:inherit;color:#c9c5bc;">
<defs>
<marker id="webmcp-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker>
</defs>
<rect x="48" y="8" width="304" height="84" rx="6" fill="none" stroke="currentColor" stroke-opacity="0.45"/>
<text x="60" y="28" font-size="11" font-weight="600" letter-spacing="1" fill="currentColor">YOUR PAGE</text>
<rect x="60" y="36" width="256" height="24" rx="4" fill="none" stroke="currentColor" stroke-opacity="0.4"/>
<text x="68" y="52" font-size="10" fill="currentColor">name · description · inputSchema · execute()</text>
<text x="60" y="80" font-size="12" fill="#ffffff">execute() runs here, in the user's session</text>
<line x1="120" y1="92" x2="120" y2="130" stroke="currentColor" stroke-opacity="0.6" marker-end="url(#webmcp-arrow)"/>
<text x="128" y="116" font-size="10" fill="currentColor">registerTool</text>
<line x1="280" y1="132" x2="280" y2="94" stroke="currentColor" stroke-opacity="0.6" marker-end="url(#webmcp-arrow)"/>
<text x="272" y="116" font-size="10" fill="currentColor" text-anchor="end">execute()</text>
<rect x="48" y="132" width="304" height="64" rx="6" fill="none" stroke="currentColor" stroke-opacity="0.6"/>
<text x="60" y="152" font-size="11" font-weight="600" letter-spacing="1" fill="currentColor">BROWSER</text>
<text x="60" y="172" font-size="12" fill="#ffffff">holds the registry, mediates every call</text>
<text x="60" y="188" font-size="10" fill="currentColor">origin tags · permissions · user confirmations</text>
<line x1="120" y1="196" x2="120" y2="234" stroke="currentColor" stroke-opacity="0.6" marker-end="url(#webmcp-arrow)"/>
<text x="128" y="220" font-size="10" fill="currentColor">surfaces tools</text>
<line x1="280" y1="236" x2="280" y2="198" stroke="currentColor" stroke-opacity="0.6" marker-end="url(#webmcp-arrow)"/>
<text x="272" y="220" font-size="10" fill="currentColor" text-anchor="end">tool call</text>
<rect x="48" y="236" width="304" height="64" rx="6" fill="#d84835" fill-opacity="0.09" stroke="#d84835" stroke-opacity="0.7"/>
<text x="60" y="256" font-size="11" font-weight="600" letter-spacing="1" fill="#d84835">BROWSER AGENT · MODEL</text>
<text x="60" y="276" font-size="12" fill="#ffffff">picks tools by name and schema</text>
<text x="60" y="292" font-size="10" fill="currentColor">no DOM parsing, no screenshots</text>
</svg>
<figcaption style="margin-top:0.75rem;font-size:0.9rem;color:#928c80;">The agent never parses your markup. It calls tools by name and schema, the browser mediates each call, and execute() runs in the page with the user's existing session while the user watches the same page update.</figcaption>
</figure>
<p>The <a href="https://webmachinelearning.github.io/webmcp/">spec</a> frames it well. Pages that use WebMCP &ldquo;can be thought of as Model Context Protocol servers that implement tools in client-side script instead of on the backend.&rdquo;</p>
<p>What WebMCP isn&rsquo;t deserves the same precision, because that&rsquo;s where early conclusions run ahead of the spec:</p>
<ul>
<li><strong>It is MCP-inspired, not MCP over the wire.</strong> Chrome&rsquo;s documentation describes it as a set of &ldquo;MCP-inspired&rdquo; APIs rather than a JavaScript implementation of MCP. It has tools only, with no resources, no prompts, no JSON-RPC, and no transport. The browser mediates everything.</li>
<li><strong>It is a Draft Community Group Report, not a W3C Standard.</strong> It was proposed by Microsoft and Google engineers in August 2025 under the W3C Web Machine Learning Community Group, and the current draft was last revised August 26, 2026. It is not on the W3C Standards Track. That can change, but today you are building against a moving proposal.</li>
<li><strong>It is not a discovery mechanism.</strong> Tools exist only while a user has your page open. There is no crawlable manifest. WebMCP does not replace structured data, readable content, or a public API.</li>
<li><strong>It is not headless automation.</strong> The design intent, in Chrome&rsquo;s words, is local browser workflows with human oversight. A cron job cannot call your WebMCP tools. Neither can Claude Code.</li>
</ul>
<p>Who consumes these tools matters more than the spec text. As of August 25, 2026, OpenAI ships WebMCP support (branded &ldquo;Site tools&rdquo;) in the ChatGPT desktop app&rsquo;s built-in browser, Codex, and ChatGPT Sites. Google has said Gemini in Chrome will consume WebMCP tools, though I haven&rsquo;t seen that confirmed as shipping. Before the OpenAI launch there was no mainstream consumer at all, which tells you how early this is.</p>
<h2 id="a-tool-is-four-things">A Tool Is Four Things</h2>
<p>Every WebMCP tool is the same four parts:</p>
<ol>
<li><strong>A name.</strong> The identifier the agent calls. Keep it short, stable, and unambiguous.</li>
<li><strong>A description.</strong> Natural language explaining what the tool does and when to use it. This is what the model reads to decide whether the tool fits the task, so write it the way you&rsquo;d write for a new teammate, including side effects.</li>
<li><strong>An input schema.</strong> JSON Schema describing the parameters. This is the contract that replaces guessing at your form fields.</li>
<li><strong>An execute function.</strong> A callback that does the work. It&rsquo;s a thin wrapper around code your app already has, calling the same functions your own UI calls, not new logic you write for the agent.</li>
</ol>
<figure class="post-diagram" style="margin:2rem 0 2.5rem;">
<svg viewBox="0 0 400 308" role="img" aria-label="The anatomy of a WebMCP tool. One registerTool call contains four parts. A name, the identifier the agent calls. A description, what the model reads to choose the tool. An input schema, the JSON Schema contract for inputs. And an execute function, a thin wrapper that calls the same functions your UI already calls." style="max-width:400px;width:100%;height:auto;display:block;margin:0 auto;font-family:inherit;color:#c9c5bc;">
<defs>
<marker id="webmcp-tool-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker>
</defs>
<rect x="48" y="8" width="304" height="212" rx="6" fill="none" stroke="currentColor" stroke-opacity="0.45"/>
<text x="60" y="28" font-size="11" font-weight="600" letter-spacing="1" fill="currentColor">A WEBMCP TOOL</text>
<text x="340" y="28" font-size="10" fill="currentColor" fill-opacity="0.7" text-anchor="end">registerTool({ ... })</text>
<rect x="60" y="40" width="280" height="36" rx="4" fill="none" stroke="currentColor" stroke-opacity="0.4"/>
<text x="72" y="55" font-size="12" font-weight="600" fill="#ffffff">name</text>
<text x="72" y="69" font-size="10" fill="currentColor">the identifier the agent calls</text>
<rect x="60" y="84" width="280" height="36" rx="4" fill="none" stroke="currentColor" stroke-opacity="0.4"/>
<text x="72" y="99" font-size="12" font-weight="600" fill="#ffffff">description</text>
<text x="72" y="113" font-size="10" fill="currentColor">what the model reads to choose the tool</text>
<rect x="60" y="128" width="280" height="36" rx="4" fill="none" stroke="currentColor" stroke-opacity="0.4"/>
<text x="72" y="143" font-size="12" font-weight="600" fill="#ffffff">inputSchema</text>
<text x="72" y="157" font-size="10" fill="currentColor">JSON Schema, the contract for inputs</text>
<rect x="60" y="172" width="280" height="36" rx="4" fill="#d84835" fill-opacity="0.12" stroke="#d84835"/>
<text x="72" y="187" font-size="12" font-weight="600" fill="#ffffff">execute()</text>
<text x="72" y="201" font-size="10" fill="currentColor">a thin wrapper, not new logic</text>
<line x1="200" y1="208" x2="200" y2="250" stroke="currentColor" stroke-opacity="0.6" marker-end="url(#webmcp-tool-arrow)"/>
<text x="208" y="240" font-size="10" fill="currentColor">calls</text>
<rect x="48" y="252" width="304" height="48" rx="6" fill="none" stroke="currentColor" stroke-opacity="0.45"/>
<text x="60" y="272" font-size="11" font-weight="600" letter-spacing="1" fill="currentColor">YOUR EXISTING CODE</text>
<text x="60" y="290" font-size="12" fill="#ffffff">the same functions your UI calls</text>
</svg>
<figcaption style="margin-top:0.75rem;font-size:0.9rem;color:#928c80;">Three of the four parts are the contract the model reads. The fourth is a thin wrapper around code your app already has.</figcaption>
</figure>
<p>Here&rsquo;s the shape, trimmed from the pattern I use in the video. The app validates video ideas, and this tool submits one:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// app/javascript/webmcp/tools.js
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nb">document</span><span class="p">.</span><span class="nx">modelContext</span><span class="o">?</span><span class="p">.</span><span class="nx">registerTool</span> <span class="o">===</span> <span class="s2">&#34;function&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nb">document</span><span class="p">.</span><span class="nx">modelContext</span><span class="p">.</span><span class="nx">registerTool</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;submit_video_idea&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">description</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Submit a video idea for validation. Starts an async research job. Returns the idea id to use when polling for the verdict.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">inputSchema</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">type</span><span class="o">:</span> <span class="s2">&#34;object&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">properties</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">title</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">type</span><span class="o">:</span> <span class="s2">&#34;string&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">description</span><span class="o">:</span> <span class="s2">&#34;Working title for the video idea&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nx">required</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;title&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nx">additionalProperties</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="kr">async</span> <span class="nx">execute</span><span class="p">({</span> <span class="nx">title</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// The same code path the submit button already uses
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="kr">const</span> <span class="nx">idea</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">submitIdea</span><span class="p">({</span> <span class="nx">title</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">content</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span> <span class="nx">type</span><span class="o">:</span> <span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="nx">text</span><span class="o">:</span> <span class="sb">`Idea </span><span class="si">${</span><span class="nx">idea</span><span class="p">.</span><span class="nx">id</span><span class="si">}</span><span class="sb"> submitted. Validation is running.`</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Four details in this example matter:</p>
<ul>
<li><strong>Feature detection is mandatory.</strong> Most browsers don&rsquo;t have <code>document.modelContext</code> yet, so the guard clause keeps the page working everywhere else. This mirrors the pattern OpenAI recommends in their <a href="https://learn.chatgpt.com/docs/webmcp">WebMCP guide</a>.</li>
<li><strong>The return value goes back to the model.</strong> Return enough for the agent to verify the result and take its next step, like the id it needs for polling. The browser stringifies the result for the model.</li>
<li><strong>Annotations tune trust.</strong> Mark non-mutating tools with <code>annotations: { readOnlyHint: true }</code> so the browser can skip confirmation prompts, and mark tools that return user-generated or external content with <code>untrustedContentHint</code>.</li>
<li><strong>Registration is scoped.</strong> You can pass an <code>AbortSignal</code> to unregister a tool when app state changes, and <code>provideContext</code> replaces the whole toolset at once. Tools should reflect what the current page can actually do.</li>
</ul>
<p>There&rsquo;s also a declarative variant where you annotate an ordinary form with <code>toolname</code> and <code>tooldescription</code> attributes and the browser derives the tool schema from the fields. It&rsquo;s a nice fit for lead capture and booking forms, but ChatGPT&rsquo;s browser only supports the imperative API today, so I&rsquo;d treat the declarative form as something to watch rather than ship.</p>
<p>If most of your product&rsquo;s behavior already lives in client-side functions, that&rsquo;s the whole integration. The work is choosing which capabilities to expose and writing honest descriptions.</p>
<h2 id="removing-the-middleware-doesnt-remove-the-security-boundary">Removing the Middleware Doesn&rsquo;t Remove the Security Boundary</h2>
<p>Because there&rsquo;s no separate service to stand up, it&rsquo;s easy to assume there&rsquo;s no new security work either.</p>
<p>A WebMCP tool runs in the page with the user&rsquo;s session and cookies. The agent&rsquo;s cart is the user&rsquo;s cart. That&rsquo;s the feature, and it&rsquo;s also the risk: every tool you register is a new entry point into your application, operated by a model that reads untrusted content all day. The spec&rsquo;s own security section describes prompt injection through tool descriptions and tool outputs, over-privileged tools, and schemas that invite the model to pass sensitive data.</p>
<p>The working rule is that nothing about your backend security model changes. Authentication, ownership checks, quotas, and rate limits stay server-side, exactly where they were before WebMCP existed:</p>
<ul>
<li><strong>Auth stays on the server.</strong> The tool rides the existing session. Your server still decides what that session is allowed to do.</li>
<li><strong>Ownership checks stay on the server.</strong> A <code>list_channels</code> tool returns the current user&rsquo;s channels because the endpoint scopes the query, not because the tool&rsquo;s description promises to behave.</li>
<li><strong>Quotas and validation stay on the server.</strong> If submissions are limited per plan, the limit is enforced where it always was. <code>execute</code> can validate inputs for fast feedback, but client-side validation remains a UX courtesy, not a control.</li>
</ul>
<p>If a capability would be unsafe as a public endpoint called by a hostile client, it&rsquo;s unsafe as a WebMCP tool. The browser adds real mitigations on top (confirmation prompts for sensitive actions, origin isolation, a Permissions Policy for tools, and ChatGPT treats all site-provided tool definitions as untrusted), but those protect the user from your tools. Protecting your system from a confused or manipulated agent is still your job, and it lives behind the same boundary it always did.</p>
<p>WebMCP removes the integration layer, not the trust boundary. Expose the narrowest capability that serves the task, and let the server keep saying no.</p>
<h2 id="webmcp-vs-mcp-when-each-one-fits">WebMCP vs MCP: When Each One Fits</h2>
<p>WebMCP does not replace a dedicated MCP server, and both Chrome and OpenAI say so directly. Chrome&rsquo;s guidance is to treat them as &ldquo;partners, not opponents.&rdquo; OpenAI&rsquo;s split is that MCP fits searching a service or managing records through an API, while WebMCP fits the moments when &ldquo;you and the agent need to see the same thing.&rdquo;</p>
<p>The structural differences drive the decision:</p>
<table>
  <thead>
      <tr>
          <th>Dimension</th>
          <th>WebMCP (in-page tools)</th>
          <th>Dedicated MCP server</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Where the tool runs</td>
          <td>Client-side, in the page</td>
          <td>On a server you host and operate</td>
      </tr>
      <tr>
          <td>Lifecycle</td>
          <td>Ephemeral, tab-bound, gone on navigation</td>
          <td>Persistent service</td>
      </tr>
      <tr>
          <td>Who can call it</td>
          <td>Browser-integrated agents only</td>
          <td>Any MCP client (Claude, Claude Code, CLI agents, cloud agents)</td>
      </tr>
      <tr>
          <td>Auth</td>
          <td>The user&rsquo;s existing session</td>
          <td>OAuth or API keys you build and maintain</td>
      </tr>
      <tr>
          <td>State</td>
          <td>Live page state, shared with the human</td>
          <td>Fetched from your system of record</td>
      </tr>
      <tr>
          <td>Human oversight</td>
          <td>User watches and can intervene on the same screen</td>
          <td>Headless</td>
      </tr>
      <tr>
          <td>Build cost</td>
          <td>Wrap existing functions</td>
          <td>New service, with hosting, auth, versioning, and ops</td>
      </tr>
  </tbody>
</table>
<p>When I&rsquo;m deciding for a specific capability, I ask these questions in order:</p>
<ol>
<li><strong>Is there a browser session with your page open while the task runs?</strong> This is the hard constraint, and it&rsquo;s about the tab, not the human. Tools exist only while a WebMCP-capable browser has your page loaded, so a cron job, a backend service, or a CLI agent like Claude Code has no path to them. Those clients need an MCP server or a plain API.</li>
<li><strong>Is the work attended or unattended?</strong> Nothing in the API strictly requires a human watching, and an agent driving its own browser can call your tools without one. But the stated design intent is human oversight, the browser leans on user confirmation for sensitive actions, and today&rsquo;s consumers are built around an attended session. I&rsquo;d treat WebMCP as the attended path and put unattended automation on MCP, where auth and rate limits are designed for exactly that.</li>
<li><strong>Does the action need the page&rsquo;s live state or session?</strong> A cart, an unsaved draft, a filtered dashboard. This is where WebMCP is the natural fit, because a separate server would have to replicate that state to act on it.</li>
<li><strong>Does the logic already exist client-side?</strong> If yes, WebMCP costs almost nothing. If the logic only exists behind an authenticated server API, an MCP server may be nearly as cheap to build and reaches far more clients.</li>
<li><strong>Is the answer &ldquo;both&rdquo;?</strong> Then share the implementation. Keep the business logic in one place, expose it through MCP for headless clients, and register thin WebMCP tools in the page that call the same functions for in-browser collaboration.</li>
</ol>
<p>The failure mode to avoid is treating this as a versus decision at the company level. It&rsquo;s a per-capability decision. Checkout with a live cart wants WebMCP, nightly reporting wants MCP, and both can sit on the same service layer.</p>
<h2 id="what-this-looks-like-in-a-production-app">What This Looks Like in a Production App</h2>
<p>In the video I wire WebMCP into CreatorSignal, a production Rails app of mine that validates YouTube video ideas and returns a scored Go, Refine, or Kill verdict. It&rsquo;s the same app from <a href="/posts/2026-02-13-linear-agent-loop/">my Linear agent loop post</a>. The demo exposes three tools, and the trio is deliberate because each one teaches a different part of the pattern:</p>
<ul>
<li><strong>List channels</strong> is the read-only tool. This is where <code>readOnlyHint</code> belongs, and where the server scopes the query to the signed-in user. It exists so the agent can ground itself in real data instead of guessing at ids.</li>
<li><strong>Submit a video idea</strong> is the write tool. It belongs on the same code path the form submit uses, so every server-side check that protects the form protects the tool for free.</li>
<li><strong>Poll validation status</strong> exists because the interesting work is async. Validation runs as a background job, so the submit tool returns an id and this tool reports the job&rsquo;s progress until the verdict lands. Tab-bound tools can&rsquo;t wait around for a slow job, so you give the agent a way to check back, the same way you&rsquo;d design a polling endpoint for any client.</li>
</ul>
<p>The polling tool is the one that generalizes. Most real applications have async work in them, and &ldquo;return an id, poll for status&rdquo; translates directly from API design into tool design. Good WebMCP tools end up looking like good API endpoints with better documentation. The disciplines are the same ones as MCP tool design: narrow inputs, described side effects, and enough in the return value for the caller to verify what happened.</p>
<h2 id="where-webmcp-stands-today">Where WebMCP Stands Today</h2>
<p>WebMCP is early, and the specifics of how early should shape how much you build on it.</p>
<table>
  <thead>
      <tr>
          <th>Surface</th>
          <th>Status (as of late August 2026)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Spec</td>
          <td><a href="https://webmachinelearning.github.io/webmcp/">Draft Community Group Report</a>, revised 2026-08-26. Not a W3C Standard, not on the Standards Track</td>
      </tr>
      <tr>
          <td>Chrome 149+</td>
          <td><a href="https://developer.chrome.com/blog/ai-webmcp-origin-trial">Origin trial open</a>. Local testing via the <code>chrome://flags/#enable-webmcp-testing</code> flag</td>
      </tr>
      <tr>
          <td>Edge 147+</td>
          <td>Experimental, behind a flag</td>
      </tr>
      <tr>
          <td>Firefox / Safari</td>
          <td>In the discussion, no commitment</td>
      </tr>
      <tr>
          <td>ChatGPT desktop browser, Codex, ChatGPT Sites</td>
          <td><a href="https://learn.chatgpt.com/docs/webmcp">Shipping since 2026-08-25</a> as &ldquo;Site tools.&rdquo; Imperative API only, top-level page only, gated to specific models, and not available in Enterprise/Edu workspaces</td>
      </tr>
      <tr>
          <td>Gemini in Chrome</td>
          <td>Announced as a consumer, not confirmed shipping</td>
      </tr>
  </tbody>
</table>
<p>On August 5, 2026, <a href="https://shopify.dev/changelog/webmcp-liquid-hydrogen">Shopify enabled WebMCP tools</a> on every Liquid storefront and the Hydrogen developer preview, with nothing for merchants to install. Ten tools covering catalog search, cart, checkout, and store policies. That&rsquo;s precise scope, every Liquid storefront rather than every Shopify store, but it&rsquo;s still one platform making a large slice of commerce agent-usable by default. Combined with OpenAI shipping a consumer, the chicken-and-egg problem that stalls most web proposals has a crack in it.</p>
<p>Now the caveats I&rsquo;d weigh before committing:</p>
<ul>
<li><strong>The API is still moving.</strong> In July 2026 the spec relocated the API from <code>navigator.modelContext</code> to <code>document.modelContext</code>, and Chrome 150 deprecates the old path. Early adopters are already carrying migration debt, and I would expect more of it before this stabilizes.</li>
<li><strong>The consumer base is narrow.</strong> Chromium only, and actual consumption today is effectively the ChatGPT desktop browser. Whether Gemini in Chrome ships, and whether Anthropic&rsquo;s browser agent consumes WebMCP tools, are open questions.</li>
<li><strong>There is no discovery story.</strong> Tools are invisible until a user lands on your page. WebMCP is a better contract for agents that already arrive, not a way to attract them.</li>
</ul>
<p>None of that makes it a bad bet. The integration cost is low precisely because the tools wrap code you already have, which caps the downside. But go in knowing you&rsquo;re adopting an origin-trial API, not a settled platform feature.</p>
<h2 id="how-id-start">How I&rsquo;d Start</h2>
<p>If you own a web app and want to try this, the path I&rsquo;d take is small and low-commitment:</p>
<ol>
<li>Enable the flag in Chrome 149+ (or register for the origin trial for real users) and install Chrome&rsquo;s Model Context Tool Inspector extension so you can see and execute registered tools manually.</li>
<li>Register one read-only tool that wraps a function your app already has, with <code>readOnlyHint</code> set and a description you&rsquo;d be comfortable showing a new hire.</li>
<li>Add one write tool that goes through the exact code path your UI uses, and confirm your server-side auth, ownership, and quota checks fire when the tool calls it.</li>
<li>Only then decide whether a dedicated MCP server belongs in the picture, using the questions above per capability rather than picking a side.</li>
</ol>
<p>That ordering keeps the security boundary honest and gives you a working demo in an afternoon instead of an infrastructure project.</p>
<hr>
<p>If you&rsquo;re working out how your product should meet AI agents, whether that&rsquo;s WebMCP tools, an MCP server, or the service layer that backs both, this is the kind of architecture decision I help teams make. <a href="/ai-agents/">See how I help teams build agentic systems</a>.</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li><a href="https://webmachinelearning.github.io/webmcp/">WebMCP specification</a> is the Draft Community Group Report itself.</li>
<li><a href="https://github.com/webmachinelearning/webmcp">WebMCP explainer and repo</a> covers the motivation, the declarative API, and open questions.</li>
<li><a href="https://developer.chrome.com/docs/ai/webmcp">Chrome&rsquo;s WebMCP documentation</a> includes the imperative and declarative API guides and tool security guidance.</li>
<li><a href="https://developer.chrome.com/docs/ai/webmcp/compare-mcp">Comparing WebMCP and MCP</a> is Chrome&rsquo;s own take on the decision this post frames.</li>
<li><a href="https://learn.chatgpt.com/docs/webmcp">OpenAI&rsquo;s WebMCP guide for ChatGPT</a> documents what Site tools support today.</li>
<li><a href="https://shopify.dev/changelog/webmcp-liquid-hydrogen">Shopify&rsquo;s WebMCP changelog entry</a> details the default-on storefront tools.</li>
<li><a href="https://youtu.be/HAsz_4epET8">WebMCP: How to Make Your Site Agent-Ready Without the Middleware (YouTube)</a> is the companion video where I wire these tools into a production Rails app.</li>
</ul>
]]></content:encoded></item></channel></rss>