Framework Guide · WebMCP · Astro
WebMCP for Astro: the complete guide to agent-ready islands
Astro ships near-zero JavaScript and WebMCP lives inside JavaScript — which makes Astro the most contrarian, and arguably the most rewarding, place to adopt the WebMCP standard. This reference article, written for site architects and front-end engineers, covers the three legitimate integration surfaces on an Astro site (plain script tags, framework islands, declarative forms), the view-transition pitfall, the Chrome 149–156 origin trial wiring, and the verification procedure with the official tooling.
Fact-checked and updated on September 2, 2026 for the WebMCP ecosystem (CG-DRAFT). Primary sources are cited at the end of the article.
Why Astro and WebMCP are an unusual but excellent match
Astro’s core promise is islands architecture: pages render to static HTML at build time, and client-side JavaScript loads only for components you explicitly hydrate with a client:* directive ([Astro islands documentation](https://docs.astro.build/en/concepts/islands/)). WebMCP is a browser API incubated in the W3C Web Machine Learning Community Group: a page registers tools — named functions with JSON-Schema inputs, or annotated forms — that an in-browser agent such as Gemini in Chrome can invoke without pixel-guessing ([specification](https://webmachinelearning.github.io/webmcp)).
The tension is productive. On a React or Vue site you fight to keep agent plumbing out of the hydration budget; on Astro, the budget is the design constraint from day one. You end up asking the right question — which handful of actions deserve to exist as callable tools, and where does their few kilobytes of JavaScript run? — and answering it deliberately gives you the best cost-per-agent-feature ratio on the modern web.
Implementation status: what you can actually ship in 2026
Chrome’s rollout timeline matters for Astro because a static site is cached at the edge, and origin-trial tokens interact with caching:
- Chrome 146 (February 2026): early preview behind
chrome://flags/#enable-webmcp-testing. Local development only. - Chrome 149 (June 2026): public origin trial begins — register your origin at the Chrome Origin Trials console, receive a token, and serve it via an
Origin-TrialHTTP header or a<meta http-equiv="origin-trial">tag. The trial runs through Chrome 156 ([Chrome for Developers](https://developer.chrome.com/docs/ai/webmcp)). - Chrome 150+: stabilization around the canonical
document.modelContextroot; the earliernavigator.modelContextremains as a compatibility alias during the trial window.
Outside Chrome: Microsoft co-authored the spec but WebMCP is not in Edge’s release notes; Firefox and Safari have expressed interest without announced timelines. Every integration in this guide therefore opens with feature detection — which on Astro costs literally nothing for unsupported visitors, since your script can bail before importing anything.
Surface 1: the plain <script> tag
In an .astro file, a bare <script> is bundled, content-hashed and executed as a module on the client — no directive, no framework runtime ([Astro client-side scripts](https://docs.astro.build/en/guides/client-side-scripts/)). For a content-and-forms site this is the entire imperative integration:
---
// src/pages/index.astro
---
<html lang="en">
<head>
<title>Acme — Search our catalog</title>
{import.meta.env.PUBLIC_ORIGIN_TRIAL_TOKEN && (
<meta http-equiv="origin-trial" content={import.meta.env.PUBLIC_ORIGIN_TRIAL_TOKEN} />
)}
</head>
<body>
<main>
<h1>Acme Catalog</h1>
<div class="search">
<input id="q" type="search" placeholder="Search products" />
</div>
</main>
<script>
const modelContext = document.modelContext ?? navigator.modelContext;
if (modelContext && 'registerTool' in modelContext) {
const controller = new AbortController();
await modelContext.registerTool(
{
name: 'search-catalog',
description:
'Searches the Acme product catalog by keyword. Returns up to 10 products with name, price and URL.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search keywords' },
maxResults: { type: 'integer', minimum: 1, maximum: 10, default: 5 },
},
required: ['query'],
additionalProperties: false,
},
annotations: { readOnlyHint: true },
async execute({ query, maxResults = 5 }) {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const { products } = await res.json();
return {
content: [{ type: 'text', text: JSON.stringify(products.slice(0, maxResults)) }],
};
},
},
{ signal: controller.signal },
);
}
</script>
</body>
</html>
Three production details are already baked in. First, feature detection before anything else — the `if` guard means visitors on unsupported browsers download a dead branch and pay nothing. Second, the tool returns the MCP content envelope (`{ content: [{ type: “text”, text }] }`), so your in-page tools stay schema-compatible with server-side MCP servers (compare our Declarative API reference, which uses the same envelope). Third, registration is bound to an AbortController signal: the specification deliberately has no unregisterTool() — aborting the signal is unregistration, which keeps page-lifetime tool state honest.
<meta> token variant and the anonymous variant can collide, serving a cached page whose token was stripped. Prefer the Origin-Trial header at the CDN level — it travels with every response and never enters the cache key.Surface 2: tools inside framework islands
When the action lives in a hydrated React/Svelte/Vue component, register where the lifecycle lives. Astro’s client:load, client:idle and client:visible directives control when the island — and therefore its tools — materialize; a checkout island marked client:visible publishes add-to-cart only once the user (or agent) scrolls it into reach.
// src/components/CartTools.jsx — React island
'use client';
import { useEffect } from 'react';
export default function CartTools() {
useEffect(() => {
const modelContext = document.modelContext ?? navigator.modelContext;
if (!modelContext) return;
const controller = new AbortController();
modelContext.registerTool(
{
name: 'add-to-cart',
description: 'Adds a product to the cart by product id, with quantity (default 1).',
inputSchema: {
type: 'object',
properties: {
productId: { type: 'string' },
quantity: { type: 'integer', minimum: 1, maximum: 20, default: 1 },
},
required: ['productId'],
additionalProperties: false,
},
async execute({ productId, quantity = 1 }) {
const r = await fetch('/api/cart', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ productId, quantity }),
});
return { content: [{ type: 'text', text: await r.text() }] };
},
},
{ signal: controller.signal },
);
return () => controller.abort();
}, []);
return null;
}
Ready-made hooks exist if you prefer not to hand-roll: usewebmcp (tools only) and @mcp-b/react-webmcp (tools, prompts, resources) from the MCP-B project, with @mcp-b/global as an SSR-safe polyfill that no-ops outside the browser ([MCP-B framework guide](https://docs.mcp-b.ai/how-to/frameworks)).
Surface 3: declarative forms — the Astro-native sweet spot
Astro content sites are heavy on contact, newsletter and quote forms — and the Declarative API needs no JavaScript at all: the browser compiles annotated HTML into a tool, synthesizing the JSON Schema from the inputs’ native semantics. The full mechanics are dissected in our Declarative API reference article; on Astro the pattern is:
<form action="/contact/thanks" method="post"
toolname="contact-sales"
tooldescription="Sends a message to the Acme sales team. Takes name, email and an optional company; returns a confirmation page.">
<label for="name">Name</label>
<input id="name" name="name" type="text" required />
<label for="email">Email</label>
<input id="email" name="email" type="email" required />
<label for="company">Company</label>
<input id="company" name="company" type="text" />
<button type="submit">Contact us</button>
</form>
Omit toolautosubmit and the browser leaves the final submit to a human — the spec’s built-in human-in-the-loop. Annotating the markup pass of an existing Astro site is a one-afternoon, zero-dependency upgrade to agent-readiness.
The Astro-specific pitfall: View Transitions
With Astro’s <ClientRouter />, navigation is client-side and page scripts do not re-execute — a registered tool from the previous page either lingers, doubles, or goes stale against the new page’s data, and declarative tools move with swapped DOM in ways the draft spec does not yet standardize. Astro itself signals this with astro:astro:before-preparation lifecycle events; the disciplined pattern is to abort your controller on navigation and re-register per page:
let controller;
function register() {
controller = new AbortController();
document.modelContext?.registerTool(searchTool, { signal: controller.signal });
}
document.addEventListener('astro:before-preparation', () => controller?.abort());
document.addEventListener('astro:page-load', register);
register();
Until you need transitions on tool-bearing routes, the pragmatic alternative is exactly as legitimate: mark those layouts transition:animate="none" and keep the rest of the site animated.
Verification with the official tooling
Build, preview (astro preview — production output, not dev-server illusions), enable chrome://flags/#enable-webmcp-testing locally or load the token, then validate with the Model Context Tool Inspector extension (François Beaufort, Chrome team): discovery listing, synthesized-schema inspection, manual execution, and Gemini-simulated routing. In DevTools directly:
await document.modelContext.getTools();
await document.modelContext.executeTool('search-catalog', '{"query":"wool socks"}');
FAQ
Does registering WebMCP tools slow down my Astro pages?
No. A feature-detected script that bails on unsupported browsers is a few hundred bytes after the guard branch; Astro bundles and hashes it like any other module, and declarative forms add zero JavaScript by construction.
Should the token go in the layout or per page?
Every HTML page where tools must be visible needs the token (header injection covers this automatically). Token-bearing pages are also the ones you should exclude from aggressive HTML caching if your cache keys do not vary on it.
Astro or Next.js for an agent-ready site?
If your site is content-first, Astro: the tool surface is smaller and the hydration budget is already near zero. If your app is stateful product UI with authenticated flows, the App Router’s boundary model fits better — see our Next.js WebMCP guide.
Do desktop agents (Claude, Cursor) see Astro page tools?
No — in-browser tools require a live tab and an in-browser client. Agents that never render your HTML need a server-side MCP endpoint instead; the mature architecture ships both from one registry, as our WordPress guide demonstrates end to end.
Conclusion
Astro’s near-zero-JS philosophy is not an obstacle to WebMCP — it is a forcing function for the honest subset: register a search, expose the forms, hydrate the cart tool only where the cart lives. The agent surface you ship is small, explicit, and cached at the edge, while human visitors keep the page weight Astro was built for. That is the whole trick, and it is available today.
Primary sources
- webmachinelearning.github.io/webmcp — W3C CG specification.
- github.com/webmachinelearning/webmcp — specification repository and Declarative API explainer.
- developer.chrome.com/docs/ai/webmcp — official Chrome documentation and origin trial.
- docs.astro.build — islands architecture.
- docs.astro.build — client-side scripts.
- docs.mcp-b.ai — framework registration patterns.
- Model Context Tool Inspector — validation extension.