Install in a TanStack Start app
Six steps, from an empty folder to a tool the agent cursor runs, in a TanStack Start app.
Six steps, from an empty folder to a tool the agent cursor runs. No example app in the repository runs TanStack Start: the code on this page is type-checked against the packages, not run.
Create a project
Skip this if you already have a TanStack Start app. The TanStack CLI creates one; its defaults are fine.
npx @tanstack/cli@latest createpnpm dlx @tanstack/cli@latest createyarn dlx @tanstack/cli@latest createbunx @tanstack/cli@latest createnubx @tanstack/cli@latest createaube dlx @tanstack/cli@latest createInstall 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 as a URL in the root route and add it to the document’s links, next to your own.
import cursorCss from "@webmcp-agents/core/cursor.css?url";
import appCss from "../styles.css?url";
// In head():
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "stylesheet", href: cursorCss },
],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. Your page renders inside it right away, on the
server too.
import { WebAgentsProvider } from "@webmcp-agents/react";
import type { ReactNode } from "react";
export function Providers({ children }: { children: ReactNode }) {
return <WebAgentsProvider>{children}</WebAgentsProvider>;
}Wrap the page in it in the root route. The highlighted lines are the ones you add.
import {
createRootRoute,
HeadContent,
Scripts,
} from "@tanstack/react-router";
import cursorCss from "@webmcp-agents/core/cursor.css?url";
import type { ReactNode } from "react";
import { Providers } from "../components/providers";
import appCss from "../styles.css?url";
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: "utf-8" },
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
],
links: [
{ rel: "stylesheet", href: appCss },
{ rel: "stylesheet", href: cursorCss },
],
}),
shellComponent: RootDocument,
});
function RootDocument({ children }: { children: ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
<Providers>{children}</Providers>
<Scripts />
</body>
</html>
);
}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>
);
}Render it in a route.
import { createFileRoute } from "@tanstack/react-router";
import { ClearCart } from "../components/clear-cart";
export const Route = createFileRoute("/")({ component: Home });
function Home() {
return (
<main>
<ClearCart />
</main>
);
}Run it
Start the app 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.