Install without a framework
Six steps, from an empty folder to a tool the agent cursor runs, with plain HTML and TypeScript and createWebAgents.
Six steps, from an empty folder to a tool the agent cursor runs, with no framework: plain HTML, TypeScript and
createWebAgents from @webmcp-agents/core. Use this for a classic site, or with Vue or Svelte.
Create a project
Skip this if you already have a page with a bundler. In the prompts, choose Vanilla and TypeScript.
npm create vite@latestpnpm create vite@latestyarn create vite@latestbun create vite@latestnub create vite@latestaube create vite@latestInstall the packages
The agent cursor, then the devtools panel for development. Without React, nothing installs the panel for you, and it
draws itself with React, so React comes along as a development dependency. Browsers without WebMCP need nothing more:
createWebAgents loads a polyfill for them.
npm install @webmcp-agents/corepnpm add @webmcp-agents/coreyarn add @webmcp-agents/corebun add @webmcp-agents/corenub add @webmcp-agents/coreaube add @webmcp-agents/corenpm install -D @webmcp-agents/devtools react react-dompnpm add -D @webmcp-agents/devtools react react-domyarn add -D @webmcp-agents/devtools react react-dombun add -D @webmcp-agents/devtools react react-domnub add -D @webmcp-agents/devtools react react-domaube add -D @webmcp-agents/devtools react react-domAdd the styles
Import the cursor’s stylesheet in your entry file.
import "@webmcp-agents/core/cursor.css";Add the provider
Without a framework there is no provider component: createWebAgents is the function <WebAgentsProvider> wraps. It
installs the cursor and registers your tools, which you write in the next step; in a browser without WebMCP it loads a
polyfill first, by itself. In development the same file loads the devtools: <WebAgentsProvider> installs them
itself, but createWebAgents cannot (the devtools are built on core), so this page keeps that one block.
import { createWebAgents } from "@webmcp-agents/core";
import "@webmcp-agents/core/cursor.css";
import { saveNote, tools } from "./tools";
createWebAgents({ tools });
// Development only: the devtools panel, wired to the cursor above.
// <WebAgentsProvider> installs it itself; createWebAgents cannot,
// since the devtools are built on this package.
if (import.meta.env.DEV) {
const { installWebAgentsDevtools } = await import(
"@webmcp-agents/devtools"
);
installWebAgentsDevtools();
}
// A person submits this.
const form = document.querySelector("form")!;
form.addEventListener("submit", (event) => {
event.preventDefault();
saveNote(String(new FormData(form).get("text")));
form.reset();
});Add your first tool
The action is one function with two callers: a person submits the form, an agent calls the tool. The page needs
nothing for the cursor: it finds the button by its name and the field by its name.
import { defineWebTool } from "@webmcp-agents/core";
const notes: string[] = [];
// One function, two callers.
export function saveNote(text: string) {
notes.push(text);
document.querySelector("#notes")!.textContent = notes.join(" · ");
return `Saved: ${text}`;
}
// An agent calls this.
const saveNoteTool = defineWebTool({
id: "save_note",
title: "Save note",
description:
"Save a note. Use when the user asks you to write something down.",
inputSchema: {
type: "object",
properties: {
text: { type: "string", description: "The note, e.g. Buy milk" },
},
required: ["text"],
},
execute: ({ text }: { text: string }) => saveNote(text),
});
export const tools = { saveNote: saveNoteTool };<form>
<label>Note <input name="text" required /></label>
<button type="submit">Save note</button>
</form>
<p id="notes"></p>
<script type="module" src="/src/main.ts"></script>Run it
Start the page 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 save_note, type a note and press Run. The
cursor types it into Note, presses Save note, and the tool answers “Saved: …”.
Next
The pages under Build show React code. Everything they teach about a tool applies here unchanged, because
useWebTool takes the same definition as defineWebTool.