Claude Platform Docs
Messages도구

SDK 도구 세트를 사용한 브라우저 및 컴퓨터 사용

Python 또는 TypeScript SDK에서 브라우저 사용 도구 또는 컴퓨터 사용 도구를 실행합니다. SDK는 루프와 사용자가 구성한 검사를 실행하며, 브라우저나 데스크톱은 사용자가 제공합니다.

Python 및 TypeScript SDK의 브라우저 및 컴퓨터 도구 세트를 사용하면 웹 브라우저나 데스크톱에서 작업하는 에이전트를 구축할 수 있습니다. 이 에이전트는 프로그래밍 인터페이스가 없는 애플리케이션도 사용할 수 있습니다. 브라우저 사용 도구를 통해 Claude는 웹페이지를 탐색하고, 읽고, 상호작용합니다. 컴퓨터 사용 도구를 통해 Claude는 스크린샷으로 데스크톱을 보고 마우스와 키보드를 사용합니다.

SDK는 Claude를 사용자의 브라우저나 데스크톱에 연결합니다. navigate에 대한 URL 정책이나 승인 콜백처럼 사용자가 구성한 검사를 실행합니다. 검사를 통과한 각 호출을 사용자의 드라이버로 보내고, 드라이버가 반환한 내용으로 tool_result를 구성합니다. 도구 러너가 에이전트 루프를 대신 실행해 줄 수 있습니다.

브라우저나 데스크톱을 조작하는 드라이버는 사용자가 제공합니다. 제공업체의 드라이버로 시작하세요. 제공업체의 드라이버를 사용하면 몇 줄의 코드만으로 에이전트를 만들 수 있습니다. 적합한 드라이버가 없다면 Playwright 같은 브라우저 자동화 라이브러리로 직접 작성하세요.

이 페이지의 대부분은 브라우저 도구 세트에 관한 내용입니다. 차이점은 컴퓨터 도구 세트에서 다룹니다.

드라이버를 직접 작성하는 것은 고급 사례이며, 드라이버 구현에서 다룹니다. claude-quickstarts 저장소에도 Python과 TypeScript로 작성된 예제 브라우저 드라이버가 있습니다. 이 드라이버는 "Chrome DevTools Protocol"(Chrome 개발자 도구 프로토콜), 즉 CDP를 통해 Chromium을 제어하며, 프로덕션 코드가 아닙니다.

제공업체의 드라이버 사용하기

파트너 통합에 있는 것과 같은 제공업체의 드라이버를 사용하면 몇 줄의 코드로 에이전트를 구축할 수 있습니다. 이 예제의 제공업체는 가상의 것입니다. example_browser_provider와 ExampleBrowser를 실제 제공업체의 패키지와 드라이버 클래스로 바꾸고, 제공업체 문서에 설명된 대로 설정하세요.

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)

드라이버 인스턴스 자체를 tools 항목으로 전달하세요. 러너는 도구 세트를 닫지 않으므로 하나의 인스턴스로 여러 번의 실행을 처리할 수 있습니다. 작업이 끝나면 직접 닫으세요.

드라이버를 직접 작성하려면 드라이버 구현을 참조하세요.

파트너 통합

Browser Use, Browserbase, Daytona, E2B는 SDK 도구 세트용 자체 드라이버를 제공합니다.

브라우저 도구 세트용 Browser Use 드라이버입니다.

브라우저 도구 세트용 Browserbase Stagehand 드라이버입니다.

컴퓨터 도구 세트용 Daytona 드라이버입니다.

E2B

브라우저 및 컴퓨터 도구 세트용 E2B 드라이버입니다.

파트너들은 GitHub에도 코드를 공개하고 있습니다: Browser Use 빠른 시작, Browserbase 도구 세트, E2B 예제.

드라이버 사용자 지정

이 섹션의 예제는 이 페이지 뒷부분의 고급 예제에 나오는 드라이버인 MyBrowser를 사용합니다.

멤버 활성화 또는 비활성화

configs는 도구 세트 구성에 설명된 멤버별 설정을 받습니다. 변경하는 멤버만 나열하세요:

# read_console도 구현하는 MyBrowser
browser = MyBrowser(
    backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
)

SDK는 코드가 실행되기 전에 비활성화된 멤버에 대한 호출을 거부합니다. 클래스가 execute를 재정의하지 않는 한, 클래스가 구현하지 않은 멤버를 활성화하는 것은 구성 오류입니다.

전후 훅 추가

execute를 재정의하고 부모의 execute를 호출하세요. 그 호출 이전의 코드는 URL 정책, 파일 정책, confirm 이후에 실행되며, 입력을 변경할 수 있습니다. SDK는 변경된 입력을 다시 검사하지 않습니다. 호출 이후의 코드는 결과를 받으며, 결과를 변경할 수 있습니다. 호출을 거부하려면 ToolError를 raise하세요(TypeScript에서는 throw).

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

드라이버 구현

드라이버는 BetaAbstractBrowserToolset20260801의 서브클래스입니다. 여기에서 브라우저가 지원하는 멤버를 재정의하세요. 구현하지 않은 멤버는 비활성화된 상태로 API에 전송됩니다. 그래도 Claude가 이를 호출하면 SDK는 오류를 반환하고 실행은 계속됩니다. execute를 재정의하면 비활성화 상태로 전송되는 멤버가 달라집니다(전후 훅 추가). 각 멤버는 호출 컨텍스트와 BetaBrowserNavigateInput 같은 타입이 지정된 객체 형태의 멤버 입력을 받습니다. 입력 타입은 anthropic.types.beta에서 가져옵니다. 멤버 도구에 각 입력의 필드가 나열되어 있습니다.

TypeScript에서는 SDK가 프로토타입에서 멤버를 찾으므로, 멤버를 화살표 함수 필드가 아닌 메서드로 작성하세요. TypeScript에서는 type 멤버를 type_로 표기합니다. Python에서는 type입니다.

결과 반환

멤버가 반환하는 내용에 따라 Claude가 읽는 내용이 결정됩니다. 성공한 결과는 상태 보고로부터 빌드된 browser_state 블록으로 끝납니다. 오류 결과에는 블록이 포함되지 않습니다.

멤버반환 값Claude가 읽는 내용
screenshot, zoomBetaScreenshotResult이미지 블록 하나
navigateBetaBrowserNavigateResultNavigated 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문자열해당 문자열, 빈 문자열인 경우 (empty)
그 외 모든 멤버없음, 또는 한 줄의 텍스트Clicked. 같은 짧은 확인 메시지와, 그 뒤에 별도의 텍스트 블록으로 표시되는 반환된 줄

브라우저 상태 보고

SDK는 결과를 반환하는 각 호출 후에 _browser_state를 호출하며, 거부되거나 실패한 호출도 포함됩니다. 열려 있는 모든 탭과 마지막 보고 이후 변경된 내용을 반환하세요:

  • 열린 탭과 다운로드 이벤트.
  • 요청 훅이 차단한 각 탐색에 대한 BetaNavigationRefused.
  • 드라이버가 닫은 각 네이티브 대화 상자에 대한 BetaDialogDismissed.

이 모든 항목을 state_changes에 넣으세요. Python에서 BetaNavigationRefused()와 BetaDialogDismissed(kind=..., message=...)는 anthropic.tools.browser에서 가져옵니다. TypeScript에서는 { type: "navigation_refused" }와 { type: "dialog_dismissed", kind, message }입니다.

마지막 두 항목은 API 상태 변경이 아닙니다. SDK는 이를 browser_state 블록 외부의 텍스트로 Claude에게 보고합니다. 거부된 탐색은 모두 합쳐 한 줄로 보고하되 URL은 포함하지 않고, 닫힌 대화 상자는 처음 세 개에 대해 각각 한 줄씩 보고한 다음 나머지는 개수로 보고합니다.

열린 탭이 있을 때는 정확히 하나가 활성 상태여야 합니다. tab_id를 받는 모든 멤버는 해당 ID가 가리키는 탭에서 동작해야 합니다. 보고에 대한 API의 제한은 browser_state로 탭 추적하기에 나열되어 있습니다.

Claude는 각 탭의 URL, 각 다운로드의 URL, navigate가 반환하는 URL을 드라이버가 작성한 그대로 읽습니다. SDK는 이러한 URL을 파싱하지 않습니다. 줄 바꿈과 기타 제어 문자를 공백으로 바꾸고, 양 끝의 공백을 제거하며, 각 URL을 4,096자에서 자릅니다. 따라서 data: URL의 내용, file: URL의 경로, URL에 포함된 사용자 이름이나 비밀번호가 모두 Claude에게 전달됩니다.

오류 처리

멤버 또는 SDK가 발생시킨 예외Claude가 읽는 내용실행
ToolError오류 결과로서의 해당 메시지계속됨
ToolsetUsageError없음중지됨
그 외 모든 예외오류 결과로서 Python에서는 ClassName: message, TypeScript에서는 Error: message계속됨

SDK는 구성 오류, 호출 중 SDK 오용, 또는 close 이후의 호출에 대해 ToolsetUsageError를 발생시킵니다. 또한 _browser_state가 어떤 예외든, 심지어 ToolError라도 발생시키면 이 오류를 발생시킵니다. 도구 러너와 tool_result는 이를 잡지 않으므로 호출자에게 전달됩니다.

SDK는 멤버의 오류 텍스트, left_click 같은 작업이 반환하는 줄, 실패한 다운로드의 오류, 닫힌 대화 상자의 메시지에 포함된 로컬 경로를 숨기지 않습니다.

URL 정책, 파일 정책 또는 confirm 호출 가능 객체에서 발생한 ToolError는 작성된 그대로 Claude에게 전달되므로, 텍스트에 로컬 경로를 포함하지 마세요. 멤버에서 예외를 잡고, 자체 텍스트로 ToolError를 raise하세요(TypeScript에서는 throw).

응답이 스트리밍되는 동안 호출 시작

기본적으로 도구 러너는 Claude의 응답이 끝난 후 해당 턴의 호출을 실행합니다. 조기 시작을 사용하면 응답이 아직 스트리밍되는 동안 브라우저 또는 컴퓨터 호출을 시작할 수 있습니다.

이를 켜려면 고급 예제처럼 도구 러너에 stream=True와 run_tools_eagerly=True를 전달하세요. stream을 사용하면 러너에 대한 루프의 각 반복에서 메시지 대신 스트림을 받습니다.

조기 시작은 호출이 시작될 수 있는 시점을 바꿀 뿐, 도구 세트가 호출을 실행하는 방식은 바꾸지 않습니다:

  • 검사는 여전히 먼저 실행됩니다: SDK는 호출이 실행되기 전에 URL 정책, 파일 정책, confirm을 호출합니다. 조기 시작을 사용하면 응답이 아직 스트리밍되는 동안 confirm을 호출할 수 있습니다.
  • 호출은 여전히 한 번에 하나씩 실행됩니다: 도구 세트는 Claude가 작성한 순서대로 호출을 실행합니다. 실패한 호출은 여전히 해당 턴에서 그 도구 세트의 이후 호출을 중지시킵니다.
  • 시작된 호출은 되돌릴 수 없습니다: 이후 응답이 예를 들어 max_tokens에서 잘리거나 루프가 일찍 중지되더라도 작업은 여전히 수행되며, Claude는 그 결과를 읽지 못합니다.

도구 러너 없이 실행

인스턴스를 tools에 전달하고(TypeScript에서는 browser.toJSON()), 각 멤버 호출에 tool_result로 응답하세요. 브라우저 사용 도구는 첫 번째 실패한 호출에서 중지할 것을 요구합니다(배치 동작).

이 루프는 고급 예제의 MyBrowser와 URL 정책을 사용합니다. 호출이 실패한 후에는 해당 턴의 이후 호출을 실행하지 않고 응답합니다:

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:
                # 호출이 실패하면 해당 턴의 나머지 호출은 실행되지 않고 응답만 반환됩니다.
                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, 그리고 배치 동작에서 요구하는 정확한 텍스트가 포함됩니다. 도구 러너도 동일한 응답을 보냅니다.

고급 예제: 드라이버 직접 작성하기

이 예제는 드라이버 구현의 주요 부분을 하나의 드라이버인 MyBrowser에 담고, 도구 러너와 조기 시작으로 실행합니다. MyBrowser는 navigate, screenshot, left_click과 함께, 모든 드라이버에 필요한 상태 보고인 _browser_state를 구현합니다.

backend는 Playwright 같은 브라우저 자동화 라이브러리를 감싸는 자체 래퍼를 나타냅니다. URL 정책은 예시입니다. URL 정책 작성에서 이를 설명합니다.

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은 "back", "forward", "reload" 또는 URL 정책이
        # 허용한 URL이며, Claude가 작성한 그대로입니다. is_allowed와 마찬가지로 backend.goto는
        # 스킴으로 시작하지 않는 URL에 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: 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:
        # 반환할 것이 없습니다: 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()


ALLOWED_HOSTS = ("example.com", "iana.org")
SCHEME_PREFIX = re.compile(r"[a-z][a-z0-9+.-]*:", re.IGNORECASE)


def is_allowed(url: str) -> bool:
    # 프로덕션 정책이 아닌 예시입니다. "Write a URL policy"를 참조하세요.
    if url.lower() == "about:blank":
        return True
    with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
    # 브라우저는 웹 URL에서 "\"를 "/"로 읽습니다.
    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())

도구 세트를 안전하게 실행

Claude의 다음 작업은 Claude가 읽는 페이지에 따라 달라집니다. 페이지 또는 페이지에 주입된 텍스트는 내부 서비스에 접근하거나 호스트에서 파일을 빼내려고 시도할 수 있습니다. 또한 실제 영향을 미치는 작업을 유발하려고 시도할 수도 있습니다. 일회용 브라우저가 아닌 다른 대상에 대해 드라이버를 실행하기 전에 다음 여섯 단계를 수행하세요:

  1. 작업에 필요하지 않은 모든 navigate URL을 거부하는 URL 정책을 작성하세요.
  2. 드라이버에서 요청을 가로채고, 동일한 규칙으로 각 요청을 검사하세요.
  3. 컨테이너에 egress 정책을 적용하여 드라이버가 볼 수 없는 것을 네트워크가 차단하도록 하세요.
  4. 업로드와 다운로드를 제한하거나, 업로드를 꺼 두세요.
  5. confirm으로 영향이 큰 멤버에 승인을 요구하세요.
  6. 각 세션마다 전용 컨테이너 또는 VM에서 브라우저 호스트를 격리하세요.

SDK는 전달한 정책과 호출 가능 객체를 통해 1, 4, 5단계를 적용합니다. 2, 3, 6단계는 드라이버와 배포 환경에 달려 있습니다. 보안 고려 사항의 예방 조치도 적용됩니다.

URL 정책 작성

URL 정책은 url_policy로 전달하는 함수입니다. 정책은 사용자가 작성하며, 프로덕션에 사용할 수 있도록 준비하는 것도 사용자의 책임입니다. navigate가 실행되기 전에 SDK는 호출의 컨텍스트와 Claude가 작성한 그대로의 URL로 정책을 호출합니다.

URL을 허용하려면 아무것도 반환하지 마세요. 거부하려면 ToolError를 raise하세요(TypeScript에서는 throw). Claude는 오류 메시지를 읽고, navigate는 실행되지 않습니다. 정책은 각 navigate 호출의 URL만 검사합니다. 네트워크 제어 수단이 아닙니다.

다음은 고급 예제의 정책입니다. about:blank와 함께, 두 사이트 및 그 하위 도메인을 http와 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:
    # 예시일 뿐이며 프로덕션 정책이 아닙니다.
    if url.lower() == "about:blank":
        return True
    with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
    # 브라우저는 웹 URL에서 "\"를 "/"로 읽습니다.
    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)

SDK는 navigate에 대해서만 정책을 호출하며, "back", "forward", "reload"에 대해서는 호출하지 않습니다. Claude는 example.com처럼 스킴을 생략할 수 있으므로, 예제는 호스트를 읽기 전에 스킴으로 시작하지 않는 URL에 https://를 추가합니다. navigate는 Claude가 작성한 그대로의 URL을 받습니다. 브라우저가 정책이 검사한 URL을 열도록, 그곳에서도 동일한 검사로 스킴을 추가하세요.

이 예제는 프로덕션 수준이 아닙니다. 프로덕션 정책에는 다음과 같은 영역에서 훨씬 더 많은 것이 필요합니다:

  • Claude가 클릭하는 링크, 제출하는 양식, 리디렉션, 페이지가 자체적으로 보내는 요청. 이는 드라이버만 볼 수 있습니다. 드라이버에서 요청 가로채기를 참조하세요.
  • 공격자가 선택할 수 있는, 페이지 콘텐츠에서 비롯된 호스트 이름과 URL.
  • 단순한 문자열 분할로 호스트 이름을 찾는 대신, 브라우저와 같은 방식으로 각 URL을 파싱하기.
  • URL 스킴. SDK는 이를 검사하지 않으므로, 드라이버가 열 의도가 없는 모든 스킴을 거부하세요.
  • URL 검사로는 볼 수 없는 것을 위한 컨테이너의 egress 규칙. 컨테이너에 egress 정책 적용을 참조하세요.

url_policy=None을 전달하면 SDK는 URL로의 모든 navigate를 거부하므로, 구성에서 읽어 온 None이 실수로 URL을 검사되지 않은 상태로 두는 일은 없습니다. TypeScript에서 undefined는 옵션을 설정하지 않은 상태로 두며, 전달하지 않은 것과 같습니다.

드라이버에서 요청 가로채기

URL 정책은 navigate 호출의 URL만 판단합니다. Claude가 클릭하는 링크, 제출하는 양식, 리디렉션, 또는 허용된 페이지의 이미지나 스크립트는 여전히 정책이 거부할 호스트에 접근할 수 있습니다. 이러한 호스트를 차단하려면 egress 규칙을 사용하세요.

요청 가로채기 기능이 있는 드라이버는 요청 훅, 즉 자동화 라이브러리가 각 요청 전에 호출하는 함수에서 이러한 요청도 검사할 수 있습니다. 규칙을 한 번만 작성하도록 정책이 사용하는 함수를 호출하세요:

class MyBrowser(BetaAbstractBrowserToolset20260801):
    ...

    # context.route("**/*", self._guard)로 Playwright 브라우저 컨텍스트에 등록됩니다
    def _guard(self, route):
        # is_allowed는 "Write a URL policy"에서 작성한 함수입니다
        if not is_allowed(route.request.url):
            return route.abort("blockedbyclient")
        route.continue_()

이와 같은 요청 훅은 WebSocket 핸드셰이크, 서비스 워커 요청, 리디렉션 홉을 보지 못합니다. 서비스 워커를 차단하세요. WebSocket 핸드셰이크와 리디렉션 홉은 이를 볼 수 있는 훅으로 검사하세요. 예제 정책은 ws:// 및 wss:// URL을 거부하므로, WebSocket URL을 is_allowed에 전달하기 전에 ws://를 http://로, wss://를 https://로 바꾸세요.

컨테이너에 egress 정책 적용

가로채기로는 브라우저가 보내는 모든 요청을 볼 수 없으며, URL 정책은 이름이 어디로 확인되는지 보지 못합니다. 컨테이너의 네트워크가 적용하는 "egress policy"(송신 정책)는 두 가지를 모두 처리합니다:

  • IPv4와 IPv6에서 링크 로컬 및 사설 주소 대역을 차단하세요. 여기에는 클라우드 메타데이터 주소 169.254.169.254가 포함됩니다.
  • 작업에 필요한 호스트로의 아웃바운드 연결만 허용하세요. 규칙이 IP 주소와 일치하는 방식이라면, 컨테이너가 시작될 때 허용하는 호스트 이름을 확인(resolve)하세요.
  • DNS는 컨테이너의 리졸버로만 허용하세요.

Kubernetes NetworkPolicy나 클라우드 방화벽처럼 브라우저 컨테이너 외부에서 적용되는 규칙은 컨테이너 내부의 루프백을 보지 못합니다. 이러한 규칙만 있으면 페이지는 그곳에서 수신 대기 중인 모든 것에 접근할 수 있으며, 여기에는 브라우저의 열린 탭 목록과 각 탭을 제어하는 주소를 보여 주는 DevTools 포트도 포함됩니다.

업로드 및 다운로드 제한

file_upload는 기본적으로 꺼져 있습니다. file_policy가 없으면 SDK는 경로나 문서 ID를 제공하는 모든 업로드를 거부합니다. 업로드를 활성화하려면 작업의 파일만 담고 있는 업로드 디렉터리 하나와 함께 BetaLocalFilePolicy를 전달하세요:

from anthropic.tools.browser import BetaLocalFilePolicy

# file_upload도 구현하는 MyBrowser입니다
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,
    ),
)

SDK는 심볼릭 링크를 따라가며 각 업로드 경로를 확인하고, 업로드 루트 외부의 모든 경로를 거부합니다. 파일 정책은 다운로드 디렉터리가 업로드 루트이거나, 업로드 루트 안에 있거나, 업로드 루트를 포함하는 경우 구성 오류를 발생시킵니다. 이 검사는 심볼릭 링크를 놓칠 수 있으므로, 심볼릭 링크로 인해 다운로드 디렉터리가 업로드 루트와 같아지거나 하나가 다른 하나 안에 들어가지 않도록 하세요. SDK는 expose_download_paths가 true이고 파일이 다운로드 디렉터리 안에 있을 때만 상태 보고에 다운로드의 path를 포함합니다.

기본 제공 파일 정책은 SDK를 실행하는 프로세스의 파일 시스템에서 경로를 검사합니다. 이는 해당 파일 시스템을 공유하는 브라우저만 보호합니다. 원격 브라우저의 경우 대신 원격 및 호스팅 브라우저를 따르세요.

다운로드는 다음과 같이 설정하세요:

  • 다운로드 디렉터리를 모드 0700으로 직접 생성하고, noexec,nosuid,nodev로 마운트하세요.
  • 셸이나 파일 도구처럼 Claude가 호출할 수 있는 다른 도구가 디렉터리에 접근할 수 없도록 하세요.
  • download_failed 상태 변경에서 error는 고정된 문구로 작성하세요. 예외의 텍스트에는 경로나 URL이 포함될 수 있습니다.
  • 사람이 결정하기 전까지는 다운로드한 파일을 대화로 읽어 들이거나 실행하지 마세요.

영향이 큰 멤버에 승인 요구

javascript_exec와 file_upload는 기본적으로 꺼져 있습니다. confirm 호출 가능 객체 없이 둘 중 하나를 활성화하면 생성자가 구성 오류를 발생시킵니다. confirm 호출 가능 객체가 있으면 SDK는 실행되려는 모든 호출 전에 이를 호출합니다. 없으면 아무것도 묻지 않습니다. TypeScript에서 confirm: null은 구성 오류를 발생시키지 않으며, SDK는 모든 멤버에 대한 모든 호출을 거부합니다.

호출을 실행하려면 True를, 거부하려면 False를 반환하세요. 호출 가능 객체가 사람에게 묻는 경우, 멤버, 페이지의 URL, 호출의 입력을 보여 주세요. 먼저 URL과 입력에서 출력 가능한 ASCII 범위 밖의 모든 문자를 이스케이프하세요. 둘 다 페이지의 텍스트를 담고 있을 수 있기 때문입니다.

이 예제는 자체 ask_user 함수를 통해 승인이 필요한 두 멤버에 대해 묻고, 나머지는 승인합니다:

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
        # 승인은 이 페이지의 정확히 이 입력에만 적용됩니다.
        key = (name, page, detail)
        if key not in granted and ask_user(question):
            granted.add(key)
        return key in granted

    return confirm


# javascript_exec 및 file_upload도 구현하는 MyBrowser
# file_upload에는 "Confine uploads and downloads"의 file_policy도 필요합니다
browser = MyBrowser(
    backend,
    configs={"javascript_exec": {"enabled": True}, "file_upload": {"enabled": True}},
    confirm=make_confirm(),
    url_policy=url_policy,
)

make_confirm()를 호출할 때마다 승인 내역이 없는 호출 가능 객체가 반환됩니다. 도구 세트마다 한 번씩 호출하고, 각 사용자에게 자체 도구 세트를 제공하세요.

승인은 마지막 상태 보고에 나타난 페이지를 대상으로 하며, 호출이 실행되기 전에 페이지가 변경될 수 있습니다. 구매, 메시지 전송, 약관 동의는 left_click과 type 같은 일반 멤버를 통해 이루어지므로, confirm은 이름으로 이를 구별할 수 없습니다. 사람이 이를 승인하도록 하려면 해당 멤버에 대해서도 물어보세요.

egress 규칙이 브라우저의 아웃바운드 연결을 작업에 필요한 호스트로 제한하지 않는 한 javascript_exec를 활성화하지 마세요. 스크립트는 브라우저가 접근할 수 있는 모든 주소로 페이지의 콘텐츠를 보낼 수 있습니다.

navigate 호출의 javascript: URL도 javascript_exec가 꺼져 있어도 현재 페이지에서 스크립트를 실행합니다. confirm은 그 호출을 navigate로 보므로, 예제의 confirm 호출 가능 객체는 이를 승인합니다. 드라이버는 javascript: URL을 거부해야 합니다. 예제에서는 URL 정책이 confirm이 실행되기 전에 이를 거부합니다.

브라우저 호스트 격리

각 세션마다 최소 권한의 전용 컨테이너 또는 VM에서 브라우저를 실행하세요:

  • root가 아닌 사용자로 실행하고, 브라우저가 허용하는 경우 읽기 전용 루트 파일 시스템을 사용하세요.
  • 구성한 업로드 및 다운로드 디렉터리(있는 경우) 외에는 호스트에서 아무것도 마운트하지 마세요.
  • 자격 증명을 환경에 두지 말고, 새 브라우저 프로필로 시작하세요.
  • Claude가 호출할 수 있는 다른 도구와 파일 시스템을 공유하지 마세요.

API를 호출하는 코드는 API 키와 대화를 보유하므로 브라우저 컨테이너 외부에서 실행하세요. 도구 러너와 tool_result는 모두 해당 코드의 프로세스에서 도구 세트를 실행하므로, 브라우저는 도구 세트의 파일 시스템을 공유하지 않습니다. 브라우저를 원격으로 취급하세요. 원격 및 호스팅 브라우저가 적용됩니다.

페이지 텍스트, 스크린샷, 콘솔 및 네트워크 항목, 탭 제목, 다운로드 이름을 포함하여 페이지가 반환하는 모든 것을 신뢰할 수 없는 것으로 취급하세요.

원격 및 호스팅 브라우저

일부 브라우저는 SDK를 실행하는 프로세스와 파일 시스템을 공유하지 않습니다. 다른 컨테이너에 있는 브라우저, DevTools URL로 접근하는 브라우저, 호스팅 브라우저 서비스의 브라우저가 그 예입니다. 이러한 경우에도 URL 정책, 요청 가로채기, confirm은 여전히 사용자의 프로세스에서 실행됩니다.

호스팅 브라우저의 경우 제공업체가 egress와 호스트 격리를 제어합니다. 자체 egress 정책은 그곳에 적용되지 않으므로, 드라이버의 요청 훅이 브라우저 요청에 대한 유일한 검사 수단입니다. 브라우저의 네트워크가 어디에 접근할 수 있는지 확인하세요.

기본 제공 경로 검사는 원격 브라우저를 보호하지 않습니다. BetaLocalFilePolicy는 SDK를 실행하는 프로세스의 파일 시스템에서 경로를 검사하지만, 브라우저는 자체 파일 시스템을 읽고 씁니다.

SDK는 브라우저가 원격인지 감지할 수 없습니다. 따라서 원격 브라우저의 경우, 드라이버가 자체 BetaFilePolicy로 브라우저가 실행되는 곳에서 업로드 경로를 검사하지 않는 한 file_upload를 꺼 두세요. BetaFilePolicy는 각 업로드의 경로와 문서 ID를 검증하고, Claude가 다운로드의 경로를 볼 수 있는지 결정합니다.

원격 브라우저의 호스트에 업로드 및 다운로드 제한의 다운로드 설정이 되어 있지 않다면, 원격 브라우저가 다운로드를 거부하도록 하세요.

제공업체의 API 키와, 키가 포함될 수 있는 세션의 연결 URL이 로그, 도구 결과, 오류 텍스트에 포함되지 않도록 하세요. 제공업체가 세션을 녹화하는 경우, 녹화본은 Claude가 보고 입력한 모든 것의 또 다른 사본이며, 제공업체의 보존 약관이 적용됩니다.

컴퓨터 도구 세트

BetaAbstractComputerToolset20260801은 컴퓨터 사용 도구를 위한 클래스입니다. 드라이버는 screenshot이나 left_click처럼 도구마다 하나의 메서드를 갖는 서브클래스입니다. 파트너 통합에 있는 파트너의 드라이버에서 시작하세요. 직접 작성하려면 claude-quickstarts 저장소에 Python과 TypeScript로 된 최소 VNC 예제가 있습니다. 이 예제는 VNC를 통해 데스크톱을 제어하며, 프로덕션 코드가 아닙니다.

이 클래스는 configs, confirm, tool_configs를 받으며, 브라우저 클래스와 같은 방식으로 전달합니다. 드라이버 사용자 지정, 오류 처리, 응답이 스트리밍되는 동안 호출 시작, 도구 러너 없이 실행도 이 클래스에 적용되며, 다음과 같은 차이점이 있습니다:

  • 브라우저 전용 옵션: 이 클래스에는 URL 정책, 파일 정책, 상태 보고가 없습니다.
  • configs: 구현하는 모든 도구는 기본적으로 켜져 있습니다. 도구별 설정은 도구 매개변수에 나열되어 있습니다.
  • confirm: 클래스가 type, key, 또는 hold_key를 구현하는 경우, confirm 호출 가능 객체를 전달하거나 configs로 해당 도구를 끄지 않으면 생성자가 구성 오류를 발생시킵니다. execute를 재정의하는 클래스는 세 가지를 모두 구현한 것으로 간주됩니다.
  • 건너뛴 호출: 컴퓨터 호출이 실패한 후, 도구 러너는 해당 턴의 이후 컴퓨터 호출에 Not executed: an earlier computer action in this turn failed.로 응답합니다. 브라우저 호출은 다른 텍스트를 받습니다. 직접 작성한 루프에서는 건너뛴 컴퓨터 호출에 정확히 그 텍스트로 응답하세요(배치 액션).
  • execute: TypeScript에서 execute 재정의는 name을 BetaComputerMemberName으로, input을 BetaComputerMemberInput으로 타입 지정합니다. 둘 다 @anthropic-ai/sdk/resources/beta에서 가져옵니다.
  • 오류 텍스트: SDK는 컴퓨터 도구의 오류 텍스트나 도구가 반환하는 텍스트에 포함된 로컬 경로를 숨기지 않습니다. Claude는 둘 다 4,096자에서 잘린 상태로 읽습니다. 도구에서 예외를 잡고, 자체 텍스트로 ToolError를 raise하세요(TypeScript에서는 throw).

데스크톱 드라이버 작성

이 드라이버는 사용 가능한 작업에 나열된 17개 도구 중 5개를 구현합니다. 구현하지 않은 도구는 드라이버 구현에서 설명하듯이 비활성화된 상태로 API에 전송됩니다. 예제에서 display는 VNC 클라이언트나 xdotool처럼 데스크톱을 제어하는 것을 감싸는 자체 래퍼를 나타내며, ask_user는 사람에게 묻는 자체 함수를 나타냅니다.

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:
        # Claude의 좌표는 이 이미지 내의 픽셀 위치입니다.
        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는 [x, y]이며, 커서 위치를 클릭하려면 None입니다. input.text는
        # 누르고 있을 보조 키(예: "shift")입니다. 반환할 것은 없으며, Claude는 "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,
    # type과 key에 필수입니다. Run the computer toolset safely를 참조하세요
    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)

네 가지 도구에는 입력 필드가 없습니다: screenshot, cursor_position, left_mouse_down, left_mouse_up. 이들을 포함한 모든 입력 타입은 anthropic.types.beta에서 가져옵니다.

TypeScript에서는 도구를 화살표 함수 필드가 아닌 메서드로 작성하세요. 그 이유는 드라이버 구현에서 설명합니다.

좌표 스케일링 및 이미지 크기 조정

Claude가 보내는 모든 지점은 여러분이 반환하는 스크린샷 내의 픽셀 위치입니다. 각 coordinate와 start_coordinate, 그리고 zoom region의 두 모서리가 모두 해당됩니다. SDK는 이 값들을 변경하지 않고 그대로 도구에 전달합니다.

스크린샷의 크기가 디스플레이와 다르다면, 드라이버에서 양방향으로 변환하세요. Claude의 지점은 디스플레이 픽셀로, 여러분이 보고하는 커서 위치는 스크린샷 픽셀로 변환해야 합니다. 세션 전체에서 스크린샷 크기를 동일하게 유지하고, 디스플레이와 같은 종횡비를 사용하세요:

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):
        # 해당 좌표를 거부합니다. 범위 안으로 옮기면 Claude가 선택하지 않은 위치를 클릭하게 됩니다.
        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)

각 coordinate와 start_coordinate에 대해 동작을 수행하기 전에 to_display를 호출하고, cursor_position이 보고하는 위치에 대해서는 to_screenshot를 호출하세요. coordinate가 없는 클릭이나 스크롤은 커서 위치에서 동작하므로 변환할 것이 없습니다. zoom region도 같은 비율로 스케일링하세요. zoom 이미지는 이를 바꾸지 않습니다. Claude가 다음에 보내는 지점은 여전히 전체 스크린샷 내의 픽셀 위치입니다.

SDK는 이미지를 읽거나 크기를 조정하지 않습니다. API는 모델의 제한을 초과하는 이미지를 거부하며, 이 경우 실행이 종료됩니다. 각 스크린샷과 zoom 이미지는 드라이버에서 스케일링하세요. 이미지 한도에 맞게 스크린샷 크기 조정을 참조하세요. 긴 실행에서는 하나의 요청에 20개가 넘는 이미지가 포함될 수 있으며, 이 경우 API는 각 이미지에 더 엄격한 제한을 적용합니다. 스크린샷 기록 관리를 참조하세요.

입력 검사 및 결과

호출의 입력 검사를 SDK에 의존하지 마세요. 드라이버가 사용하는 필드를 직접 검사하고, 실행할 수 없는 호출은 거부하도록 하세요. ToolError를 raise하세요(TypeScript에서는 throw하세요). Claude는 메시지를 읽고 수정된 호출을 보낼 수 있습니다.

두 SDK 모두 duration, repeat 횟수, scroll_amount를 제한하지 않습니다. 컴퓨터 도구 세트를 안전하게 실행하기의 4단계에서 이를 다룹니다.

도구가 반환하는 값에 따라 Claude가 읽는 내용이 결정됩니다:

도구반환값Claude가 읽는 내용
screenshot, zoomBetaScreenshotResult하나의 이미지 블록
cursor_positionBetaComputerCursorPositionResultX={x},Y={y}
그 외 모든 도구없음 또는 한 줄의 텍스트Clicked.와 같은 짧은 확인 메시지, 그리고 별도의 텍스트 블록에 담긴 반환된 줄

컴퓨터 도구 세트를 안전하게 실행하기

화면에 표시되는 내용이 Claude의 다음 동작을 좌우합니다. 페이지나 알림이 Claude로 하여금 잘못된 창에 입력하게 하거나, 실제 영향을 미치는 동작을 실행하게 하려고 시도할 수 있습니다.

일회용 머신이 아닌 환경에서 드라이버를 실행하기 전에 다음 다섯 단계를 수행하세요:

  1. 데스크톱을 격리하세요. 세션마다 자격 증명과 호스트 마운트가 없는 전용 최소 권한 컨테이너 또는 VM에서 실행하세요. 작업에 필요한 애플리케이션만 설치하세요. API를 호출하는 코드는 그 외부에서 실행하세요.
  2. 실제 영향을 미치는 동작은 confirm으로 통제하세요. 구매나 메시지 전송은 평범한 left_click, type 또는 key이며, confirm은 화면이 아니라 도구와 그 입력을 받습니다. 따라서 Claude가 데스크톱에서 접근할 수 있는 애플리케이션을 기준으로 어떤 도구에 승인이 필요한지 선택하세요.
  3. 작업에 필요하지 않다면 터미널, 실행 대화 상자, 런처를 데스크톱에 두지 마세요. 이들 중 하나가 키보드 포커스를 가지고 있으면 type, key, hold_key는 Claude가 입력하는 모든 것을 실행하며, 클릭 한 번으로 프로그램이 시작될 수 있습니다. 작업에 필요하다면 키보드 입력과 클릭 전에 사람에게 확인을 받고, 명령이 작업에 필요한 범위를 넘어서지 못하도록 데스크톱을 격리하세요.
  4. 스크린샷 밖의 지점, 몇 분에 걸친 wait, 매우 큰 repeat 또는 scroll_amount처럼 데스크톱이 따르지 않아야 할 입력은 거부하세요. 값을 범위 안으로 조정하는 대신 ToolError를 raise하세요(TypeScript에서는 throw하세요).
  5. 창 제목과 클립보드 텍스트를 포함하여 화면의 모든 것을 신뢰할 수 없는 것으로 취급하세요. 검사 없이 실행하거나 전달하지 마세요.

SDK는 여러분이 전달하는 호출 가능 객체를 통해 2단계를 적용합니다. 나머지 단계는 여러분의 드라이버와 배포 환경에 달려 있습니다. 보안 고려 사항의 예방 조치도 함께 적용됩니다.

다음 confirm은 여러분의 ask_user 함수를 통해 모든 키보드 입력 전에 사람에게 확인을 받고, 그 외의 모든 호출은 승인합니다. context.member는 도구의 이름입니다. 클릭도 사람이 승인하도록 하려면 ASK_FIRST를 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
    # 표시된 것은 "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)

거부된 호출은 오류 결과를 받으며, 실행은 계속됩니다. 거부된 type 호출의 경우 Claude는 The user did not grant permission to run 'type'. Do not retry it unless the user asks you to.를 읽습니다.

이 confirm은 나중에 추가하는 도구를 포함하여 ASK_FIRST에 없는 모든 도구를 승인합니다. 안전하게 실패(fail closed)하도록 하려면 데스크톱에 영향을 주지 않는 도구(screenshot, zoom, cursor_position, wait)만 승인하고 그 외의 모든 도구에 대해서는 확인을 받으세요.

하나의 요청에서 두 도구 세트 모두 사용하기

두 인스턴스를 모두 tools에 전달하세요. 각 호출에는 browser 또는 computer인 toolset_name이 포함되며, 도구 러너는 해당 호출을 일치하는 인스턴스로 보냅니다. 호출이 실패하면 도구 러너는 해당 턴에서 같은 도구 세트에 대한 이후 호출만 건너뛰므로, 컴퓨터 호출이 실패해도 해당 턴의 브라우저 호출은 건너뛰지 않습니다.

# MyBrowser, backend, url_policy는 고급 예제에서 가져온 것입니다.
# confirm은 "Run the computer toolset safely"에 나온 함수입니다.
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())

직접 작성하는 루프에서는 toolset_name이 호출의 toolset_name과 일치하는 인스턴스의 tool_result로 각 호출에 응답하세요. 각 턴에서 도구 세트별로 실패한 호출을 따로 추적하고, 건너뛴 호출에는 해당 도구 세트 고유의 텍스트로 응답하세요. 브라우저의 텍스트는 도구 러너 없이 실행에 있으며, 컴퓨터의 텍스트는 이 섹션 상단의 건너뛴 호출 항목에 있습니다.

참조

생성자 옵션은 두 SDK에서 동일한 의미를 갖습니다:

PythonTypeScript설정 대상
configsconfigs활성화할 멤버
confirmconfirm각 호출을 승인하거나 거부하는 호출 가능 객체
url_policyurlPolicyURL로의 각 navigate 전에 SDK가 호출하는 URL 정책
file_policyfilePolicy업로드 루트 및 다운로드 경로 노출
tool_configstoolConfigscache_control과 같은 tools 항목의 필드
_browser_state 메서드browserState상태 보고

브라우저 클래스는 표의 모든 옵션을 받습니다. 컴퓨터 클래스는 configs, confirm, tool_configs를 받습니다.

생성 후에는 옵션을 변경할 수 없습니다. Python 브라우저 도구 세트 가이드, TypeScript 브라우저 도구 세트 가이드, Python 컴퓨터 도구 세트 가이드, TypeScript 컴퓨터 도구 세트 가이드에서 각 옵션을 설명합니다. Python 가이드에서는 비동기 클래스인 BetaAsyncAbstractBrowserToolset20260801과 BetaAsyncAbstractComputerToolset20260801도 다룹니다.

제한 사항

  • SDK에는 브라우저, 데스크톱, 기성 드라이버, URL 정책이 포함되어 있지 않습니다: 이들은 사용자가 제공해야 합니다. 파트너 통합, 드라이버 구현, URL 정책 작성을 참조하세요.
  • URL 정책은 navigate 호출만 확인합니다: 페이지가 보내는 요청이나, Claude가 클릭한 링크 또는 리디렉션처럼 다른 방식으로 열리는 페이지는 확인하지 않습니다. 드라이버에서 요청 가로채기를 참조하세요.
  • 승인은 마지막 상태 보고를 기반으로 합니다: 해당 보고 이후 페이지가 변경될 수 있습니다. SDK는 호출이 실행되기 전에 페이지를 다시 검사하지 않습니다.
  • 하나의 도구 세트에 대한 호출은 한 번에 하나씩 실행됩니다: 이 동작은 끌 수 없습니다.
  • SDK는 탭 ID가 고유한지, 탭이 몇 개인지 확인하지 않으며, 활성 탭이 하나인지도 항상 확인하지는 않습니다: API는 이러한 규칙을 위반하는 보고를 거부합니다.
  • 컴퓨터 호출의 경우 confirm은 화면이 아니라 도구와 그 입력을 받습니다: 이것만으로는 특정 지점의 클릭이 무엇을 하는지 알 수 없습니다.
  • SDK는 컴퓨터 도구 세트의 이미지 크기를 조정하지 않습니다: API는 모델의 제한을 초과하는 이미지를 거부합니다. 좌표 스케일링 및 이미지 크기 조정을 참조하세요.

다음 단계

멤버 도구, browser_state 블록, 그리고 도구의 보안 고려 사항.

17개의 도구, 좌표 및 이미지 제한, 그리고 도구의 보안 고려 사항.

SDK가 루프를 실행하는 방식과 SDK가 보내는 메시지를 변경하는 방법.

신뢰할 수 없는 콘텐츠를 읽는 모든 애플리케이션을 위한 가드레일.

Was this page helpful?