Install in an Astro site
Six steps, from an empty folder to a tool the agent cursor runs, in an Astro site with a React island.
Six steps, from an empty folder to a tool the agent cursor runs. In Astro, React runs in islands, so the provider and
the components that have tools share one island. This site’s own landing page is built that way.
No React on the page? Install without a framework shows createWebAgents, which a plain
Astro <script> can call.
Create a project
Skip this if you already have an Astro site with React. Create the site, then add React for the islands.
npm create astro@latestpnpm create astro@latestyarn create astro@latestbun create astro@latestnub create astro@latestaube create astro@latestnpx astro add reactpnpm dlx astro add reactyarn dlx astro add reactbunx astro add reactnubx astro add reactaube dlx astro add reactInstall the packages
The agent cursor and the React bindings. Nothing else: the provider loads a WebMCP polyfill where the browser has none, and the devtools panel in development.
npm install @webmcp-agents/core @webmcp-agents/reactpnpm add @webmcp-agents/core @webmcp-agents/reactyarn add @webmcp-agents/core @webmcp-agents/reactbun add @webmcp-agents/core @webmcp-agents/reactnub add @webmcp-agents/core @webmcp-agents/reactaube add @webmcp-agents/core @webmcp-agents/reactAdd the styles
Import the cursor’s stylesheet in your layout’s front matter, next to your own.
---
import "@webmcp-agents/core/cursor.css";
---Add the provider
<WebAgentsProvider> installs the cursor and registers your tools; in a browser without WebMCP it loads a polyfill
first, by itself, and in development it installs the devtools panel. It goes on a page in the next step.
import { WebAgentsProvider } from "@webmcp-agents/react";
import type { ReactNode } from "react";
export function Providers({ children }: { children: ReactNode }) {
return <WebAgentsProvider>{children}</WebAgentsProvider>;
}Add your first tool
The action is one function with two callers: a person clicks the button, an agent calls the tool. useWebTool
registers the tool while the component is on the page. A plain <button> is enough: the cursor finds it by its role
and name.
import { useWebTool } from "@webmcp-agents/react";
import { useState } from "react";
export function ClearCart() {
const [items, setItems] = useState(["Ocean Tee", "Canvas Tote"]);
const clear = () => {
setItems([]);
return "The cart is empty.";
};
// An agent calls this.
useWebTool({
id: "clear_cart",
title: "Clear cart",
description:
"Empty the cart. Use when the user asks to start over. " +
"The user confirms on the page.",
destructive: true,
requireConfirmation: true,
execute: clear,
});
// A person clicks this.
return (
<div>
<p>
{items.length > 0 ? items.join(", ") : "The cart is empty."}
</p>
<button type="button" onClick={clear}>
Clear cart
</button>
</div>
);
}Put the component inside the provider in one island, and render the island on a page. client:load renders it on
the server and hydrates it in the browser.
import { ClearCart } from "./clear-cart";
import { Providers } from "./providers";
// One island: the provider and the components whose tools it shows.
export function Cart() {
return (
<Providers>
<ClearCart />
</Providers>
);
}---
import { Cart } from "../components/cart";
import Layout from "../layouts/Layout.astro";
---
<Layout>
<Cart client:load />
</Layout>Run it
Start the site and open it.
npm run devpnpm run devyarn run devbun run devnub run devaube run devOpen the devtools panel (bottom right, or Alt+Shift+A), expand clear_cart and press Run. The cursor flies
to Clear cart and waits there. Click the button yourself: the cart empties, and the tool answers “The cart is
empty.”
Next
Define a tool
What you just wrote, line by line: execute, the title the cursor looks for, and target for when the name is
not enough.