Skip to content
WebMCP Agents
Esc
↑↓navigate↵open⌘Jpreview
On this page

Define an agent

One definition for what the assistant knows, which tools it may use and which helpers it hands work to. The page and your server read the same file.

Your tools say what can be done on the page. An agent says how: the order the tools go in, what to ask the person, what to avoid, and which tools are its own. defineWebAgent is the one place you write that, the way defineWebTool is the one place you write a tool. It is plain data, so the chat in the browser and your /api/chat route read the same file.

A complete example

Two agents: shopper on every page, and orders, a helper it hands questions about past orders to.

import { defineWebAgent } from "@webmcp-agents/chat/agent";

import { tools } from "./tools";

// Plain data: no DOM, no secrets. The page and your /api/chat route
// both import this file.

export const orders = defineWebAgent({
  id: "orders",
  description:
    "Answers questions about past orders: what was bought, and " +
    "where an order is now.",
  instructions: [
    "list_orders and track_order are on the orders page: " +
      "go_to orders first.",
    "Answer with the order's id, its status and when it arrives.",
  ],
  tools: [tools.goTo, tools.listOrders, tools.trackOrder],

  // A faster model is enough for this narrow job.
  model: "anthropic/claude-sonnet-5-5",
});

export const shopper = defineWebAgent({
  id: "shopper",
  instructions: [
    "You help people shop at Field Store.",
    "Read the cart with get_cart before you change it.",
    "add_to_cart is on a product's page only.",
    "Hand questions about past orders to the orders agent.",
  ],
  tools: [
    tools.getTime,
    tools.getPage,
    tools.goTo,
    tools.getCart,

    // A tool a component owns (useWebTool) goes in by its id.
    "add_to_cart",
    "place_order",
  ],
  agents: [orders],
});

// What your /api/chat route serves: the top-level agents. A
// sub-agent is found through its parent.
export const agents = [shopper];
import { defineWebAgent } from "@webmcp-agents/chat/agent";

import { tools } from "./tools";

// Plain data: no DOM, no secrets. The page and your /api/chat route
// both import this file.

export const orders = defineWebAgent({
  id: "orders",
  description:
    "Answers questions about past orders: what was bought, and " +
    "where an order is now.",
  instructions: [
    "list_orders and track_order are on the orders page: " +
      "go_to orders first.",
    "Answer with the order's id, its status and when it arrives.",
  ],
  tools: [tools.goTo, tools.listOrders, tools.trackOrder],

  // A faster model is enough for this narrow job.
  model: "anthropic/claude-sonnet-5-5",
});

export const shopper = defineWebAgent({
  id: "shopper",
  instructions: [
    "You help people shop at Field Store.",
    "Read the cart with get_cart before you change it.",
    "add_to_cart is on a product's page only.",
    "Hand questions about past orders to the orders agent.",
  ],
  tools: [
    tools.getTime,
    tools.getPage,
    tools.goTo,
    tools.getCart,

    // A tool a component owns (useWebTool) goes in by its id.
    "add_to_cart",
    "place_order",
  ],
  agents: [orders],
});

// What your /api/chat route serves: the top-level agents. A
// sub-agent is found through its parent.
export const agents = [shopper];
import { defineWebAgent } from "@webmcp-agents/chat/agent";

import { tools } from "./tools";

// Plain data: no DOM, no secrets. The page and your /api/chat route
// both import this file.

export const orders = defineWebAgent({
  id: "orders",
  description:
    "Answers questions about past orders: what was bought, and " +
    "where an order is now.",
  instructions: [
    "list_orders and track_order are on the orders page: " +
      "go_to orders first.",
    "Answer with the order's id, its status and when it arrives.",
  ],
  tools: [tools.goTo, tools.listOrders, tools.trackOrder],

  // A faster model is enough for this narrow job.
  model: "anthropic/claude-sonnet-5-5",
});

export const shopper = defineWebAgent({
  id: "shopper",
  instructions: [
    "You help people shop at Field Store.",
    "Read the cart with get_cart before you change it.",
    "add_to_cart is on a product's page only.",
    "Hand questions about past orders to the orders agent.",
  ],
  tools: [
    tools.getTime,
    tools.getPage,
    tools.goTo,
    tools.getCart,

    // A tool a component owns (useWebTool) goes in by its id.
    "add_to_cart",
    "place_order",
  ],
  agents: [orders],
});

// What your /api/chat route serves: the top-level agents. A
// sub-agent is found through its parent.
export const agents = [shopper];
import { defineWebAgent } from "@webmcp-agents/chat/agent";

import { tools } from "./tools";

// Plain data: no DOM, no secrets. The page and your /api/chat route
// both import this file.

export const orders = defineWebAgent({
  id: "orders",
  description:
    "Answers questions about past orders: what was bought, and " +
    "where an order is now.",
  instructions: [
    "list_orders and track_order are on the orders page: " +
      "go_to orders first.",
    "Answer with the order's id, its status and when it arrives.",
  ],
  tools: [tools.goTo, tools.listOrders, tools.trackOrder],

  // A faster model is enough for this narrow job.
  model: "anthropic/claude-sonnet-5-5",
});

export const shopper = defineWebAgent({
  id: "shopper",
  instructions: [
    "You help people shop at Field Store.",
    "Read the cart with get_cart before you change it.",
    "add_to_cart is on a product's page only.",
    "Hand questions about past orders to the orders agent.",
  ],
  tools: [
    tools.getTime,
    tools.getPage,
    tools.goTo,
    tools.getCart,

    // A tool a component owns (useWebTool) goes in by its id.
    "add_to_cart",
    "place_order",
  ],
  agents: [orders],
});

// What your /api/chat route serves: the top-level agents. A
// sub-agent is found through its parent.
export const agents = [shopper];

Import defineWebAgent from @webmcp-agents/chat/agent: that entry has no React and no DOM, so a server file can import it. Install the package if you have not yet:

npm install @webmcp-agents/chat
pnpm add @webmcp-agents/chat
yarn add @webmcp-agents/chat
bun add @webmcp-agents/chat
nub add @webmcp-agents/chat
aube add @webmcp-agents/chat

Instructions

instructions is a string or a list of lines, added to the system prompt under “About this site”. Write what the model cannot read from the tools: the order of a task (“list_orders before track_order”), which page a tool is on, what to ask the person, and what never to do. Tool descriptions say what each tool does; instructions say how they fit.

The browser sends only the agent’s id. The server reads the instructions from its own copy of this file, so page text never writes your system prompt.

The tools it may use

Left out, tools is every tool on the page. A list narrows it, and takes both kinds of tool, mixed:

  • A catalog tool, by reference: tools.getCart. The object stands for its id.
  • A component’s tool, by its id: "add_to_cart". A tool written with useWebTool has no object to import.

The list is an allow-list on both sides: the browser offers the model only these tools, and the server refuses a call to any other. Listing a catalog tool by reference makes this file import your catalog, which is why catalog files use defineWebTool from @webmcp-agents/core and import no React.

A function over the page’s tool descriptors works too, for rules a list cannot say:

tools: (tools) => tools.filter((tool) => tool.name !== "place_order"),

Sub-agents

agents gives an agent helpers. Each one becomes a tool named delegate_to_<id> (here delegate_to_orders) that takes a task. When the parent calls it, the chat runs the helper’s own loop, with its own instructions, tools and model, and hands its final answer back as the tool’s result.

  • Write a description on a sub-agent. It becomes the delegate tool’s description, which is how the parent knows when to call it.
  • A sub-agent sees only the task, not the parent’s conversation, so the parent has to hand over a complete one.
  • It still works the page. The helper’s tools run in the browser under the cursor, and anything confirm-gated still waits for the person’s click. In the chat, the delegate call shows as a card with the helper’s own calls inside it.

Register only the top-level agents on the server (the agents export above): a sub-agent is found through its parent. Ids must be unique across the whole tree.

A model per agent

model names the agent’s model as "<provider>/<model>". Without it, the agent uses the server’s default (WEB_AGENTS_MODEL, else anthropic/claude-opus-5-5). Give a narrow helper a faster model, as orders does.

It is a string, never a key: the server resolves it with the provider’s key from its own environment. A provider other than the default needs its key and its SDK on the server (Add the chat).

Use it

The definition is the same wherever the agent runs. The chat takes it as a prop (<AgentChat agent={shopper} />, the next page). To run it with no chat on the page, pick the tab that fits:

In a component, with live status:

const agent = useWebAgent(shopper);

<Button onClick={() => agent.invoke("Where is my last order?")}>
  {agent.status === "running" ? "Working…" : "Track my last order"}
</Button>

Anywhere else, on the agent itself:

const { text } = await shopper.invoke("Where is my last order?");

Call an agent has both in full.

Try it

An agent needs a model to run, and the next page adds one: the chat and its /api/chat route. Then ask the chat “Where is my last order?”. shopper calls delegate_to_orders, and the card for it shows the helper going to /orders, calling list_orders and then track_order.

Options you’ll use most

Option What it is for
id The agent’s name, 1–64 of a-z A-Z 0-9 _ . -. The browser sends it; the server looks it up.
instructions How the tools fit together: a string or lines.
tools The tools it may use: catalog tools and ids, mixed, or a function. Default: every tool on the page.
agents Helpers it hands work to, each as a delegate_to_<id> tool.
description For a sub-agent: what it does, in one sentence.
model "<provider>/<model>". Default: the server’s.
maxSteps Model steps per message for this agent.

Every option: defineWebAgent and useWebAgent.

Was this page helpful?