Uso del navegador y de la computadora con los conjuntos de herramientas del SDK
Ejecuta la herramienta de uso del navegador o la herramienta de uso de computadora desde el SDK de Python o TypeScript. El SDK ejecuta el bucle y las comprobaciones que configures, y tú proporcionas el navegador o el escritorio.
Con los "toolsets" (conjuntos de herramientas) de navegador y de computadora de los SDK de Python y TypeScript, puedes crear un agente que trabaje en un navegador web o en un escritorio. Puede usar aplicaciones que no tienen interfaz programática. A través de la herramienta de uso del navegador, Claude navega, lee e interactúa con páginas web. A través de la herramienta de uso de computadora, Claude ve un escritorio en capturas de pantalla y usa el mouse y el teclado.
El SDK conecta a Claude con tu navegador o escritorio. Ejecuta las comprobaciones que configures, como una política de URL para navigate o un callback de aprobación. Envía cada llamada que las supera a tu "driver" (controlador) y construye el tool_result a partir de lo que devuelve el controlador. El "tool runner" (ejecutor de herramientas) puede ejecutar el bucle agéntico por ti.
Tú proporcionas el controlador, que opera el navegador o el escritorio. Empieza con el controlador de un proveedor. Con uno, un agente requiere unas pocas líneas de código. Si ninguno se ajusta, escribe el tuyo con una biblioteca de automatización de navegadores como Playwright.
La mayor parte de esta página trata sobre el conjunto de herramientas de navegador. Conjunto de herramientas de computadora cubre lo que difiere.
Escribir tu propio controlador es un caso avanzado, que se cubre en Implementar un controlador. El repositorio claude-quickstarts también tiene un controlador de navegador de ejemplo en Python y TypeScript. Controla Chromium a través del Chrome DevTools Protocol (CDP), y no es código de producción.
Usar un controlador de un proveedor
Con un controlador de un proveedor, como uno de los que aparecen en Integraciones de socios, puedes crear un agente en unas pocas líneas de código. El proveedor de este ejemplo es inventado. Reemplaza example_browser_provider y ExampleBrowser por el paquete y la clase de controlador de un proveedor real, y configúralos como se describe en la documentación del proveedor.
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)Pasa la propia instancia del controlador como entrada de tools. El ejecutor nunca cierra el conjunto de herramientas, así que una instancia puede servir para varias ejecuciones. Ciérrala cuando termines.
Para escribir tu propio controlador, consulta Implementar un controlador.
Integraciones de socios
Browser Use, Browserbase, Daytona y E2B publican sus propios controladores para los conjuntos de herramientas del SDK.
Controlador de Browser Use para el conjunto de herramientas de navegador.
Controlador Stagehand de Browserbase para el conjunto de herramientas de navegador.
Controlador de Daytona para el conjunto de herramientas de computadora.
Controladores de E2B para los conjuntos de herramientas de navegador y de computadora.
Nuestros socios también publican código en GitHub: el inicio rápido de Browser Use, el conjunto de herramientas de Browserbase y el ejemplo de E2B.
Personalizar un controlador
Los ejemplos de esta sección usan MyBrowser, el controlador del ejemplo avanzado que aparece más adelante en esta página.
Habilitar o deshabilitar miembros
configs acepta la configuración por miembro descrita en Configura el conjunto de herramientas. Enumera solo los miembros que cambies:
# Un MyBrowser que también implementa read_console
browser = MyBrowser(
backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
)El SDK rechaza una llamada a un miembro deshabilitado antes de que se ejecute tu código. Habilitar un miembro que tu clase no implementa es un error de configuración, a menos que la clase sobrescriba execute.
Agregar hooks antes y después
Sobrescribe execute y llama al execute de la clase padre. El código antes de esa llamada se ejecuta después de la política de URL, la política de archivos y confirm, y puede cambiar la entrada. El SDK no vuelve a comprobar la entrada modificada. El código después de la llamada recibe el resultado y puede cambiarlo. Lanza ToolError (con throw en TypeScript) para rechazar la llamada.
Sobrescribir execute cambia qué miembros se le ofrecen a Claude. El SDK cuenta cada miembro como implementado, así que a Claude se le ofrecen todos los miembros que están activados por defecto. El MyBrowser del ejemplo avanzado atiende tres miembros, así que el siguiente TracedBrowser le ofrece a Claude miembros que no puede atender. Desactiva esos miembros con configs antes de usarlo.
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 resultImplementar un controlador
Un controlador es tu subclase de BetaAbstractBrowserToolset20260801. En ella, sobrescribe los miembros que admite tu navegador. Un miembro que no implementas se envía a la API como deshabilitado. Si Claude lo llama de todos modos, el SDK devuelve un error y la ejecución continúa. Sobrescribir execute cambia qué miembros se envían como deshabilitados (Agregar hooks antes y después). Cada miembro recibe el contexto de la llamada y la entrada del miembro como un objeto tipado, como BetaBrowserNavigateInput. Los tipos de entrada provienen de anthropic.types.beta. Herramientas miembro enumera los campos de cada entrada.
En TypeScript, escribe los miembros como métodos, no como campos de función flecha, porque el SDK los busca en el prototipo. Escribe el miembro type como type_ en TypeScript. En Python, es type.
Devolver resultados
Lo que devuelve un miembro determina lo que lee Claude. Un resultado exitoso termina con un bloque browser_state construido a partir de tu informe de estado. Un resultado de error no lleva ningún bloque.
| Miembro | Devuelve | Claude lee |
|---|---|---|
screenshot, zoom | BetaScreenshotResult | Un bloque de imagen |
navigate | BetaBrowserNavigateResult | Navigated to {url} — {title} (HTTP {status}) |
new_tab, switch_tab, list_tabs, close_tab | Una entrada de pestaña (new_tab, switch_tab), una lista de entradas de pestaña (list_tabs) o nada (close_tab) | Solo el bloque browser_state |
read_page, get_page_text, find, read_console, read_network, javascript_exec | Una cadena | La cadena, o (empty) para una cadena vacía |
| Cualquier otro miembro | Nada, o una línea de texto | Una confirmación breve, como Clicked., seguida de la línea devuelta en un bloque de texto propio |
Informar el estado del navegador
El SDK llama a _browser_state después de cada llamada que devuelve un resultado, incluidas las llamadas rechazadas y fallidas. Devuelve cada pestaña abierta y lo que cambió desde el último informe:
- Las pestañas que se abrieron y los eventos de descarga.
- Un
BetaNavigationRefusedpor cada navegación que bloqueó tu hook de solicitudes. - Un
BetaDialogDismissedpor cada diálogo nativo que descartó tu controlador.
Pon todo esto en state_changes. En Python, BetaNavigationRefused() y BetaDialogDismissed(kind=..., message=...) provienen de anthropic.tools.browser. En TypeScript, son { type: "navigation_refused" } y { type: "dialog_dismissed", kind, message }.
Los dos últimos no son cambios de estado de la API. El SDK se los informa a Claude como texto fuera del bloque browser_state: una línea para todas las navegaciones rechazadas, que no incluye una URL, y una línea para cada uno de los tres primeros diálogos descartados, seguida de un recuento de los demás.
Cuando hay alguna pestaña abierta, exactamente una debe estar activa. Cada miembro que recibe un tab_id debe actuar sobre la pestaña que nombra. Los límites de la API sobre el informe se enumeran en Rastrea pestañas con browser_state.
Claude lee la URL de cada pestaña, la URL de cada descarga y la URL que devuelve tu navigate, tal como las escribió tu controlador. El SDK no analiza estas URL. Convierte los saltos de línea y otros caracteres de control en espacios, recorta los extremos y corta cada URL a 4,096 caracteres. Así que el contenido de una URL data:, la ruta de una URL file: y un nombre de usuario o contraseña en una URL llegan todos a Claude.
Manejar errores
| Lanzado por un miembro o el SDK | Claude lee | La ejecución |
|---|---|---|
ToolError | Su mensaje, como resultado de error | Continúa |
ToolsetUsageError | Nada | Se detiene |
| Cualquier otra excepción | ClassName: message en Python o Error: message en TypeScript, como resultado de error | Continúa |
El SDK lanza ToolsetUsageError ante un error de configuración, un uso indebido del SDK durante una llamada o una llamada después de close. También lanza uno cuando _browser_state lanza cualquier excepción, incluso un ToolError. El ejecutor de herramientas y tool_result no lo capturan, así que llega a quien los llamó.
El SDK no oculta las rutas locales en el texto de error de un miembro, en la línea que devuelve una acción como left_click, en el error de una descarga fallida ni en el mensaje de un diálogo descartado.
Un ToolError de tu política de URL, tu política de archivos o tu callable confirm llega a Claude tal como está escrito, así que no incluyas rutas locales en su texto. Captura las excepciones en tus miembros y lanza ToolError (con throw en TypeScript) con tu propio texto.
Iniciar llamadas mientras la respuesta hace streaming
Por defecto, el ejecutor de herramientas ejecuta las llamadas de un turno después de que termina la respuesta de Claude. Con el inicio anticipado, puede iniciar una llamada del navegador o de la computadora mientras la respuesta todavía está haciendo streaming.
Para activarlo, pasa stream=True y run_tools_eagerly=True al ejecutor de herramientas, como hace el ejemplo avanzado. Con stream, cada pasada de tu bucle sobre el ejecutor te da un stream en lugar de un mensaje.
El inicio anticipado cambia cuándo puede comenzar una llamada, no cómo la ejecuta el conjunto de herramientas:
- Las comprobaciones siguen ejecutándose primero: El SDK llama a tu política de URL, tu política de archivos y
confirmantes de que se ejecute una llamada. Con el inicio anticipado, puede llamar aconfirmmientras la respuesta todavía está haciendo streaming. - Las llamadas siguen ejecutándose de una en una: Un conjunto de herramientas ejecuta sus llamadas en el orden en que Claude las escribió. Una llamada fallida sigue deteniendo las llamadas posteriores de ese conjunto de herramientas en el turno.
- Una llamada que ya comenzó no se puede deshacer: Si luego la respuesta se corta, por ejemplo en
max_tokens, o tu bucle se detiene antes, la acción igual ocurre y Claude nunca lee su resultado.
Ejecutar sin el ejecutor de herramientas
Pasa la instancia en tools (browser.toJSON() en TypeScript) y responde cada llamada a un miembro con tool_result. La herramienta de uso del navegador requiere que te detengas en la primera llamada fallida (Acciones por lotes).
Este bucle usa MyBrowser y la política de URL del ejemplo avanzado. Después de una llamada fallida, responde las llamadas posteriores del turno sin ejecutarlas:
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:
# Tras una llamada fallida, el resto del turno se responde, no se ejecuta.
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})Cada respuesta a una llamada omitida lleva is_error, el toolset_name de la llamada y el texto exacto que requiere Acciones por lotes. El ejecutor de herramientas envía la misma respuesta.
Ejemplo avanzado: escribe tu propio controlador
Este ejemplo reúne las partes principales de Implementar un controlador en un solo controlador, MyBrowser, y lo ejecuta con el ejecutor de herramientas y el inicio anticipado. MyBrowser implementa navigate, screenshot y left_click, además de _browser_state, el informe de estado que necesita todo controlador.
backend representa tu propio wrapper alrededor de una biblioteca de automatización de navegadores, como Playwright. La política de URL es un ejemplo. Escribir una política de URL la explica.
import re
from urllib.parse import urlsplit
from anthropic import Anthropic
from anthropic.tools import ToolError
from anthropic.tools.browser import (
BetaAbstractBrowserToolset20260801,
BetaBrowserNavigateResult,
BetaBrowserState,
BetaScreenshotResult,
BetaToolsetCallContext,
BetaURLContext,
)
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: BetaToolsetCallContext) -> BetaBrowserState:
return BetaBrowserState(
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: BetaToolsetCallContext, input: BetaBrowserNavigateInput
) -> BetaBrowserNavigateResult:
# input.url es "back", "forward", "reload" o una URL que tu política de URL
# permitió, tal como Claude la escribió. Igual que is_allowed, backend.goto agrega https://
# a una URL que no comienza con un esquema.
page = self.backend.goto(input.url, input.tab_id)
return BetaBrowserNavigateResult(
url=page.url, status=page.status, title=page.title
)
def screenshot(
self, context: BetaToolsetCallContext, input: BetaBrowserScreenshotInput
) -> BetaScreenshotResult:
data = self.backend.png_base64(input.tab_id)
return BetaScreenshotResult(data=data, media_type="image/png")
def left_click(
self, context: BetaToolsetCallContext, input: BetaBrowserLeftClickInput
) -> None:
# Nada que devolver: Claude lee "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()
ALLOWED_HOSTS = ("example.com", "iana.org")
SCHEME_PREFIX = re.compile(r"[a-z][a-z0-9+.-]*:", re.IGNORECASE)
def is_allowed(url: str) -> bool:
# Un ejemplo, no una política de producción. Consulta "Escribir una política de URL".
if url.lower() == "about:blank":
return True
with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
# Un navegador interpreta "\" como "/" en una URL web.
try:
parts = urlsplit(with_scheme.replace("\\", "/"))
except ValueError:
return False
host = parts.hostname or ""
listed = any(host == name or host.endswith(f".{name}") for name in ALLOWED_HOSTS)
return parts.scheme in ("http", "https") and listed
def url_policy(context: BetaURLContext, url: str) -> None:
if not is_allowed(url):
raise ToolError(f"blocked: {url} is not on an allowed host")
client = Anthropic()
with MyBrowser(backend, url_policy=url_policy) 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.",
}
],
stream=True,
run_tools_eagerly=True, # so a call can start before the response ends
)
for stream in runner:
print(stream.get_final_message())Ejecutar el conjunto de herramientas de forma segura
La siguiente acción de Claude depende de las páginas que lee. Una página, o un texto inyectado en ella, puede intentar llegar a servicios internos o extraer archivos del host. También puede intentar desencadenar acciones con efectos reales. Antes de ejecutar un controlador contra cualquier cosa que no sea un navegador desechable, sigue estos seis pasos:
- Escribe una política de URL que rechace cada URL de
navigateque la tarea no necesite. - Intercepta las solicitudes en el controlador y comprueba cada solicitud con las mismas reglas.
- Aplica una "egress policy" (política de salida) en el contenedor, para que la red bloquee lo que el controlador no puede ver.
- Limita las cargas y descargas, o deja las cargas desactivadas.
- Controla los miembros con consecuencias con
confirm. - Aísla el host del navegador en un contenedor o VM dedicado para cada sesión.
El SDK aplica los pasos 1, 4 y 5 mediante las políticas y el callable que le pases. Los pasos 2, 3 y 6 dependen de tu controlador y tu despliegue. Las precauciones de Consideraciones de seguridad también se aplican.
Escribir una política de URL
Una política de URL es una función que pasas como url_policy. Tú escribes la política y eres responsable de prepararla para producción. Antes de que se ejecute tu navigate, el SDK llama a la política con el contexto de la llamada y la URL tal como la escribió Claude.
No devuelvas nada para permitir la URL. Para rechazarla, lanza ToolError (con throw en TypeScript). Claude lee el mensaje del error y navigate no se ejecuta. La política comprueba solo la URL de cada llamada a navigate. No es un control de red.
Esta es la política del ejemplo avanzado. Permite about:blank, además de dos sitios y sus subdominios sobre http y https:
import re
from urllib.parse import urlsplit
from anthropic.tools import ToolError
from anthropic.tools.browser import BetaURLContext
ALLOWED_HOSTS = ("example.com", "iana.org")
SCHEME_PREFIX = re.compile(r"[a-z][a-z0-9+.-]*:", re.IGNORECASE)
def is_allowed(url: str) -> bool:
# Un ejemplo, no una política para producción.
if url.lower() == "about:blank":
return True
with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
# Un navegador interpreta "\" como "/" en una URL web.
try:
parts = urlsplit(with_scheme.replace("\\", "/"))
except ValueError:
return False
host = parts.hostname or ""
listed = any(host == name or host.endswith(f".{name}") for name in ALLOWED_HOSTS)
return parts.scheme in ("http", "https") and listed
def url_policy(context: BetaURLContext, url: str) -> None:
if not is_allowed(url):
raise ToolError(f"blocked: {url} is not on an allowed host")
browser = MyBrowser(backend, url_policy=url_policy)El SDK llama a la política solo para navigate, y no para "back", "forward" ni "reload". Claude puede omitir el esquema, como en example.com, así que antes de leer el host, el ejemplo agrega https:// a una URL que no empieza con un esquema. Tu navigate recibe la URL tal como la escribió Claude. Agrega el esquema allí con la misma prueba, para que el navegador abra la URL que comprobó tu política.
El ejemplo no es apto para producción. Una política de producción necesita mucho más, en áreas como estas:
- Enlaces en los que Claude hace clic, formularios que envía, redirecciones y las solicitudes que una página hace por su cuenta. Solo tu controlador los ve. Consulta Interceptar solicitudes en el controlador.
- Nombres de host y URL que provienen del contenido de la página, que un atacante puede elegir.
- Analizar cada URL como lo hace el navegador, no encontrar el nombre de host con una simple división de cadenas.
- Esquemas de URL. El SDK no los comprueba, así que rechaza cada esquema que tu controlador no pretenda abrir.
- Reglas de salida en el contenedor, para lo que ninguna comprobación de URL puede ver. Consulta Aplicar una política de salida en el contenedor.
Si pasas url_policy=None, el SDK rechaza cada navigate a una URL, así que un None leído de tu configuración no puede dejar URL sin comprobar por accidente. En TypeScript, undefined deja la opción sin establecer, igual que no pasarla.
Interceptar solicitudes en el controlador
La política de URL evalúa solo la URL de una llamada a navigate. Un enlace en el que Claude hace clic, un formulario que envía, una redirección o una imagen o script en una página permitida aún pueden alcanzar un host que la política rechazaría. Usa reglas de salida para bloquear esos hosts.
Un controlador con intercepción de solicitudes también puede comprobar esas solicitudes en su hook de solicitudes, la función que tu biblioteca de automatización llama antes de cada solicitud. Llama a la función que usa tu política, para escribir las reglas una sola vez:
class MyBrowser(BetaAbstractBrowserToolset20260801):
...
# Registrado en un contexto de navegador de Playwright con context.route("**/*", self._guard)
def _guard(self, route):
# is_allowed es la función de "Escribe una política de URL"
if not is_allowed(route.request.url):
return route.abort("blockedbyclient")
route.continue_()Un hook de solicitudes como este no ve los handshakes de WebSocket, las solicitudes de service workers ni los saltos de redirección. Bloquea los service workers. Comprueba los handshakes de WebSocket y los saltos de redirección con un hook que los vea. La política de ejemplo rechaza las URL ws:// y wss://, así que cambia ws:// por http:// y wss:// por https:// antes de pasar una URL de WebSocket a is_allowed.
Aplicar una política de salida en el contenedor
La interceptación no puede ver todas las solicitudes que hace el navegador, y la política de URL no ve dónde se resuelve un nombre. Una política de salida que aplique la red del contenedor cubre ambos casos:
- Bloquea los rangos de direcciones link-local y privadas en IPv4 e IPv6. Eso incluye la dirección de metadatos de la nube
169.254.169.254. - Permite conexiones salientes solo a los hosts que la tarea necesita. Si tus reglas coinciden con direcciones IP, resuelve los nombres de host que permites cuando se inicia el contenedor.
- Permite DNS solo hacia el resolvedor del contenedor.
Las reglas aplicadas fuera del contenedor del navegador, como una NetworkPolicy de Kubernetes o un firewall en la nube, no ven el loopback dentro de él. Con solo esas reglas, una página puede alcanzar cualquier cosa que esté escuchando allí, incluido un puerto de DevTools, que muestra una lista de las pestañas abiertas del navegador y la dirección que controla cada una.
Limitar las cargas y descargas
file_upload está desactivado por defecto. Sin file_policy, el SDK rechaza toda carga que indique una ruta o un ID de documento. Para habilitar las cargas, pasa un BetaLocalFilePolicy con un directorio de carga que contenga solo los archivos de la tarea:
from anthropic.tools.browser import BetaLocalFilePolicy
# Un MyBrowser que también implementa file_upload
browser = MyBrowser(
backend,
configs={"file_upload": {"enabled": True}},
confirm=make_confirm(), # required for file_upload; see Gate consequential members
url_policy=url_policy,
file_policy=BetaLocalFilePolicy(
upload_roots=["/task/uploads"],
download_dir="/task/downloads",
expose_download_paths=False,
),
)El SDK resuelve cada ruta de carga, siguiendo los enlaces simbólicos, y rechaza cualquier ruta fuera de las raíces de carga. La política de archivos lanza un error de configuración para un directorio de descarga que sea una raíz de carga, esté dentro de una o contenga una. Esa comprobación puede pasar por alto un enlace simbólico, así que no dejes que uno haga que el directorio de descarga sea el mismo que una raíz de carga, ni que ponga uno dentro del otro. El SDK incluye el path de una descarga en el informe de estado solo cuando expose_download_paths es verdadero y el archivo está dentro del directorio de descarga.
La política de archivos incluida comprueba las rutas en el sistema de archivos del proceso que ejecuta el SDK. Solo protege a un navegador que comparte ese sistema de archivos. Para un navegador remoto, sigue Navegadores remotos y alojados en su lugar.
Configura las descargas de esta manera:
- Crea tú mismo el directorio de descarga con el modo
0700y móntalo connoexec,nosuid,nodev. - Mantén el directorio fuera del alcance de otras herramientas que Claude pueda llamar, como un shell o una herramienta de archivos.
- En un cambio de estado
download_failed, escribeerrorcomo una frase fija. El texto de una excepción puede contener la ruta o la URL. - No leas un archivo descargado en la conversación, ni lo ejecutes, hasta que una persona lo decida.
Controlar los miembros con consecuencias
javascript_exec y file_upload están desactivados por defecto. Si habilitas cualquiera de los dos sin un callable confirm, el constructor lanza un error de configuración. Con un callable confirm, el SDK lo llama antes de cada llamada que está a punto de ejecutarse. Sin uno, no se pregunta nada. En TypeScript, confirm: null no lanza el error de configuración, y el SDK rechaza cada llamada a cualquier miembro.
Devuelve True para ejecutar la llamada, o False para rechazarla. Cuando tu callable le pregunte a una persona, muéstrale el miembro, la URL de la página y la entrada de la llamada. Primero, escapa cada carácter fuera del ASCII imprimible en la URL y la entrada, porque ambas pueden llevar texto de la página.
Este ejemplo pregunta por los dos miembros controlados mediante tu propia función ask_user, y aprueba el resto:
import json
from collections.abc import Callable
from urllib.parse import urlsplit
from anthropic.tools.browser import BetaConfirmContext
GATED = {"javascript_exec", "file_upload"}
def shown(value: object) -> str:
"""A value as JSON, with every character outside printable ASCII escaped."""
return json.dumps(value, ensure_ascii=True, indent=2)
def make_confirm() -> Callable[[BetaConfirmContext], bool]:
granted: set[tuple[str, str, str]] = set()
def confirm(context: BetaConfirmContext) -> bool:
name = context.member
if name not in GATED:
return True
detail = shown(context.input.to_dict())
page = context.tab_url
where = shown(page) if page else "a tab with no URL"
question = f"Allow {name} on {where}?\n{detail}"
if page is None or urlsplit(page).scheme not in ("http", "https"):
return ask_user(question) # no web origin: ask every time
# Una aprobación cubre solo esta entrada exacta en esta página.
key = (name, page, detail)
if key not in granted and ask_user(question):
granted.add(key)
return key in granted
return confirm
# Un MyBrowser que también implementa javascript_exec y file_upload
# file_upload también necesita la file_policy de "Confine uploads and downloads"
browser = MyBrowser(
backend,
configs={"javascript_exec": {"enabled": True}, "file_upload": {"enabled": True}},
confirm=make_confirm(),
url_policy=url_policy,
)Cada llamada a make_confirm() devuelve un callable sin aprobaciones. Llámala una vez por cada conjunto de herramientas y dale a cada usuario su propio conjunto de herramientas.
Una aprobación abarca la página tal como la mostró el último informe de estado, y la página puede cambiar antes de que se ejecute la llamada. Las compras, los mensajes enviados y los términos aceptados ocurren a través de miembros ordinarios como left_click y type, por lo que confirm no puede identificarlos por nombre. Para que una persona los apruebe, pregunta también por esos miembros.
No habilites javascript_exec a menos que las reglas de salida limiten las conexiones salientes del navegador a los hosts que la tarea necesita. Un script puede enviar el contenido de la página a cualquier dirección que el navegador pueda alcanzar.
Una URL javascript: en una llamada a navigate también ejecuta un script en la página actual, incluso cuando javascript_exec está desactivado. confirm ve esa llamada como un navigate, así que el callable confirm del ejemplo la aprobaría. Tu controlador debe rechazar las URL javascript:. En el ejemplo, la política de URL las rechaza antes de que se ejecute confirm.
Aislar el host del navegador
Ejecuta el navegador en un contenedor o VM dedicado y con privilegios mínimos para cada sesión:
- Ejecútalo como un usuario que no sea root, con un sistema de archivos raíz de solo lectura donde el navegador lo permita.
- No montes nada del host más allá de los directorios de carga y descarga que configuraste, si los hay.
- Mantén las credenciales fuera del entorno y empieza con un perfil de navegador nuevo.
- No compartas ningún sistema de archivos con otras herramientas que Claude pueda llamar.
Ejecuta el código que llama a la API fuera del contenedor del navegador, porque ese código contiene tu clave de API y la conversación. El ejecutor de herramientas y tool_result ejecutan ambos el toolset en el proceso de ese código, así que el navegador no comparte el sistema de archivos del toolset. Trata el navegador como remoto: se aplica Navegadores remotos y alojados.
Trata todo lo que devuelve una página como no confiable, incluidos el texto de la página, las capturas de pantalla, las entradas de consola y de red, los títulos de las pestañas y los nombres de las descargas.
Navegadores remotos y alojados
Algunos navegadores no comparten un sistema de archivos con el proceso que ejecuta el SDK. Algunos ejemplos son un navegador en otro contenedor, uno al que accedes mediante una URL de DevTools y uno de un servicio de navegador alojado. Con estos, la política de URL, la interceptación de solicitudes y confirm siguen ejecutándose en tu proceso.
Con un navegador alojado, el proveedor controla la salida y el aislamiento del host. Tu propia política de salida no se aplica allí, por lo que el hook de solicitudes del controlador es tu única comprobación sobre las solicitudes del navegador. Averigua a qué puede llegar la red del navegador.
Las comprobaciones de rutas incluidas no protegen a un navegador remoto. BetaLocalFilePolicy comprueba las rutas en el sistema de archivos del proceso que ejecuta el SDK, y el navegador lee y escribe en su propio sistema de archivos.
El SDK no puede detectar que un navegador es remoto. Así que, para un navegador remoto, mantén file_upload desactivado a menos que tu controlador compruebe las rutas de carga donde se ejecuta el navegador, con un BetaFilePolicy propio. Un BetaFilePolicy examina las rutas y los ID de documento de cada carga, y decide si Claude ve la ruta de una descarga.
Haz que un navegador remoto rechace las descargas, a menos que su propio host tenga la configuración de descargas de Limitar las cargas y descargas.
Mantén la clave de API del proveedor y la URL de conexión de la sesión, que puede contener una clave, fuera de los registros, los resultados de herramientas y el texto de error. Si el proveedor graba las sesiones, la grabación es otra copia de todo lo que Claude vio y escribió, y los términos de retención del proveedor se aplican a ella.
Conjunto de herramientas de computadora
BetaAbstractComputerToolset20260801 es la clase para la herramienta de uso de computadora. Un controlador es una subclase con un método por herramienta, como screenshot o left_click. Comienza con el controlador de un socio en Integraciones de socios. Para escribir el tuyo, el repositorio claude-quickstarts tiene un ejemplo mínimo de VNC en Python y TypeScript. Controla un escritorio a través de VNC y no es código de producción.
La clase recibe configs, confirm y tool_configs, y los pasas igual que con la clase del navegador. Personalizar un controlador, Manejar errores, Iniciar llamadas mientras la respuesta hace streaming y Ejecutar sin el ejecutor de herramientas también se aplican a ella, con estas diferencias:
- Opciones exclusivas del navegador: La clase no tiene política de URL, ni política de archivos, ni informe de estado.
configs: Cada herramienta que implementes está activada por defecto. La configuración por herramienta se enumera en Parámetros de la herramienta.confirm: Si tu clase implementatype,keyohold_key, el constructor lanza un error de configuración a menos que pases un callableconfirmo desactives esas herramientas conconfigs. Una clase que sobrescribeexecutecuenta como si implementara las tres.- Llamadas omitidas: Después de una llamada de computadora fallida, el ejecutor de herramientas responde las llamadas de computadora posteriores del turno con
Not executed: an earlier computer action in this turn failed.Las llamadas del navegador reciben un texto diferente. En un bucle que escribas tú, responde las llamadas de computadora omitidas con ese texto exacto (Acciones por lotes). execute: En TypeScript, una sobrescritura deexecutetipanamecomoBetaComputerMemberNameeinputcomoBetaComputerMemberInput. Ambos provienen de@anthropic-ai/sdk/resources/beta.- Texto de error: El SDK no oculta las rutas locales en el texto de error de una herramienta de computadora ni en el texto que devuelve una herramienta. Claude lee ambos, cortados a 4,096 caracteres. Captura las excepciones en tus herramientas y lanza
ToolError(conthrowen TypeScript) con tu propio texto.
Escribir un controlador de escritorio
Este controlador implementa cinco de las 17 herramientas enumeradas en Acciones disponibles. Una herramienta que no implementas se envía a la API como deshabilitada, como explica Implementar un controlador. En el ejemplo, display representa tu propio wrapper alrededor de lo que controle el escritorio, como un cliente VNC o xdotool, y ask_user representa tu propia función que le pregunta a una persona.
from anthropic import Anthropic
from anthropic.tools.computer import (
BetaAbstractComputerToolset20260801,
BetaComputerCursorPositionResult,
BetaScreenshotResult,
BetaToolsetCallContext,
)
from anthropic.types.beta import (
BetaComputerCursorPositionInput,
BetaComputerKeyInput,
BetaComputerLeftClickInput,
BetaComputerScreenshotInput,
BetaComputerTypeInput,
)
class MyDesktop(BetaAbstractComputerToolset20260801):
def __init__(self, display, **options):
super().__init__(**options)
self.display = display
def screenshot(
self, context: BetaToolsetCallContext, input: BetaComputerScreenshotInput
) -> BetaScreenshotResult:
# Las coordenadas de Claude son posiciones en píxeles en esta imagen.
data = self.display.png_base64()
return BetaScreenshotResult(data=data, media_type="image/png")
def cursor_position(
self, context: BetaToolsetCallContext, input: BetaComputerCursorPositionInput
) -> BetaComputerCursorPositionResult:
x, y = self.display.cursor()
return BetaComputerCursorPositionResult(x=x, y=y)
def left_click(
self, context: BetaToolsetCallContext, input: BetaComputerLeftClickInput
) -> None:
# input.coordinate es [x, y], o None para hacer clic en el cursor. input.text es un
# modificador que se mantiene pulsado, como "shift". No devuelve nada: Claude lee "Clicked."
self.display.click(input.coordinate, input.text)
def type(
self, context: BetaToolsetCallContext, input: BetaComputerTypeInput
) -> None:
self.display.type(input.text)
def key(self, context: BetaToolsetCallContext, input: BetaComputerKeyInput) -> None:
self.display.press(input.text, input.repeat or 1)
def close(self) -> None:
super().close() # first, so no call is still using the desktop when it closes
self.display.close()
client = Anthropic()
with MyDesktop(
display,
# obligatorio para type y key; consulta Ejecutar el conjunto de herramientas de computadora de forma segura
confirm=lambda context: ask_user(f'Allow the "{context.member}" tool?'),
) as desktop:
runner = client.beta.messages.tool_runner(
model="claude-opus-5-5",
max_tokens=1024,
tools=[desktop],
messages=[
{"role": "user", "content": "Open the calculator and compute 17 * 23."}
],
)
for message in runner:
print(message)Cuatro herramientas no tienen campos de entrada: screenshot, cursor_position, left_mouse_down y left_mouse_up. Cada tipo de entrada, incluidos los suyos, proviene de anthropic.types.beta.
En TypeScript, escribe las herramientas como métodos, no como campos con funciones flecha. Implementar un controlador explica el motivo.
Escalar coordenadas y dimensionar imágenes
Cada punto que Claude envía es una posición en píxeles en las capturas de pantalla que devuelves: cada coordinate y start_coordinate, y ambas esquinas de una region de zoom. El SDK los pasa a tus herramientas sin cambios.
Si tus capturas de pantalla tienen un tamaño diferente al de la pantalla, convierte en ambas direcciones en tu controlador: los puntos de Claude a píxeles de la pantalla, y la posición del cursor que informas a píxeles de la captura de pantalla. Mantén el mismo tamaño de captura de pantalla durante toda la sesión y dale la relación de aspecto de la pantalla:
from anthropic.tools import ToolError
SCREENSHOT_SIZE = (1280, 720) # the size of every full screenshot the driver returns
DISPLAY_SIZE = (2560, 1440)
def to_display(coordinate: list[int]) -> tuple[int, int]:
x, y = coordinate
width, height = SCREENSHOT_SIZE
if not (0 <= x < width and 0 <= y < height):
# Rechaza el punto. Moverlo dentro del rango haría clic donde Claude no eligió.
raise ToolError(f"[{x}, {y}] is outside the {width}x{height} screenshot")
return x * DISPLAY_SIZE[0] // width, y * DISPLAY_SIZE[1] // height
def to_screenshot(x: int, y: int) -> tuple[int, int]:
width, height = DISPLAY_SIZE
return x * SCREENSHOT_SIZE[0] // width, y * SCREENSHOT_SIZE[1] // height
to_display([640, 360]) # (1280, 720)Llama a to_display sobre cada coordinate y start_coordinate antes de actuar sobre ellos, y a to_screenshot sobre la posición que informa cursor_position. Un clic o desplazamiento sin coordinate actúa en el cursor, así que no hay nada que convertir. Escala una region de zoom por el mismo factor. Una imagen de zoom no cambia esto: los siguientes puntos que Claude envía siguen siendo posiciones en píxeles en la captura de pantalla completa.
El SDK nunca lee ni redimensiona una imagen. La API rechaza una imagen que supere los límites del modelo, lo que termina la ejecución. Escala cada captura de pantalla e imagen de zoom en el controlador. Consulta Dimensiona las capturas de pantalla para ajustarse a los límites de imagen. En una ejecución larga, una solicitud puede contener más de 20 imágenes, y entonces la API somete cada una a un límite más estricto. Consulta Gestionar el historial de capturas de pantalla.
Comprobaciones de entrada y resultados
No dependas del SDK para comprobar la entrada de una llamada. Haz que tu controlador compruebe los campos que usa y rechace una llamada que no pueda ejecutar: lanza ToolError (con throw en TypeScript). Claude lee el mensaje y puede enviar una llamada corregida.
Ningún SDK limita una duration, un recuento de repeat ni un scroll_amount. El paso 4 de Ejecutar el conjunto de herramientas de computadora de forma segura los cubre.
Lo que devuelve una herramienta determina lo que Claude lee:
| Herramienta | Devuelve | Claude lee |
|---|---|---|
screenshot, zoom | BetaScreenshotResult | Un bloque de imagen |
cursor_position | BetaComputerCursorPositionResult | X={x},Y={y} |
| Cualquier otra herramienta | Nada, o una línea de texto | Una confirmación breve, como Clicked., y luego la línea devuelta en un bloque de texto propio |
Ejecutar el conjunto de herramientas de computadora de forma segura
Lo que hay en la pantalla dirige la siguiente acción de Claude. Una página o una notificación puede intentar que Claude escriba en la ventana equivocada, o desencadenar una acción con efectos reales.
Antes de ejecutar un controlador contra cualquier cosa que no sea una máquina desechable, sigue estos cinco pasos:
- Aísla el escritorio. Ejecútalo en un contenedor o VM dedicado y con privilegios mínimos para cada sesión, sin credenciales y sin montajes del host. Instala solo las aplicaciones que la tarea necesita. Ejecuta fuera de él el código que llama a la API.
- Controla con
confirmlas acciones que tienen efectos reales. Una compra o un mensaje enviado es unleft_click,typeokeyordinario, yconfirmrecibe la herramienta y su entrada, no la pantalla. Así que elige qué herramientas necesitan aprobación según las aplicaciones a las que Claude puede acceder en el escritorio. - Mantén las terminales, los diálogos de ejecución y los lanzadores fuera del escritorio a menos que la tarea necesite uno. Cuando uno tiene el foco del teclado,
type,keyyhold_keyejecutan lo que Claude escriba, y un clic puede iniciar un programa. Si la tarea necesita uno, pregunta a una persona antes de la entrada de teclado y los clics, y aísla el escritorio para que un comando no pueda llegar más allá de lo que la tarea necesita. - Rechaza la entrada que tu escritorio no debería aceptar, como un punto fuera de la captura de pantalla, un
waitde varios minutos, o unrepeatoscroll_amountmuy grande. LanzaToolError(conthrowen TypeScript) en lugar de ajustar el valor al rango. - Trata todo lo que hay en la pantalla como no confiable, incluidos los títulos de ventana y el texto del portapapeles. No lo ejecutes ni lo transmitas sin comprobarlo.
El SDK aplica el paso 2 mediante el callable que pasas. Los demás pasos dependen de tu controlador y tu despliegue. También se aplican las precauciones de Consideraciones de seguridad.
El siguiente confirm pregunta a una persona antes de cualquier entrada de teclado, mediante tu propia función ask_user, y aprueba todas las demás llamadas. context.member es el nombre de la herramienta. Para que una persona apruebe también los clics, establece ASK_FIRST en KEYBOARD | CLICKS.
from anthropic.tools.computer import BetaComputerConfirmContext
KEYBOARD = {"type", "key", "hold_key"}
CLICKS = {
"left_click",
"right_click",
"middle_click",
"double_click",
"triple_click",
"left_click_drag",
"left_mouse_down",
"left_mouse_up",
}
ASK_FIRST = KEYBOARD
def confirm(context: BetaComputerConfirmContext) -> bool:
if context.member not in ASK_FIRST:
return True
# se muestra la función de "Gate consequential members".
detail = shown(context.input.to_dict())
return ask_user(f'Allow the "{context.member}" tool with this input?\n{detail}')
desktop = MyDesktop(display, confirm=confirm)Una llamada rechazada obtiene un resultado de error y la ejecución continúa. Para una llamada type rechazada, Claude lee The user did not grant permission to run 'type'. Do not retry it unless the user asks you to.
Este confirm aprueba cada herramienta que no está en ASK_FIRST, incluida cualquier herramienta que agregues más adelante. Para fallar de forma cerrada, aprueba solo las herramientas sin efecto en el escritorio (screenshot, zoom, cursor_position y wait) y pregunta por todas las demás.
Usar ambos conjuntos de herramientas en una solicitud
Pasa ambas instancias en tools. Cada llamada lleva su toolset_name, browser o computer, y el ejecutor de herramientas envía la llamada a la instancia correspondiente. Después de una llamada fallida, el ejecutor de herramientas omite solo las llamadas posteriores del turno al mismo conjunto de herramientas, así que una llamada de computadora fallida no omite las llamadas de navegador del turno.
# MyBrowser, backend y url_policy provienen del ejemplo avanzado.
# confirm es la función de "Ejecuta el conjunto de herramientas de computadora de forma segura".
with (
MyBrowser(backend, url_policy=url_policy) as browser,
MyDesktop(display, confirm=confirm) as desktop,
):
runner = client.beta.messages.tool_runner(
model="claude-opus-5-5",
max_tokens=1024,
tools=[browser, desktop],
messages=[
{
"role": "user",
"content": "Copy the heading on example.com into the open text editor.",
}
],
stream=True,
run_tools_eagerly=True, # so a call can start before the response ends
)
for stream in runner:
print(stream.get_final_message())En un bucle que escribas tú, responde cada llamada con tool_result en la instancia cuyo toolset_name coincida con el toolset_name de la llamada. En cada turno, registra las llamadas fallidas de cada conjunto de herramientas por separado, y responde una llamada omitida con el texto propio de ese conjunto de herramientas. Ejecutar sin el ejecutor de herramientas tiene el texto del navegador, y la viñeta de llamadas omitidas al principio de esta sección tiene el de la computadora.
Referencia
Las opciones del constructor tienen el mismo significado en ambos SDK:
| Python | TypeScript | Establece |
|---|---|---|
configs | configs | Qué miembros están habilitados |
confirm | confirm | El callable que aprueba o rechaza cada llamada |
url_policy | urlPolicy | Tu política de URL, que el SDK llama antes de cada navigate a una URL |
file_policy | filePolicy | Raíces de carga y exposición de la ruta de descarga |
tool_configs | toolConfigs | Campos para la entrada de tools, como cache_control |
El método _browser_state | browserState | El informe de estado |
La clase de navegador acepta todas las opciones de la tabla. La clase de computadora acepta configs, confirm y tool_configs.
No puedes cambiar una opción después de la construcción. La guía del conjunto de herramientas de navegador para Python, la guía del conjunto de herramientas de navegador para TypeScript, la guía del conjunto de herramientas de computadora para Python y la guía del conjunto de herramientas de computadora para TypeScript describen cada opción. Las guías de Python también cubren las clases asíncronas, BetaAsyncAbstractBrowserToolset20260801 y BetaAsyncAbstractComputerToolset20260801.
Limitaciones
- El SDK no incluye un navegador, un escritorio, un controlador listo para usar ni una política de URL: Tú los proporcionas. Consulta Integraciones de socios, Implementar un controlador y Escribir una política de URL.
- La política de URL solo ve las llamadas
navigate: No ve las solicitudes que hace una página, ni las páginas que se abren de cualquier otra forma, como desde un enlace en el que Claude hace clic o una redirección. Consulta Interceptar solicitudes en el controlador. - Una aprobación se basa en el último informe de estado: La página puede cambiar después de ese informe. El SDK no vuelve a comprobar la página antes de que se ejecute la llamada.
- Las llamadas en un mismo conjunto de herramientas se ejecutan de una en una: No puedes desactivar esto.
- El SDK no comprueba que los ID de pestaña sean únicos ni cuántas pestañas hay, y no siempre comprueba que haya una pestaña activa: La API rechaza un informe que incumpla esas reglas.
- Para una llamada de computadora,
confirmrecibe la herramienta y su entrada, no la pantalla: No puedes saber a partir de ellas qué hace un clic en un punto determinado. - El SDK no redimensiona las imágenes de un conjunto de herramientas de computadora: La API rechaza una imagen que supere los límites del modelo. Consulta Escalar coordenadas y dimensionar imágenes.
Próximos pasos
Las herramientas miembro, el bloque browser_state y las consideraciones de seguridad de la herramienta.
Las 17 herramientas, las coordenadas y los límites de imagen, y las consideraciones de seguridad de la herramienta.
Cómo el SDK ejecuta el bucle y cómo cambiar los mensajes que envía.
Barreras de protección para cualquier aplicación que lea contenido no confiable.
Was this page helpful?