---
description: "Turns a WebAgent into an AI SDK ToolLoopAgent for one request: the model (whose key lives on the server), the page's live tools and the agent's instructions exist together only there."
---

# `toToolLoopAgent`

Turns a `WebAgent` into an AI SDK `ToolLoopAgent` for one request: the model (whose key lives on the server), the page's live tools and the agent's instructions exist together only there. [`createChatHandler`](/docs/reference/chat/create-chat-handler) does this for every POST; reach for these functions to run a `WebAgent` inside your own AI SDK code, or to inspect the settings the handler builds.

## Import

```ts
import {
  toToolLoopAgent,
  toToolLoopAgentSettings,
  toToolSet,
} from "@webmcp-agents/chat/server";
import type {
  ToToolLoopAgentOptions,
  WebAgentSettings,
} from "@webmcp-agents/chat/server";
```

## Example

Build the agent for the shopper with the tools a page sent, and run it once. The model is one step per request: a tool call comes back for the browser to run.

```ts title="run-agent.ts"
import {
  resolveModel,
  toToolLoopAgent,
} from "@webmcp-agents/chat/server";
import type { ToolDescriptor } from "@webmcp-agents/chat";

import { shopper } from "@/agents";

// What a page sent with its request: the tools it has right now.
const pageTools: ToolDescriptor[] = [
  {
    name: "get_cart",
    description: "Read the cart: its lines and total.",
    inputSchema: { type: "object", properties: {} },
  },
];

const { model } = await resolveModel(
  "anthropic/claude-opus-5-5",
  process.env,
);

// An AI SDK ToolLoopAgent: the shopper's instructions, its tool
// subset, and one model step (the browser runs the tools).
const loop = toToolLoopAgent(shopper, {
  model,
  tools: pageTools,
  path: "/cart",
});

const result = await loop.generate({ prompt: "What is in my cart?" });
console.log(result.text);
```

To tune the agent the handler builds instead (telemetry, `prepareStep`, middleware), pass [`prepareAgent`](/docs/reference/chat/create-chat-handler#options) to `createChatHandler`.

## Options

`toToolLoopAgent(agent, options)` and `toToolLoopAgentSettings(agent, options)` take the agent (or `undefined` for the plain assistant) and a `ToToolLoopAgentOptions`.

| Name | Type | Default | Description |
|---|---|---|---|
| `agent` | `WebAgent \| undefined` | required | The agent from [`defineWebAgent`](/docs/reference/chat/define-web-agent), or `undefined` for the plain assistant: the base prompt, every page tool, no delegation note. |
| `model` | `LanguageModel` | required | The resolved model: see [`resolveModel`](/docs/reference/chat/resolve-model). |
| `tools` | `readonly ToolDescriptor[]` | required | The page's tools right now, as the request carried them. [`selectTools(agent, tools)`](/docs/reference/chat/define-web-agent#helpers) is applied: the agent's filter, plus its delegate tools. |
| `path` | `string` | not set | The page's same-origin path, for the "The user is on the page …" line. |
| `base` | `string` | `DEFAULT_SYSTEM_PROMPT` | The base system prompt: see [`buildSystemPrompt`](/docs/reference/chat/system-prompt). |
| `extraInstructions` | `string` | not set | Appended after the instructions, for example a tool-budget note (the handler passes one once the step budget is spent). |
| `maxOutputTokens` | `number` | not set | Output token cap per step, passed through. |
| `providerOptions` | AI SDK `providerOptions` | not set | Passed through: see [`providerOptionsFor`](/docs/reference/chat/resolve-model). |
| `stopWhen` | AI SDK `stopWhen` | `stepCountIs(1)` | When the loop stops. One model step per request, because the browser runs the tools in between. |
| `toolSet` | `ToolSet` | not set | Replaces the tool set entirely (the handler passes `{}` once the step budget is spent). |

## Returns

- `toToolLoopAgentSettings` returns `WebAgentSettings`: `{ id: agent?.id, model, instructions, tools, stopWhen, maxOutputTokens, providerOptions }`. `instructions` is `buildSystemPrompt(base, path, agent?.instructions, agent has sub-agents)` plus `extraInstructions`; `tools` is `toToolSet(selectTools(agent, tools))`.
- `toToolLoopAgent` returns `new ToolLoopAgent(settings)`.
- `toToolSet(tools)` returns the page's tools as an AI SDK tool set **without `execute`** (the browser runs them), with control characters stripped from descriptions and `$schema` and `$id` removed from schemas.

## Notes

- In the normal flow nobody constructs the `ToolLoopAgent` themselves: `defineWebAgent` is the definition and `createChatHandler` the runtime. This is the seam between the two.
- Because tools have no `execute`, a call ends the step; the browser runs the tool and posts the result with the next request.
- `ToolDescriptor` is exported from `@webmcp-agents/chat`, not from `/server`.

## See also

- [Define an agent](/docs/build/define-an-agent), the page that teaches agents
- [`defineWebAgent`](/docs/reference/chat/define-web-agent)
- [`createChatHandler`](/docs/reference/chat/create-chat-handler) and its `prepareAgent` option
- [`buildSystemPrompt`](/docs/reference/chat/system-prompt)
