browser-use-sdk changedagents-and-tools/tool-use/browser-use-sdk
Nearest release: v2.1.301, published 5 hours before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
## Use a driver from a provider ## Partner integrations ## Customize a driver ### Enable or disable members ### Add before and after hooks ## Implement a driver ### Return results ### Report browser state ### Handle errors ### Start calls while the response streams ### Run without the tool runner ## Advanced example: write your own driver ## Run the toolset safely ### Write a URL policy ### Intercept requests in the driver ### Put egress policy on the container ### Confine uploads and downloads ### Gate consequential members ### Isolate the browser host ### Remote and hosted browsers ## Computer toolset ### Write a desktop driver ### Scale coordinates and size images ### Input checks and results ### Run the computer toolset safely ### Use both toolsets in one request ## Reference ## Limitations ## Next steps
The whole hunk
1357 lines, new pageA whole new page. There's nothing to diff it against, so here is what it says.
---
title: Browser and computer use with the SDK toolsets
url: https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk
description: Run the browser use tool or the computer use tool from the Python or TypeScript SDK. The SDK runs the loop and the checks you configure, and you supply the browser or the desktop.
---
With the browser and computer toolsets in the Python and TypeScript SDKs, you can build an agent that works in a web browser or on a desktop. It can use applications with no programmatic interface. Through the [browser use tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-tool), Claude navigates, reads, and interacts with webpages. Through the [computer use tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/computer-use-tool), Claude sees a desktop in screenshots and uses the mouse and keyboard.
The SDK connects Claude to your browser or desktop. It runs the checks you configure, such as a URL policy for `navigate` or an approval callback. It sends each call that passes to your driver and builds the `tool_result` from what the driver returns. The [tool runner](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner) can run the agentic loop for you.
You supply the driver, which operates the browser or desktop. Start from a [provider's driver](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#use-a-driver-from-a-provider). With one, an agent takes a few lines of code. If none fits, write your own with a browser automation library such as Playwright.
Most of this page is about the browser toolset. [Computer toolset](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#computer-toolset) covers what differs.
Writing your own driver is an advanced case, covered in [Implement a driver](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver). The claude-quickstarts repository also has an [example browser driver](https://github.com/anthropics/claude-quickstarts/tree/main/browser-toolset) in Python and TypeScript. It controls Chromium through the Chrome DevTools Protocol (CDP), and it isn't production code.
<Note>
The browser toolset class is in beta and available in the [Python SDK](https://github.com/anthropics/anthropic-sdk-python/blob/main/browser-toolset.md) and the [TypeScript SDK](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/browser-toolset.md). The computer toolset class is in beta in the same SDKs, which have a [Python computer toolset guide](https://github.com/anthropics/anthropic-sdk-python/blob/main/computer-toolset.md) and a [TypeScript computer toolset guide](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/computer-toolset.md).
</Note>
## Use a driver from a provider
With a driver from a provider, such as one under [Partner integrations](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#partner-integrations), you can build an agent in a few lines of code. The provider in this example is made up. Replace `example_browser_provider` (typescript: `example-browser-provider`) and `ExampleBrowser` with [a real provider's](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#partner-integrations) package and driver class, and set them up as the provider's docs describe.
<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
```python Python
from anthropic import Anthropic
from example_browser_provider import ExampleBrowser # made-up provider
client = Anthropic()
with ExampleBrowser() as browser:
runner = client.beta.messages.tool_runner(
model="claude-opus-5-5",
max_tokens=1024,
tools=[browser],
messages=[
{"role": "user", "content": "Open example.com and tell me its heading."}
],
)
for message in runner:
print(message)
```
```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";
import { ExampleBrowser } from "example-browser-provider"; // made-up provider
const client = new Anthropic();
const browser = new ExampleBrowser();
try {
const runner = client.beta.messages.toolRunner({
model: "claude-opus-5-5",
max_tokens: 1024,
tools: [browser],
messages: [{ role: "user", content: "Open example.com and tell me its heading." }]
});
for await (const message of runner) {
console.log(message);
}
} finally {
await browser.close();
}
```
</CodeGroup>
Pass the driver instance itself as the `tools` entry. The runner never closes the toolset, so one instance can serve several runs. Close it when you're done.
To write your own driver, see [Implement a driver](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver).
## Partner integrations
Browser Use, Browserbase, Daytona, and E2B publish their own drivers for the SDK toolsets.
<CardGroup cols={2}>
<Card id="browser-use" title="Browser Use" image="https://platform.claude.com/docs/images/partner-logos/browser-use.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/browser-use-dark.svg" href="https://docs.browser-use.com/open-source/customize/integrations/toolsets-for-claude">
Browser Use driver for the browser toolset.
</Card>
<Card id="browserbase" title="Browserbase" image="https://platform.claude.com/docs/images/partner-logos/browserbase.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/browserbase-dark.svg" href="https://docs.stagehand.dev/v4/integrations/agent-frameworks/claude-cua-toolset-quickstart">
Browserbase Stagehand driver for the browser toolset.
</Card>
<Card id="daytona" title="Daytona" image="https://platform.claude.com/docs/images/partner-logos/daytona.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/daytona-dark.svg" href="https://www.daytona.io/docs/en/guides/claude/claude-draws-daytona-sandbox/">
Daytona driver for the computer toolset.
</Card>
<Card id="e2-b" title="E2B" image="https://platform.claude.com/docs/images/partner-logos/e2b.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/e2b-dark.svg" href="https://docs.e2b.dev/agents/claude-toolsets">
E2B drivers for the browser and computer toolsets.
</Card>
</CardGroup>
Our partners also publish code on GitHub: the [Browser Use quickstart](https://github.com/browser-use/browser-use/tree/main/examples/integrations/toolsets-for-claude), the [Browserbase toolset](https://github.com/browserbase/claude-cua-toolset), and the [E2B example](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/anthropic-computer-use-orangehrm-js).
<Warning>
Do these two things before you run a driver against anything but a throwaway browser:
* [Write a URL policy](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy). Without one, the SDK checks no URL. For a browser that a provider hosts, also read [Remote and hosted browsers](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#remote-and-hosted-browsers).
* Use a browser profile that isn't signed in to any account whose data or actions you wouldn't hand to Claude. See [Isolate the browser host](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#isolate-the-browser-host).
[Run the toolset safely](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#run-the-toolset-safely) covers the rest.
</Warning>
## Customize a driver
The examples in this section use `MyBrowser`, the driver from the [advanced example](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#quick-start) later on this page.
### Enable or disable members
`configs` takes the per-member settings described under [Configure the toolset](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-tool#configure-the-toolset). List only the members you change:
<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
```python Python
# A MyBrowser that also implements read_console
browser = MyBrowser(
backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
)
```
```typescript TypeScript
// A MyBrowser that also implements read_console
const browser = new MyBrowser(backend, {
configs: { read_console: { enabled: true }, navigate: { enabled: false } }
});
```
</CodeGroup>
The SDK refuses a call to a disabled member before your code runs. Enabling a member your class doesn't implement is a configuration error, unless the class overrides `execute`.
### Add before and after hooks
Override `execute` and call the parent's `execute`. Code before that call runs after the [URL policy](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy), the [file policy](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#confine-uploads-and-downloads), and [`confirm`](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#gate-consequential-members), and it can change the input. The SDK doesn't check the changed input again. Code after the call receives the result, and it can change the result. Raise `ToolError` (throw it in TypeScript) to refuse the call.
Overriding `execute` changes which members Claude is offered. The SDK counts every member as implemented, so Claude is offered every member that's on by default. The `MyBrowser` in the [advanced example](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#quick-start) serves three members, so the following `TracedBrowser` offers Claude members it can't serve. Turn those members off with `configs` before you use it.
<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
```python Python
import time
class TracedBrowser(MyBrowser):
def execute(self, context, name, input):
started = time.monotonic()
result = super().execute(context, name, input)
elapsed_ms = (time.monotonic() - started) * 1000
call_id = context.tool_use.id if context.tool_use else "-"
log.info("%s %s %.0fms", call_id, name, elapsed_ms)
return redact(result) if name == "get_page_text" else result
```
```typescript TypeScript
import type { BetaToolsetCallContext } from "@anthropic-ai/sdk/helpers/beta/toolsets";
import type {
BetaBrowserMemberInput,
BetaBrowserMemberName
} from "@anthropic-ai/sdk/resources/beta";
class TracedBrowser extends MyBrowser {
protected override async execute(
ctx: BetaToolsetCallContext,
name: BetaBrowserMemberName,
input: BetaBrowserMemberInput
) {
const started = performance.now();
const result = await super.execute(ctx, name, input);
log.info(`${ctx.toolUse?.id ?? "-"} ${name} ${Math.round(performance.now() - started)}ms`);
return name === "get_page_text" ? redact(result as string) : result;
}
}
```
</CodeGroup>
## Implement a driver
A driver is your subclass of `BetaAbstractBrowserToolset20260801`. In it, override the members your browser supports. A member you don't implement is sent to the API as disabled. If Claude calls it anyway, the SDK returns an error, and the run continues. Overriding `execute` changes which members are sent as disabled ([Add before and after hooks](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#add-before-and-after-hooks)). Each member receives the call context and the member's input as a typed object, such as `BetaBrowserNavigateInput`. The input types come from `anthropic.types.beta` (typescript: `@anthropic-ai/sdk/resources/beta`). [Member tools](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-tool#member-tools) lists each input's fields.
In TypeScript, write members as methods, not arrow-function fields, because the SDK finds them on the prototype. Spell the `type` member `type_` in TypeScript. In Python, it's `type`.
### Return results
What a member returns determines what Claude reads. A successful result ends with a `browser_state` block built from your state report. An error result carries no block.
| Member | Returns | Claude reads |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `screenshot`, `zoom` | `BetaScreenshotResult` | One image block |
| `navigate` | `BetaBrowserNavigateResult` | `Navigated to {url} — {title} (HTTP {status})` |
| `new_tab`, `switch_tab`, `list_tabs`, `close_tab` | A tab entry (`new_tab`, `switch_tab`), a list of tab entries (`list_tabs`), or nothing (`close_tab`) | The `browser_state` block alone |
| `read_page`, `get_page_text`, `find`, `read_console`, `read_network`, `javascript_exec` | A string | The string, or `(empty)` for an empty string |
| Every other member | Nothing, or one line of text | A short confirmation, such as `Clicked.`, then the returned line in a text block of its own |
### Report browser state
The SDK calls `_browser_state` (typescript: `browserState`) after each call that returns a result, including refused and failed calls. Return every open tab, and what changed since the last report:
* Opened tabs and download events.
* A `BetaNavigationRefused` for each navigation your [request hook](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#intercept-requests-in-the-driver) blocked.
* A `BetaDialogDismissed` for each native dialog your driver dismissed.
Put all of these in `state_changes`. In Python, `BetaNavigationRefused()` and `BetaDialogDismissed(kind=..., message=...)` come from `anthropic.tools.browser`. In TypeScript, they're `{ type: "navigation_refused" }` and `{ type: "dialog_dismissed", kind, message }`.
The last two aren't API state changes. The SDK reports them to Claude as text outside the `browser_state` block: one line for all refused navigations, which doesn't include a URL, and a line for each of the first three dismissed dialogs, then a count of any others.
When any tab is open, exactly one must be active. Every member that takes a `tab_id` must act on the tab it names. The API's limits on the report are listed under [Track tabs with `browser_state`](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-tool#track-tabs-and-page-state).
Claude reads each tab's URL, each download's URL, and the URL that your `navigate` returns, as your driver wrote them. The SDK doesn't parse these URLs. It turns line breaks and other control characters into spaces, trims the ends, and cuts each URL at 4,096 characters. So a `data:` URL's contents, a `file:` URL's path, and a user name or password in a URL all reach Claude.
### Handle errors
| Raised by a member or the SDK | Claude reads | The run |
| ----------------------------- | ------------------------------------------------------------------------------------ | --------- |
| `ToolError` | Its message, as an error result | Continues |
| `ToolsetUsageError` | Nothing | Stops |
| Any other exception | `ClassName: message` in Python or `Error: message` in TypeScript, as an error result | Continues |
The SDK raises `ToolsetUsageError` for a configuration error, a misuse of the SDK during a call, or a call after `close`. It also raises one when `_browser_state` (typescript: `browserState`) raises any exception, even a `ToolError`. The tool runner and `tool_result` (typescript: `toolResult`) don't catch it, so it reaches their caller.
The SDK doesn't hide local paths in a member's error text, in the line an action such as `left_click` returns, in a failed download's error, or in a dismissed dialog's message.
A `ToolError` from your URL policy, file policy, or `confirm` callable reaches Claude as written, so leave local paths out of its text. Catch exceptions in your members, and raise `ToolError` (throw it in TypeScript) with your own text.
### Start calls while the response streams
By default, the tool runner runs a turn's calls after Claude's response ends. With early start, it can start a browser or computer call while the response is still streaming.
To turn it on, pass `stream=True` (typescript: `stream: true`) and `run_tools_eagerly=True` (typescript: `runToolsEagerly: true`) to the tool runner, as the [advanced example](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#quick-start) does. With `stream`, each pass through your loop over the runner gives you a stream instead of a message.
Early start changes when a call can start, not how the toolset runs it:
* **The checks still run first:** The SDK calls your URL policy, your file policy, and `confirm` before a call runs. With early start, it can call `confirm` while the response is still streaming.
* **Calls still run one at a time:** A toolset runs its calls in the order Claude wrote them. A failed call still stops that toolset's later calls in the turn.
* **A call that has started can't be taken back:** If the response is then cut off, for example at `max_tokens`, or your loop stops early, the action still happens, and Claude never reads its result.
### Run without the tool runner
Pass the instance in `tools` (`browser.toJSON()` in TypeScript), and answer each member call with `tool_result` (typescript: `toolResult`). The browser use tool requires you to stop at the first failed call ([Batch actions](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-tool#batch-actions)).
This loop uses `MyBrowser` and the URL policy from the [advanced example](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-sdk#quick-start). After a failed call, it answers the turn's later calls without running them:
<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
```python Python
from anthropic.types.beta import BetaMessageParam, BetaToolResultBlockParam
NOT_EXECUTED = "Not executed: an earlier action in this turn failed."
MAX_TURNS = 10
with MyBrowser(backend, url_policy=url_policy) as browser:
messages: list[BetaMessageParam] = [{"role": "user", "content": "Open example.com"}]
for _ in range(MAX_TURNS):
response = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
tools=[browser],
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
calls = [
block
for block in response.content
if block.type == "tool_use" and block.toolset_name == browser.toolset_name
]
if not calls:
break
results: list[BetaToolResultBlockParam] = []
failed = False
for call in calls:
if failed:
# After a failed call, the rest of the turn is answered, not run.
results.append(
{
"type": "tool_result",
"tool_use_id": call.id,
"toolset_name": call.toolset_name,
"content": NOT_EXECUTED,
"is_error": True,
}
)
continue
result = browser.tool_result(call)
failed = bool(result.get("is_error"))
results.append(result)
messages.append({"role": "user", "content": results})
```
```typescript TypeScript
const NOT_EXECUTED = "Not executed: an earlier action in this turn failed.";
const MAX_TURNS = 10;
const browser = new MyBrowser(backend, { urlPolicy });
try {
const messages: Anthropic.Beta.BetaMessageParam[] = [
{ role: "user", content: "Open example.com" }
];
for (let turn = 0; turn < MAX_TURNS; turn++) {
const response = await client.beta.messages.create({
model: "claude-opus-5-5",
max_tokens: 1024,
tools: [browser.toJSON()],
messages
});
messages.push({ role: "assistant", content: response.content });
const calls = response.content.filter(
Cut at 300 lines. The page has the rest.
No line in this hunk matches that.