通过 SDK 工具集使用浏览器使用工具
从 Python 或 TypeScript SDK 运行浏览器使用工具。SDK 负责运行循环以及您配置的 URL、文件和审批检查,浏览器则由您提供。
Python 和 TypeScript SDK 包含一个用于浏览器使用工具的类。您可以继承该类,并基于您自己的浏览器自动化,为每个成员工具(例如 navigate 或 left_click)编写一个方法。SDK 会路由每个调用、检查 URL 和文件路径、询问您的审批回调,并构建每个 tool_result。
SDK 不包含浏览器、现成的 "driver"(驱动程序)或 "denylist"(拒绝列表)。适用于 Playwright 和 Chrome DevTools Protocol 的 Python 和 TypeScript 示例驱动程序位于 claude-quickstarts 仓库的 browser-toolset 文件夹中。
快速入门
驱动程序就是您编写的 BetaAbstractBrowserToolset20260801 子类。下面这个驱动程序实现了 navigate、screenshot 和 left_click,以及 _browser_state(在 TypeScript 中为 browserState),即每个驱动程序都需要的状态报告。在示例中,backend 代表您自己对浏览器自动化库(例如 Playwright)的封装。
from anthropic import Anthropic
from anthropic.tools.browser import (
BetaAbstractBrowserToolset20260801,
BetaBrowserNavigateResult,
BetaBrowserScreenshotResult,
BrowserState,
ToolsetCallContext,
)
from anthropic.types.beta import (
BetaBrowserLeftClickInput,
BetaBrowserNavigateInput,
BetaBrowserScreenshotInput,
BetaBrowserStateTabEntryParam,
)
class MyBrowser(BetaAbstractBrowserToolset20260801):
def __init__(self, backend, **options):
super().__init__(**options)
self.backend = backend
def _browser_state(self, context: ToolsetCallContext) -> BrowserState:
return BrowserState(
tabs=[
BetaBrowserStateTabEntryParam(
tab_id=tab.id,
title=tab.title,
url=tab.url,
active=tab.id == self.backend.active,
)
for tab in self.backend.tabs()
],
state_changes=self.backend.drain_changes(),
)
def navigate(
self, context: ToolsetCallContext, input: BetaBrowserNavigateInput
) -> BetaBrowserNavigateResult:
# input.url 是已通过 URL 策略检查的 URL,或为 "back"、"forward"、
# 或 "reload"。当 Claude 省略协议时,SDK 会自动添加 https://。
page = self.backend.goto(input.url, input.tab_id)
return BetaBrowserNavigateResult(
url=page.url, status=page.status, title=page.title
)
def screenshot(
self, context: ToolsetCallContext, input: BetaBrowserScreenshotInput
) -> BetaBrowserScreenshotResult:
data = self.backend.png_base64(input.tab_id)
return BetaBrowserScreenshotResult(data=data, media_type="image/png")
def left_click(
self, context: ToolsetCallContext, input: BetaBrowserLeftClickInput
) -> None:
# 无需返回内容:Claude 读取到的是 "Clicked."
self.backend.click(input.target, input.tab_id)
def close(self) -> None:
super().close() # first, so no call is still using the browser when it closes
if not self.backend.closed:
self.backend.close()
client = Anthropic()
with MyBrowser(backend, allowed_domains=["example.com", "iana.org"]) 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 the page heading.",
}
],
)
for message in runner:
print(message)将驱动程序实例本身作为 tools 条目传入。您未实现的成员会以禁用状态发送给 API。如果 Claude 仍然调用它,SDK 会返回错误,运行将继续。重写 execute 会改变哪些成员以禁用状态发送(添加前置和后置钩子)。运行器从不关闭工具集,因此一个实例可以服务多次运行。使用完毕后请将其关闭。
自定义驱动程序
启用或禁用成员
configs 接受配置工具集中描述的各成员设置。只需列出您要更改的成员:
# 同时实现了 read_console 的 MyBrowser
browser = MyBrowser(
backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
)SDK 会在您的代码运行之前拒绝对已禁用成员的调用。启用您的类未实现的成员属于配置错误,除非该类重写了 execute。
添加前置和后置钩子
重写 execute 并调用父类的 execute。该调用之前的代码在 URL 检查(设置 URL 策略)和 confirm(对有重大影响的成员进行把关)之后运行,并且可以更改输入。SDK 不会再次检查更改后的输入。该调用之后的代码会接收结果,并且可以更改结果。引发 ToolError(在 TypeScript 中为抛出)即可拒绝该调用。
重写 execute 会改变向 Claude 提供哪些成员。SDK 会将每个成员都视为已实现,因此 Claude 会获得所有默认开启的成员。快速入门中的 MyBrowser 只实现了三个成员,因此下面的 TracedBrowser 会向 Claude 提供它无法处理的成员。在使用它之前,请通过 configs 关闭这些成员。
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实现驱动程序
重写您的浏览器所支持的成员。每个成员都会接收调用上下文,以及以类型化对象(例如 BetaBrowserNavigateInput)形式提供的该成员输入。输入类型来自 anthropic.types.beta(在 TypeScript 中为 @anthropic-ai/sdk/resources/beta)。成员工具列出了每个输入的字段。
在 TypeScript 中,请将成员编写为方法,而不是箭头函数字段,因为 SDK 是在原型上查找它们的。在 TypeScript 中,type 成员应写作 type_。在 Python 中则为 type。
返回结果
成员返回的内容决定了 Claude 读取的内容。成功的结果以根据您的状态报告构建的 browser_state 块结尾。错误结果不带该块。
| 成员 | 返回 | Claude 读取的内容 |
|---|---|---|
screenshot、zoom | BetaBrowserScreenshotResult | 一个图像块 |
navigate | BetaBrowserNavigateResult | Navigated to {url} — {title} (HTTP {status}) |
new_tab、switch_tab、list_tabs、close_tab | 一个标签页条目(new_tab、switch_tab)、一个标签页条目列表(list_tabs),或不返回任何内容(close_tab) | 仅 browser_state 块 |
read_page、get_page_text、find、read_console、read_network、javascript_exec | 一个字符串 | 该字符串 |
| 其他所有成员 | 不返回任何内容,或一行文本 | 一条简短的确认信息(例如 Clicked.),然后是位于单独文本块中的返回行 |
报告浏览器状态
SDK 会在每次调用之后调用 _browser_state(在 TypeScript 中为 browserState 选项),包括被拒绝和失败的调用。请返回每个打开的标签页,以及自上次报告以来发生的变化:
- 已打开的标签页和下载事件。
- 针对您的请求钩子阻止的每次导航,返回一个
NavigationRefused。 - 针对您的驱动程序关闭的每个原生对话框,返回一个
DialogDismissed。
将所有这些内容放入 state_changes 中。在 Python 中,NavigationRefused(url=...) 和 DialogDismissed(kind=..., message=...) 来自 anthropic.tools.browser。在 TypeScript 中,它们是 { type: "navigation_refused", url } 和 { type: "dialog_dismissed", kind, message }。
后两者不是 API 状态变更。SDK 会将它们作为 browser_state 块之外的文本报告给 Claude:所有被拒绝的导航合并为一行(不指明 URL),前三个被关闭的对话框各占一行,然后是其余对话框的数量。
当有任何标签页打开时,必须恰好有一个处于活动状态。每个接受 tab_id 的成员都必须作用于其指定的标签页。SDK 使用该报告来确定结果来自哪个页面。API 对该报告的限制列在使用 browser_state 跟踪标签页中。
处理错误
| 由成员或 SDK 引发 | Claude 读取的内容 | 运行 |
|---|---|---|
ToolError | 其消息,作为错误结果 | 继续 |
| 任何其他异常 | 其文本,作为错误结果 | 继续 |
ToolsetUsageError,针对配置错误、调用期间对 SDK 的误用,或在 close 之后的调用 | 无 | 停止 |
在 Claude 读取成员的错误文本、left_click 等操作返回的行、失败下载的错误或被关闭对话框的消息之前,SDK 会将策略拒绝的每个 URL 替换为 (blocked),并将文件策略未公开的每个本地路径替换为 (path hidden)。该检查可能会遗漏某些 URL 和路径。来自您的 URL 策略、文件策略或 confirm 可调用对象的 ToolError 会按原样传达给 Claude,因此请勿在其文本中包含被拒绝的 URL 和本地路径。请在您的成员中捕获异常,并使用您自己的文本引发 ToolError(在 TypeScript 中为抛出)。
不使用工具运行器运行
在 tools 中传入实例(在 TypeScript 中为 browser.toJSON()),并使用 tool_result(在 TypeScript 中为 toolResult)响应每个成员调用。浏览器使用工具要求您在第一个失败的调用处停止(批量操作)。在调用失败后,此循环会响应该轮次中的后续调用,但不运行它们:
from anthropic.types.beta import BetaToolResultBlockParam
NOT_EXECUTED = "Not executed: an earlier action in this turn failed."
MAX_TURNS = 10
with MyBrowser(backend, allowed_domains=["example.com"]) as browser:
messages = [{"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:
# 某次调用失败后,本轮中其余的调用只会得到应答,而不会实际执行。
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})对每个被跳过调用的响应都带有 is_error、该调用的 toolset_name,以及批量操作所要求的确切文本。工具运行器会发送相同的响应。
安全地运行工具集
Claude 的下一步操作取决于它读取的页面。页面或注入页面的文本可能会尝试访问内部服务或从主机上提取文件,也可能尝试触发具有实际影响的操作。在针对一次性浏览器以外的任何浏览器运行驱动程序之前,请采取以下六个步骤:
- 设置 URL 策略:
allowed_domains、blocked_domains或您自己的url_policy。 - 在驱动程序中拦截请求,并向工具集请求裁决。
- 在容器上设置出站策略,让网络阻止驱动程序无法看到的内容。
- 限制上传和下载,或保持上传关闭。
- 使用
confirm对有重大影响的成员进行把关。 - 为每个会话在专用容器或虚拟机中隔离浏览器主机。
SDK 会按照您的配置强制执行第 1、4 和 5 步。第 2、3 和 6 步取决于您的驱动程序和部署。安全注意事项中的预防措施同样适用。
设置 URL 策略
将 allowed_domains(在 TypeScript 中为 allowedDomains)设置为任务所需的站点。任一列表中的条目可以是域名(同时涵盖其子域名)、IP 地址或 CIDR 网络。设置 allowed_domains 后,工具集会拒绝所有其他主机。
browser = MyBrowser(backend, allowed_domains=["example.com", "iana.org"])如果任务需要访问开放网络,请改为设置 blocked_domains(blockedDomains)。这些列表在比较主机名时不会对其进行解析。拒绝列表条目 127.0.0.0/8 不会阻止 localhost,因此除了网络之外,还要列出主机名。当您同时设置两个列表时,以 blocked_domains 为准。
拒绝列表无法捕获解析为私有地址的公共名称。因此,在使用 blocked_domains 时,阻止此类名称访问私有地址的是容器出站策略。
# 列出浏览器可以访问但 Claude 不得访问的主机和网络:
# IPv4 和 IPv6 中的环回、链路本地和私有地址范围、0.0.0.0/8、您的
# 云平台元数据地址和名称(例如 metadata.google.internal)、
# localhost,以及您的内部主机名。
browser = MyBrowser(backend, blocked_domains=internal_networks)url_policy(urlPolicy)会取代这两个列表,将其与任一列表一起传入属于配置错误。您的策略不返回任何内容即表示允许某个 URL,引发(抛出)ToolError 则表示拒绝该 URL。要保留默认规则,请使用 default_url_policy(defaultURLPolicy)构建默认策略,并在您自己的策略中首先调用它:
from urllib.parse import urlsplit
from anthropic.tools.browser import ToolError, URLContext, default_url_policy
listed = default_url_policy(allowed_domains=["example.com"])
def url_policy(context: URLContext, url: str) -> None:
listed(context, url) # the default rules first
if context.phase == "request" and urlsplit(url).scheme != "https":
raise ToolError("Only https navigation is allowed.")
browser = MyBrowser(backend, url_policy=url_policy)该策略会在您的代码之前对每个 navigate URL 运行,也会对结果报告的 URL 以及状态报告中的每个标签页和下载 URL 运行。在结果和状态报告中,它会跳过不指向远程主机的地址:空标签页、about:blank、浏览器的 chrome-error: 页面以及 data: 文档。
在任何策略下,工具集都会拒绝 navigate 到 http 或 https 以外的协议(about:blank 除外)。url_policy="allow_all"(urlPolicy: "allow_all")会关闭策略,但不会关闭此协议规则。
传入 url_policy=None(urlPolicy: null)会使构造函数引发配置错误,因此从您的配置中读取到的 None(null)无法关闭检查。在 TypeScript 中,undefined 会使该选项保持未设置状态,与不传入该选项相同。
当页面落在被拒绝的地址上时,Claude 会读到其内容已被隐去,并且该标签页会被列为 (blocked)。在该标签页重新位于允许的地址之前,工具集会拒绝对其进行的调用。例外情况是 navigate(但不包括 "reload")、new_tab、list_tabs、switch_tab 和 close_tab。
在驱动程序中拦截请求
URL 策略只判断工具集能看到的地址:导航、结果和状态报告。它看不到子资源、fetch() 调用、WebSocket,也看不到主机名解析到的位置。
工具集只有在调用结束时,才会在调用的结果或状态报告中检测到新地址。因此,在此之前,调用仍可能作用于被拒绝的页面,而 SDK 最多只能隐去该调用的结果。除非您的驱动程序判断每个请求(包括重定向跳转),否则重定向到被拒绝地址的页面仍会加载。
在驱动程序的 "request hook"(请求钩子,即您的自动化库在每个请求之前调用的函数)中,调用 check_url(checkURL)。它会应用工具集自身的策略,因此您无需重复编写规则:
from anthropic.tools.browser import URLContext
class MyBrowser(BetaAbstractBrowserToolset20260801):
...
# 通过 context.route("**/*", self._guard) 注册到 Playwright 浏览器上下文
def _guard(self, route):
url_context = URLContext(
member="navigate", phase="request", tab_id=self.backend.active
)
if not self.check_url(url_context, route.request.url).allowed:
return route.abort("blockedbyclient")
route.continue_()check_url 应用与 navigate 相同的协议规则和策略。像这样的请求钩子看不到 WebSocket 握手、service worker 请求或重定向跳转。请阻止 service worker。使用能够看到 WebSocket 握手和重定向跳转的钩子来判断它们。
在请求阶段,check_url 只允许 http、https 和 about:blank URL。因此,对于 WebSocket,请在调用它之前将 ws:// 改为 http://,将 wss:// 改为 https://。
在容器上设置出站策略
拦截无法看到浏览器发出的每个请求,而 URL 策略看不到名称解析到的位置。由容器网络强制执行的 "egress policy"(出站策略)可以同时覆盖这两种情况:
- 阻止 IPv4 和 IPv6 中的环回、链路本地和私有地址范围,以及
0.0.0.0/8。这包括云元数据地址169.254.169.254。 - 只允许到任务所需主机的出站连接。如果您的规则匹配 IP 地址,请在容器启动时解析您允许的主机名。
- 只允许向容器的解析器发送 DNS 请求。
- 如果您的驱动程序通过本地 DevTools 端口访问浏览器,请只允许该端口上的环回连接。针对所有环回地址的规则会将每个本地服务都暴露给页面。
限制上传和下载
file_upload 默认处于关闭状态。如果没有 file_policy,SDK 会拒绝每个指定路径或文档 ID 的上传。要启用上传,请传入一个 LocalFilePolicy(在 TypeScript 中为 NodeFilePolicy),并指定一个只包含任务文件的上传目录:
from anthropic.tools.browser import LocalFilePolicy
# 同时实现了 file_upload 的 MyBrowser
browser = MyBrowser(
backend,
configs={"file_upload": {"enabled": True}},
confirm=make_confirm(), # required for file_upload; see Gate consequential members
file_policy=LocalFilePolicy(
upload_roots=["/task/uploads"],
download_dir="/task/downloads",
expose_download_paths=False,
),
)SDK 会解析每个上传路径(跟随符号链接),并拒绝上传根目录之外的任何路径。文件策略会拒绝位于上传根目录内的下载目录。只有当 expose_download_paths(exposeDownloadPaths)为 true 且文件位于下载目录内时,下载的路径才会传达给 Claude。
内置的路径检查会在运行 SDK 的进程所在的文件系统上解析路径。它们只保护共享该文件系统的浏览器。对于远程浏览器,请改为遵循远程和托管浏览器中的说明。
请按以下方式设置下载:
- 自行创建模式为
0700的下载目录,并以noexec,nosuid,nodev方式挂载。 - 确保 Claude 可以调用的其他工具(例如 shell 或文件工具)无法访问该目录。
- 在
download_failed状态变更中,将error写为固定短语。异常的文本可能包含路径或 URL。 - 在有人做出决定之前,不要将下载的文件读入对话,也不要运行它。
对有重大影响的成员进行把关
javascript_exec 和 file_upload 默认处于关闭状态。如果您在没有 confirm 可调用对象的情况下启用其中任何一个,构造函数会引发配置错误。有了 confirm 可调用对象,SDK 会在每个即将运行的调用之前调用它。如果没有,则不会进行任何询问。
返回 True 以运行该调用,或返回 False 以拒绝它(在 TypeScript 中为 true 和 false)。当您的可调用对象询问某人时,请向其展示成员、页面的 URL 以及调用的输入。首先,请对输入中可打印 ASCII 范围之外的每个字符进行转义,因为输入可能携带来自页面的文本。
此示例通过您自己的 ask_user 函数(在 TypeScript 中为 askUser)询问这两个受把关的成员,并批准其余成员:
import json
from collections.abc import Callable
from anthropic.tools.browser import ConfirmContext
GATED = {"javascript_exec", "file_upload"}
def shown(context: ConfirmContext) -> str:
"""The call's input as JSON, with every character outside printable
ASCII escaped."""
return json.dumps(context.input.to_dict(), ensure_ascii=True, indent=2)
def make_confirm() -> Callable[[ConfirmContext], bool]:
granted: set[tuple[str, str, str]] = set()
def confirm(context: ConfirmContext) -> bool:
name = context.member
if name not in GATED:
return True
detail = shown(context)
page = context.tab_url
origin = context.origin
if page is None or origin is None or origin.startswith("chrome-error:"):
# 无来源或为错误页面:每次都询问。
return ask_user(
f"Allow {name} on {page or 'a page with no origin'}?\n{detail}"
)
# 一次批准仅适用于此页面上的这一确切输入。
key = (name, page, detail)
if key not in granted and ask_user(f"Allow {name} on {page}?\n{detail}"):
granted.add(key)
return key in granted
return confirm
# 同时实现了 javascript_exec 和 file_upload 的 MyBrowser
browser = MyBrowser(
backend,
configs={"javascript_exec": {"enabled": True}, "file_upload": {"enabled": True}},
confirm=make_confirm(),
)每次调用 make_confirm()(在 TypeScript 中为 makeConfirm())都会返回一个没有任何批准记录的可调用对象。请为每个工具集调用一次,并为每个用户提供各自的工具集。
批准针对的是上一次状态报告所显示的页面,而页面可能在调用运行之前发生变化。购买、发送消息和接受条款都是通过 left_click 和 type 等普通成员进行的,因此 confirm 无法按名称将它们挑出来。要让人来批准这些操作,请同样询问这些成员。
不要在不拦截请求的驱动程序上启用 javascript_exec。在被拒绝页面上运行的脚本可以将其内容复制到某个位置,之后的读取操作会返回该内容。
隔离浏览器主机
为每个会话在专用的、最小权限的容器或虚拟机中运行浏览器:
- 以非 root 用户身份运行,并在浏览器允许的情况下使用只读根文件系统。
- 除了您配置的上传和下载目录(如果有)之外,不要从主机挂载任何内容。
- 不要在环境中放置凭据,并从全新的浏览器配置文件开始。
- 不与 Claude 可以调用的其他工具共享任何文件系统。
请在浏览器容器之外运行调用 API 的代码,因为该代码持有您的 API 密钥和对话。工具运行器和 tool_result 都在该代码的进程中运行工具集,因此浏览器不会与工具集共享文件系统。请将浏览器视为远程浏览器:远程和托管浏览器中的内容适用。
将页面返回的所有内容都视为不可信内容,包括页面文本、屏幕截图、控制台和网络条目、标签页标题以及下载名称。
远程和托管浏览器
有些浏览器不与运行 SDK 的进程共享文件系统。例如,位于另一个容器中的浏览器、通过 DevTools URL 访问的浏览器,以及来自托管浏览器服务的浏览器。对于这些浏览器,URL 策略、请求拦截和 confirm 仍在您的进程中运行。
对于托管浏览器,由提供商控制出站流量和主机隔离。您自己的出站策略在那里不适用,因此驱动程序的请求钩子是您对浏览器请求的唯一检查。请了解浏览器的网络可以访问哪些内容。
内置的路径检查无法保护远程浏览器。LocalFilePolicy(在 TypeScript 中为 NodeFilePolicy)检查的是运行 SDK 的进程所在文件系统上的路径,而浏览器读写的是它自己的文件系统。
SDK 无法检测浏览器是否为远程浏览器。因此,对于远程浏览器,请保持 file_upload 关闭,除非您的驱动程序使用自己的 FilePolicy 在浏览器运行的位置检查上传路径。FilePolicy 会审查每次上传的路径和文档 ID,并决定 Claude 是否能看到下载的路径。
让远程浏览器拒绝下载,除非其自身主机具有限制上传和下载中所述的下载设置。
示例驱动程序遵循此规则。在远程浏览器上,它们会针对 file_policy(filePolicy)或已启用的 file_upload 引发配置错误,并将浏览器设置为拒绝下载。
不要让提供商的 API 密钥和会话的连接 URL(其中可能包含密钥)出现在日志、工具结果和错误文本中。如果提供商会录制会话,则录制内容是 Claude 所见和所输入的一切内容的另一份副本,并且适用提供商的保留条款。
参考
构造函数选项在两个 SDK 中的含义相同:
| Python | TypeScript | 设置内容 |
|---|---|---|
configs | configs | 启用哪些成员 |
confirm | confirm | 批准或拒绝每个调用的可调用对象 |
allowed_domains、blocked_domains | allowedDomains、blockedDomains | 默认 URL 策略的列表 |
url_policy | urlPolicy | 您自己的 URL 策略 |
file_policy | filePolicy | 上传根目录和下载路径公开 |
tool_configs | toolConfigs | tools 条目的字段,例如 cache_control |
_browser_state 方法 | browserState | 状态报告 |
构造之后无法更改选项。默认值、错误、上下文字段以及异步 Python 类(BetaAsyncAbstractBrowserToolset20260801)记录在 Python SDK 和 TypeScript SDK 中。
限制
- URL 策略检查的是导航,而不是每个请求: 请参阅在驱动程序中拦截请求。
- 批准基于上一次状态报告: 页面可能在该报告之后发生变化。SDK 不会在调用运行之前再次检查页面。
- 同一工具集上的调用一次只运行一个: 您无法关闭此行为。
- SDK 不检查标签页 ID 是否唯一、是否有一个标签页处于活动状态,也不检查标签页的数量: API 会拒绝违反这些规则的报告。
后续步骤
成员工具、browser_state 块以及该工具的安全注意事项。
SDK 如何运行循环,以及如何更改它发送的消息。
适用于任何读取不可信内容的应用程序的防护措施。
Was this page helpful?