---
title: SDK 도구 세트를 사용한 브라우저 및 컴퓨터 사용
url: https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk
description: Python 또는 TypeScript SDK에서 브라우저 사용 도구 또는 컴퓨터 사용 도구를 실행합니다. SDK는 루프와 사용자가 구성한 검사를 실행하며, 브라우저나 데스크톱은 사용자가 제공합니다.
---

Python 및 TypeScript SDK의 브라우저 및 컴퓨터 도구 세트를 사용하면 웹 브라우저나 데스크톱에서 작업하는 에이전트를 구축할 수 있습니다. 이 에이전트는 프로그래밍 인터페이스가 없는 애플리케이션도 사용할 수 있습니다. [브라우저 사용 도구](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool)를 통해 Claude는 웹페이지를 탐색하고, 읽고, 상호작용합니다. [컴퓨터 사용 도구](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/computer-use-tool)를 통해 Claude는 스크린샷으로 데스크톱을 보고 마우스와 키보드를 사용합니다.

SDK는 Claude를 사용자의 브라우저나 데스크톱에 연결합니다. `navigate`에 대한 URL 정책이나 승인 콜백처럼 사용자가 구성한 검사를 실행합니다. 검사를 통과한 각 호출을 사용자의 드라이버로 보내고, 드라이버가 반환한 내용으로 `tool_result`를 구성합니다. [도구 러너](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/tool-runner)가 에이전트 루프를 대신 실행해 줄 수 있습니다.

브라우저나 데스크톱을 조작하는 드라이버는 사용자가 제공합니다. [제공업체의 드라이버](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#use-a-driver-from-a-provider)로 시작하세요. 제공업체의 드라이버를 사용하면 몇 줄의 코드만으로 에이전트를 만들 수 있습니다. 적합한 드라이버가 없다면 Playwright 같은 브라우저 자동화 라이브러리로 직접 작성하세요.

이 페이지의 대부분은 브라우저 도구 세트에 관한 내용입니다. 차이점은 [컴퓨터 도구 세트](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#computer-toolset)에서 다룹니다.

드라이버를 직접 작성하는 것은 고급 사례이며, [드라이버 구현](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver)에서 다룹니다. claude-quickstarts 저장소에도 Python과 TypeScript로 작성된 [예제 브라우저 드라이버](https://github.com/anthropics/claude-quickstarts/tree/main/browser-toolset)가 있습니다. 이 드라이버는 "Chrome DevTools Protocol"(Chrome 개발자 도구 프로토콜), 즉 CDP를 통해 Chromium을 제어하며, 프로덕션 코드가 아닙니다.

<Note>
  브라우저 도구 세트 클래스는 베타 버전이며 [Python SDK](https://github.com/anthropics/anthropic-sdk-python/blob/main/browser-toolset.md)와 [TypeScript SDK](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/browser-toolset.md)에서 사용할 수 있습니다. 컴퓨터 도구 세트 클래스도 같은 SDK에서 베타 버전으로 제공되며, [Python 컴퓨터 도구 세트 가이드](https://github.com/anthropics/anthropic-sdk-python/blob/main/computer-toolset.md)와 [TypeScript 컴퓨터 도구 세트 가이드](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/computer-toolset.md)가 있습니다.
</Note>

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

[파트너 통합](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#partner-integrations)에 있는 것과 같은 제공업체의 드라이버를 사용하면 몇 줄의 코드로 에이전트를 구축할 수 있습니다. 이 예제의 제공업체는 가상의 것입니다. `example_browser_provider` (typescript: `example-browser-provider`)와 `ExampleBrowser`를 [실제 제공업체의](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#partner-integrations) 패키지와 드라이버 클래스로 바꾸고, 제공업체 문서에 설명된 대로 설정하세요.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  from anthropic import Anthropic
  from example_browser_provider import ExampleBrowser  # made-up provider

  client = Anthropic()
  with ExampleBrowser() as browser:
      runner = client.beta.messages.tool_runner(
          model="claude-opus-5-5",
          max_tokens=1024,
          tools=[browser],
          messages=[
              {"role": "user", "content": "Open example.com and tell me its heading."}
          ],
      )
      for message in runner:
          print(message)
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import { ExampleBrowser } from "example-browser-provider"; // made-up provider

  const client = new Anthropic();
  const browser = new ExampleBrowser();
  try {
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [browser],
      messages: [{ role: "user", content: "Open example.com and tell me its heading." }]
    });
    for await (const message of runner) {
      console.log(message);
    }
  } finally {
    await browser.close();
  }
  ```
</CodeGroup>

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

드라이버를 직접 작성하려면 [드라이버 구현](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver)을 참조하세요.

## 파트너 통합

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

<CardGroup cols={2}>
  <Card id="browser-use" title="Browser Use" image="https://platform.claude.com/docs/images/partner-logos/browser-use.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/browser-use-dark.svg" href="https://docs.browser-use.com/open-source/customize/integrations/toolsets-for-claude">
    브라우저 도구 세트용 Browser Use 드라이버입니다.
  </Card>

  <Card id="browserbase" title="Browserbase" image="https://platform.claude.com/docs/images/partner-logos/browserbase.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/browserbase-dark.svg" href="https://docs.stagehand.dev/v4/integrations/agent-frameworks/claude-cua-toolset-quickstart">
    브라우저 도구 세트용 Browserbase Stagehand 드라이버입니다.
  </Card>

  <Card id="daytona" title="Daytona" image="https://platform.claude.com/docs/images/partner-logos/daytona.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/daytona-dark.svg" href="https://www.daytona.io/docs/en/guides/claude/claude-draws-daytona-sandbox/">
    컴퓨터 도구 세트용 Daytona 드라이버입니다.
  </Card>

  <Card id="e2-b" title="E2B" image="https://platform.claude.com/docs/images/partner-logos/e2b.svg" imageDark="https://platform.claude.com/docs/images/partner-logos/e2b-dark.svg" href="https://docs.e2b.dev/agents/claude-toolsets">
    브라우저 및 컴퓨터 도구 세트용 E2B 드라이버입니다.
  </Card>
</CardGroup>

파트너들은 GitHub에도 코드를 공개하고 있습니다: [Browser Use 빠른 시작](https://github.com/browser-use/browser-use/tree/main/examples/integrations/toolsets-for-claude), [Browserbase 도구 세트](https://github.com/browserbase/claude-cua-toolset), [E2B 예제](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/anthropic-computer-use-orangehrm-js).

<Warning>
  일회용 브라우저가 아닌 다른 대상에 대해 드라이버를 실행하기 전에 다음 두 가지를 수행하세요:

  * [URL 정책을 작성하세요](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy). 정책이 없으면 SDK는 어떤 URL도 검사하지 않습니다. 제공업체가 호스팅하는 브라우저의 경우 [원격 및 호스팅 브라우저](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#remote-and-hosted-browsers)도 읽어 보세요.
  * Claude에게 맡기고 싶지 않은 데이터나 작업이 있는 계정에 로그인되어 있지 않은 브라우저 프로필을 사용하세요. [브라우저 호스트 격리](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#isolate-the-browser-host)를 참조하세요.

  나머지 내용은 [도구 세트를 안전하게 실행](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#run-the-toolset-safely)에서 다룹니다.
</Warning>

## 드라이버 사용자 지정

이 섹션의 예제는 이 페이지 뒷부분의 [고급 예제](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#quick-start)에 나오는 드라이버인 `MyBrowser`를 사용합니다.

### 멤버 활성화 또는 비활성화

`configs`는 [도구 세트 구성](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool#configure-the-toolset)에 설명된 멤버별 설정을 받습니다. 변경하는 멤버만 나열하세요:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  # read_console도 구현하는 MyBrowser
  browser = MyBrowser(
      backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
  )
  ```

  ```typescript TypeScript
  // `read_console`도 구현하는 MyBrowser
  const browser = new MyBrowser(backend, {
    configs: { read_console: { enabled: true }, navigate: { enabled: false } }
  });
  ```
</CodeGroup>

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

### 전후 훅 추가

`execute`를 재정의하고 부모의 `execute`를 호출하세요. 그 호출 이전의 코드는 [URL 정책](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy), [파일 정책](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#confine-uploads-and-downloads), [`confirm`](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#gate-consequential-members) 이후에 실행되며, 입력을 변경할 수 있습니다. SDK는 변경된 입력을 다시 검사하지 않습니다. 호출 이후의 코드는 결과를 받으며, 결과를 변경할 수 있습니다. 호출을 거부하려면 `ToolError`를 raise하세요(TypeScript에서는 throw).

`execute`를 재정의하면 Claude에게 제공되는 멤버가 달라집니다. SDK는 모든 멤버를 구현된 것으로 간주하므로, 기본적으로 켜져 있는 모든 멤버가 Claude에게 제공됩니다. [고급 예제](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#quick-start)의 `MyBrowser`는 세 개의 멤버만 처리하므로, 다음 `TracedBrowser`는 처리할 수 없는 멤버까지 Claude에게 제공합니다. 사용하기 전에 `configs`로 해당 멤버를 끄세요.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  import time


  class TracedBrowser(MyBrowser):
      def execute(self, context, name, input):
          started = time.monotonic()
          result = super().execute(context, name, input)
          elapsed_ms = (time.monotonic() - started) * 1000
          call_id = context.tool_use.id if context.tool_use else "-"
          log.info("%s %s %.0fms", call_id, name, elapsed_ms)
          return redact(result) if name == "get_page_text" else result
  ```

  ```typescript TypeScript
  import type { BetaToolsetCallContext } from "@anthropic-ai/sdk/helpers/beta/toolsets";
  import type {
    BetaBrowserMemberInput,
    BetaBrowserMemberName
  } from "@anthropic-ai/sdk/resources/beta";

  class TracedBrowser extends MyBrowser {
    protected override async execute(
      ctx: BetaToolsetCallContext,
      name: BetaBrowserMemberName,
      input: BetaBrowserMemberInput
    ) {
      const started = performance.now();
      const result = await super.execute(ctx, name, input);
      log.info(`${ctx.toolUse?.id ?? "-"} ${name} ${Math.round(performance.now() - started)}ms`);
      return name === "get_page_text" ? redact(result as string) : result;
    }
  }
  ```
</CodeGroup>

## 드라이버 구현

드라이버는 `BetaAbstractBrowserToolset20260801`의 서브클래스입니다. 여기에서 브라우저가 지원하는 멤버를 재정의하세요. 구현하지 않은 멤버는 비활성화된 상태로 API에 전송됩니다. 그래도 Claude가 이를 호출하면 SDK는 오류를 반환하고 실행은 계속됩니다. `execute`를 재정의하면 비활성화 상태로 전송되는 멤버가 달라집니다([전후 훅 추가](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#add-before-and-after-hooks)). 각 멤버는 호출 컨텍스트와 `BetaBrowserNavigateInput` 같은 타입이 지정된 객체 형태의 멤버 입력을 받습니다. 입력 타입은 `anthropic.types.beta` (typescript: `@anthropic-ai/sdk/resources/beta`)에서 가져옵니다. [멤버 도구](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool#member-tools)에 각 입력의 필드가 나열되어 있습니다.

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

### 결과 반환

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

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

### 브라우저 상태 보고

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

* 열린 탭과 다운로드 이벤트.
* [요청 훅](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#intercept-requests-in-the-driver)이 차단한 각 탐색에 대한 `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`로 탭 추적하기](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool#track-tabs-and-page-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` (typescript: `browserState`)가 어떤 예외든, 심지어 `ToolError`라도 발생시키면 이 오류를 발생시킵니다. 도구 러너와 `tool_result` (typescript: `toolResult`)는 이를 잡지 않으므로 호출자에게 전달됩니다.

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

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

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

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

이를 켜려면 [고급 예제](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#quick-start)처럼 도구 러너에 `stream=True` (typescript: `stream: true`)와 `run_tools_eagerly=True` (typescript: `runToolsEagerly: true`)를 전달하세요. `stream`을 사용하면 러너에 대한 루프의 각 반복에서 메시지 대신 스트림을 받습니다.

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

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

### 도구 러너 없이 실행

인스턴스를 `tools`에 전달하고(TypeScript에서는 `browser.toJSON()`), 각 멤버 호출에 `tool_result` (typescript: `toolResult`)로 응답하세요. 브라우저 사용 도구는 첫 번째 실패한 호출에서 중지할 것을 요구합니다([배치 동작](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool#batch-actions)).

이 루프는 [고급 예제](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#quick-start)의 `MyBrowser`와 URL 정책을 사용합니다. 호출이 실패한 후에는 해당 턴의 이후 호출을 실행하지 않고 응답합니다:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  from anthropic.types.beta import BetaMessageParam, BetaToolResultBlockParam

  NOT_EXECUTED = "Not executed: an earlier action in this turn failed."
  MAX_TURNS = 10

  with MyBrowser(backend, url_policy=url_policy) as browser:
      messages: list[BetaMessageParam] = [{"role": "user", "content": "Open example.com"}]
      for _ in range(MAX_TURNS):
          response = client.beta.messages.create(
              model="claude-opus-5-5",
              max_tokens=1024,
              tools=[browser],
              messages=messages,
          )
          messages.append({"role": "assistant", "content": response.content})
          calls = [
              block
              for block in response.content
              if block.type == "tool_use" and block.toolset_name == browser.toolset_name
          ]
          if not calls:
              break
          results: list[BetaToolResultBlockParam] = []
          failed = False
          for call in calls:
              if failed:
                  # 호출이 실패하면 해당 턴의 나머지 호출은 실행되지 않고 응답만 반환됩니다.
                  results.append(
                      {
                          "type": "tool_result",
                          "tool_use_id": call.id,
                          "toolset_name": call.toolset_name,
                          "content": NOT_EXECUTED,
                          "is_error": True,
                      }
                  )
                  continue
              result = browser.tool_result(call)
              failed = bool(result.get("is_error"))
              results.append(result)
          messages.append({"role": "user", "content": results})
  ```

  ```typescript TypeScript
  const NOT_EXECUTED = "Not executed: an earlier action in this turn failed.";
  const MAX_TURNS = 10;

  const browser = new MyBrowser(backend, { urlPolicy });
  try {
    const messages: Anthropic.Beta.BetaMessageParam[] = [
      { role: "user", content: "Open example.com" }
    ];
    for (let turn = 0; turn < MAX_TURNS; turn++) {
      const response = await client.beta.messages.create({
        model: "claude-opus-5-5",
        max_tokens: 1024,
        tools: [browser.toJSON()],
        messages
      });
      messages.push({ role: "assistant", content: response.content });
      const calls = response.content.filter(
        (block): block is Anthropic.Beta.BetaToolUseBlock =>
          block.type === "tool_use" && block.toolset_name === browser.toolsetName
      );
      if (calls.length === 0) {
        break;
      }
      const results: Anthropic.Beta.BetaToolResultBlockParam[] = [];
      let failed = false;
      for (const call of calls) {
        if (failed) {
          // 호출이 실패하면 해당 턴의 나머지 호출은 실행되지 않고 응답만 반환됩니다.
          results.push({
            type: "tool_result",
            tool_use_id: call.id,
            toolset_name: call.toolset_name,
            content: NOT_EXECUTED,
            is_error: true
          });
          continue;
        }
        const result = await browser.toolResult(call);
        failed = result.is_error === true;
        results.push(result);
      }
      messages.push({ role: "user", content: results });
    }
  } finally {
    await browser.close();
  }
  ```
</CodeGroup>

건너뛴 호출에 대한 각 응답에는 `is_error`, 호출의 `toolset_name`, 그리고 [배치 동작](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool#batch-actions)에서 요구하는 정확한 텍스트가 포함됩니다. 도구 러너도 동일한 응답을 보냅니다.

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

이 예제는 [드라이버 구현](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver)의 주요 부분을 하나의 드라이버인 `MyBrowser`에 담고, 도구 러너와 [조기 시작](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#start-calls-while-the-response-streams)으로 실행합니다. `MyBrowser`는 `navigate`, `screenshot`, `left_click`과 함께, 모든 드라이버에 필요한 상태 보고인 `_browser_state` (typescript: `browserState`)를 구현합니다.

`backend`는 Playwright 같은 브라우저 자동화 라이브러리를 감싸는 자체 래퍼를 나타냅니다. URL 정책은 예시입니다. [URL 정책 작성](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy)에서 이를 설명합니다.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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())
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import {
    BetaAbstractBrowserToolset20260801,
    type BetaBrowserNavigateResult,
    type BetaBrowserToolsetOptions,
    type BetaScreenshotResult,
    type BetaToolsetCallContext,
    type BetaURLContext,
    ToolError
  } from "@anthropic-ai/sdk/helpers/beta/toolsets";
  import type {
    BetaBrowserLeftClickInput,
    BetaBrowserNavigateInput,
    BetaBrowserScreenshotInput
  } from "@anthropic-ai/sdk/resources/beta";

  class MyBrowser extends BetaAbstractBrowserToolset20260801 {
    constructor(
      private backend: Backend,
      options: Omit<BetaBrowserToolsetOptions, "browserState"> = {}
    ) {
      super({
        ...options,
        // 필수: 열려 있는 모든 탭과 마지막 보고 이후 변경된 내용입니다.
        browserState: () => ({
          tabs: backend.tabs().map((tab) => ({
            tab_id: tab.id,
            title: tab.title,
            url: tab.url,
            active: tab.id === backend.active
          })),
          state_changes: backend.drainChanges()
        })
      });
    }

    protected override async navigate(
      ctx: BetaToolsetCallContext,
      input: BetaBrowserNavigateInput
    ): Promise<BetaBrowserNavigateResult> {
      // input.url은 "back", "forward", "reload" 또는 URL 정책이
      // 허용한 URL이며, Claude가 작성한 그대로입니다. isAllowed와 마찬가지로 backend.goto는
      // 스킴으로 시작하지 않는 URL에 https://를 추가합니다.
      const page = await this.backend.goto(input.url, input.tab_id);
      return { url: page.url, status: page.status, title: page.title };
    }

    protected override async screenshot(
      ctx: BetaToolsetCallContext,
      input: BetaBrowserScreenshotInput
    ): Promise<BetaScreenshotResult> {
      return { data: await this.backend.pngBase64(input.tab_id), mediaType: "image/png" };
    }

    protected override async left_click(
      ctx: BetaToolsetCallContext,
      input: BetaBrowserLeftClickInput
    ): Promise<void> {
      // 반환할 값이 없습니다: Claude는 "Clicked."를 읽습니다
      await this.backend.click(input.target, input.tab_id);
    }

    override async close(): Promise<void> {
      await super.close(); // first, so no call is still using the browser when it closes
      if (!this.backend.closed) {
        await this.backend.close();
      }
    }
  }

  const ALLOWED_HOSTS = ["example.com", "iana.org"];
  const SCHEME_PREFIX = /^[a-z][a-z0-9+.-]*:/i;

  // 프로덕션 정책이 아닌 예시입니다. "Write a URL policy"를 참조하세요.
  function isAllowed(url: string): boolean {
    if (url.toLowerCase() === "about:blank") {
      return true;
    }
    const withScheme = SCHEME_PREFIX.test(url) ? url : `https://${url}`;
    // 브라우저는 웹 URL에서 "\"를 "/"로 읽습니다.
    const parsed = URL.parse(withScheme.replaceAll("\\", "/"));
    const host = parsed?.hostname ?? "";
    const listed = ALLOWED_HOSTS.some((name) => host === name || host.endsWith(`.${name}`));
    return (parsed?.protocol === "http:" || parsed?.protocol === "https:") && listed;
  }

  function urlPolicy(ctx: BetaURLContext, url: string): void {
    if (!isAllowed(url)) {
      throw new ToolError(`blocked: ${url} is not on an allowed host`);
    }
  }

  const client = new Anthropic();
  const browser = new MyBrowser(backend, { urlPolicy });
  try {
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [browser],
      messages: [{ role: "user", content: "Open example.com and tell me the page heading." }],
      stream: true,
      runToolsEagerly: true // so a call can start before the response ends
    });
    for await (const stream of runner) {
      console.log(await stream.finalMessage());
    }
  } finally {
    await browser.close();
  }
  ```
</CodeGroup>

## 도구 세트를 안전하게 실행

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

1. 작업에 필요하지 않은 모든 `navigate` URL을 거부하는 [URL 정책을 작성](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy)하세요.
2. [드라이버에서 요청을 가로채고](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#intercept-requests-in-the-driver), 동일한 규칙으로 각 요청을 검사하세요.
3. [컨테이너에 egress 정책을 적용하여](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container) 드라이버가 볼 수 없는 것을 네트워크가 차단하도록 하세요.
4. [업로드와 다운로드를 제한하거나](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#confine-uploads-and-downloads), 업로드를 꺼 두세요.
5. `confirm`으로 [영향이 큰 멤버에 승인을 요구하세요](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#gate-consequential-members).
6. 각 세션마다 전용 컨테이너 또는 VM에서 [브라우저 호스트를 격리하세요](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#isolate-the-browser-host).

SDK는 전달한 정책과 호출 가능 객체를 통해 1, 4, 5단계를 적용합니다. 2, 3, 6단계는 드라이버와 배포 환경에 달려 있습니다. [보안 고려 사항](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool#security-considerations)의 예방 조치도 적용됩니다.

<Note>
  [보안 고려 사항](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool#security-considerations)에 설명된 Anthropic의 프롬프트 인젝션 분류기는 현재 Amazon Bedrock을 통해 전송된 브라우저 도구 세트 요청에서는 실행되지 않습니다.
</Note>

### URL 정책 작성

<Warning>
  URL 정책이 없으면 SDK는 어떤 URL도 검사하지 않으며, API는 Claude가 여는 URL을 필터링하지 않습니다. 정책이 있더라도 Claude가 클릭하는 링크나 리디렉션은 브라우저를 루프백(`localhost`, `127.0.0.1`), 클라우드 메타데이터 주소 `169.254.169.254` 같은 링크 로컬 주소, 사설 네트워크 대역으로 이동시킬 수 있습니다. [컨테이너의 egress 규칙](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container)은 링크 로컬 주소와 사설 주소를 차단할 수 있습니다. 컨테이너 외부에서 적용되는 규칙은 루프백을 차단할 수 없습니다.

  SDK는 URL의 스킴을 검사하지 않으므로, 드라이버는 `javascript:`, `view-source:`, `data:`, `file:` 등 열 의도가 없는 모든 스킴을 거부해야 합니다. 대부분의 드라이버에는 `http`와 `https`만 필요합니다. 아래 예제 정책은 `http` 및 `https` URL과 `about:blank`만 허용합니다.

  `file:` URL을 사용하면 Claude가 브라우저 호스트의 파일을 읽을 수 있습니다. `javascript:` URL은 `javascript_exec`가 꺼져 있어도 현재 페이지에서 스크립트를 실행합니다. egress 규칙은 둘 다 막지 못합니다.
</Warning>

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

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

다음은 [고급 예제](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#quick-start)의 정책입니다. `about:blank`와 함께, 두 사이트 및 그 하위 도메인을 `http`와 `https`로 허용합니다:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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)
  ```

  ```typescript TypeScript
  import { type BetaURLContext, ToolError } from "@anthropic-ai/sdk/helpers/beta/toolsets";

  const ALLOWED_HOSTS = ["example.com", "iana.org"];
  const SCHEME_PREFIX = /^[a-z][a-z0-9+.-]*:/i;

  // 예시일 뿐이며 프로덕션 정책이 아닙니다.
  function isAllowed(url: string): boolean {
    if (url.toLowerCase() === "about:blank") {
      return true;
    }
    const withScheme = SCHEME_PREFIX.test(url) ? url : `https://${url}`;
    // 브라우저는 웹 URL의 "\"를 "/"로 해석합니다.
    const parsed = URL.parse(withScheme.replaceAll("\\", "/"));
    const host = parsed?.hostname ?? "";
    const listed = ALLOWED_HOSTS.some((name) => host === name || host.endsWith(`.${name}`));
    return (parsed?.protocol === "http:" || parsed?.protocol === "https:") && listed;
  }

  function urlPolicy(ctx: BetaURLContext, url: string): void {
    if (!isAllowed(url)) {
      throw new ToolError(`blocked: ${url} is not on an allowed host`);
    }
  }

  const browser = new MyBrowser(backend, { urlPolicy });
  ```
</CodeGroup>

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

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

* Claude가 클릭하는 링크, 제출하는 양식, 리디렉션, 페이지가 자체적으로 보내는 요청. 이는 드라이버만 볼 수 있습니다. [드라이버에서 요청 가로채기](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#intercept-requests-in-the-driver)를 참조하세요.
* 공격자가 선택할 수 있는, 페이지 콘텐츠에서 비롯된 호스트 이름과 URL.
* 단순한 문자열 분할로 호스트 이름을 찾는 대신, 브라우저와 같은 방식으로 각 URL을 파싱하기.
* URL 스킴. SDK는 이를 검사하지 않으므로, 드라이버가 열 의도가 없는 모든 스킴을 거부하세요.
* URL 검사로는 볼 수 없는 것을 위한 컨테이너의 egress 규칙. [컨테이너에 egress 정책 적용](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container)을 참조하세요.

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

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

URL 정책은 `navigate` 호출의 URL만 판단합니다. Claude가 클릭하는 링크, 제출하는 양식, 리디렉션, 또는 허용된 페이지의 이미지나 스크립트는 여전히 정책이 거부할 호스트에 접근할 수 있습니다. 이러한 호스트를 차단하려면 [egress 규칙](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container)을 사용하세요.

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

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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_()
  ```

  ```typescript TypeScript
  // 드라이버 클래스에서 Playwright 브라우저 컨텍스트에 적용합니다.
  // isAllowed는 "URL 정책 작성"에서 만든 함수입니다.
  await context.route("**/*", (route) =>
    isAllowed(route.request().url()) ? route.continue() : route.abort("blockedbyclient")
  );
  ```
</CodeGroup>

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

### 컨테이너에 egress 정책 적용

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

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

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

### 업로드 및 다운로드 제한

`file_upload`는 기본적으로 꺼져 있습니다. `file_policy` (typescript: `filePolicy`)가 없으면 SDK는 경로나 [문서 ID](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool#upload-files)를 제공하는 모든 업로드를 거부합니다. 업로드를 활성화하려면 작업의 파일만 담고 있는 업로드 디렉터리 하나와 함께 `BetaLocalFilePolicy` (typescript: `BetaNodeFilePolicy`)를 전달하세요:

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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,
      ),
  )
  ```

  ```typescript TypeScript
  import { BetaNodeFilePolicy } from "@anthropic-ai/sdk/helpers/beta/toolsets/node";

  // file_upload도 구현하는 MyBrowser
  const browser = new MyBrowser(backend, {
    configs: { file_upload: { enabled: true } },
    confirm: makeConfirm(), // required for file_upload; see Gate consequential members
    urlPolicy,
    filePolicy: new BetaNodeFilePolicy({
      uploadRoots: ["/task/uploads"],
      downloadDir: "/task/downloads",
      exposeDownloadPaths: false
    })
  });
  ```
</CodeGroup>

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

기본 제공 파일 정책은 SDK를 실행하는 프로세스의 파일 시스템에서 경로를 검사합니다. 이는 해당 파일 시스템을 공유하는 브라우저만 보호합니다. 원격 브라우저의 경우 대신 [원격 및 호스팅 브라우저](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#remote-and-hosted-browsers)를 따르세요.

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

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

### 영향이 큰 멤버에 승인 요구

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

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

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

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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,
  )
  ```

  ```typescript TypeScript
  import type { BetaConfirmContext } from "@anthropic-ai/sdk/helpers/beta/toolsets";

  const GATED = new Set(["javascript_exec", "file_upload"]);

  /** A value as JSON, with every character outside printable ASCII escaped. */
  const shown = (value: unknown): string =>
    JSON.stringify(value, null, 2).replace(
      /[^\n\x20-\x7e]/g,
      (match) => `\\u${match.charCodeAt(0).toString(16).padStart(4, "0")}`
    );

  const makeConfirm = () => {
    const granted = new Set<string>();
    return async (ctx: BetaConfirmContext): Promise<boolean> => {
      const name = ctx.member;
      if (!GATED.has(name)) {
        return true;
      }
      const detail = shown(ctx.input);
      const page = ctx.tabURL;
      const where = page ? shown(page) : "a tab with no URL";
      const question = `Allow ${name} on ${where}?\n${detail}`;
      const scheme = page === undefined ? undefined : URL.parse(page)?.protocol;
      if (scheme !== "http:" && scheme !== "https:") {
        return askUser(question); // no web origin: ask every time
      }
      // 승인은 이 페이지의 정확히 이 입력에만 적용됩니다.
      const key = `${name} ${page} ${detail}`;
      if (!granted.has(key) && (await askUser(question))) {
        granted.add(key);
      }
      return granted.has(key);
    };
  };

  // javascript_exec 및 file_upload도 구현하는 MyBrowser
  // file_upload에는 "Confine uploads and downloads"의 filePolicy도 필요합니다
  const browser = new MyBrowser(backend, {
    configs: { javascript_exec: { enabled: true }, file_upload: { enabled: true } },
    confirm: makeConfirm(),
    urlPolicy
  });
  ```
</CodeGroup>

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

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

[egress 규칙](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#put-egress-policy-on-the-container)이 브라우저의 아웃바운드 연결을 작업에 필요한 호스트로 제한하지 않는 한 `javascript_exec`를 활성화하지 마세요. 스크립트는 브라우저가 접근할 수 있는 모든 주소로 페이지의 콘텐츠를 보낼 수 있습니다.

`navigate` 호출의 `javascript:` URL도 `javascript_exec`가 꺼져 있어도 현재 페이지에서 스크립트를 실행합니다. `confirm`은 그 호출을 `navigate`로 보므로, 예제의 `confirm` 호출 가능 객체는 이를 승인합니다. 드라이버는 `javascript:` URL을 거부해야 합니다. 예제에서는 [URL 정책](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#write-a-url-policy)이 `confirm`이 실행되기 전에 이를 거부합니다.

### 브라우저 호스트 격리

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

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

<Warning>
  브라우저 프로필은 Claude에게 맡기지 않을 데이터나 작업이 있는 계정에 로그인되어 있어서는 안 됩니다. `javascript_exec`는 페이지 자체의 권한, 즉 페이지의 쿠키, 스토리지, 로그인된 세션으로 실행됩니다.
</Warning>

API를 호출하는 코드는 API 키와 대화를 보유하므로 브라우저 컨테이너 외부에서 실행하세요. 도구 러너와 `tool_result` (typescript: `toolResult`)는 모두 해당 코드의 프로세스에서 도구 세트를 실행하므로, 브라우저는 도구 세트의 파일 시스템을 공유하지 않습니다. 브라우저를 원격으로 취급하세요. [원격 및 호스팅 브라우저](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#remote-and-hosted-browsers)가 적용됩니다.

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

### 원격 및 호스팅 브라우저

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

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

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

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

원격 브라우저의 호스트에 [업로드 및 다운로드 제한](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#confine-uploads-and-downloads)의 다운로드 설정이 되어 있지 않다면, 원격 브라우저가 다운로드를 거부하도록 하세요.

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

## 컴퓨터 도구 세트

`BetaAbstractComputerToolset20260801`은 [컴퓨터 사용 도구](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/computer-use-tool)를 위한 클래스입니다. 드라이버는 `screenshot`이나 `left_click`처럼 도구마다 하나의 메서드를 갖는 서브클래스입니다. [파트너 통합](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#partner-integrations)에 있는 파트너의 드라이버에서 시작하세요. 직접 작성하려면 claude-quickstarts 저장소에 Python과 TypeScript로 된 [최소 VNC 예제](https://github.com/anthropics/claude-quickstarts/tree/main/computer-toolset)가 있습니다. 이 예제는 VNC를 통해 데스크톱을 제어하며, 프로덕션 코드가 아닙니다.

이 클래스는 `configs`, `confirm`, `tool_configs` (typescript: `toolConfigs`)를 받으며, 브라우저 클래스와 같은 방식으로 전달합니다. [드라이버 사용자 지정](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#customize-a-driver), [오류 처리](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#handle-errors), [응답이 스트리밍되는 동안 호출 시작](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#start-calls-while-the-response-streams), [도구 러너 없이 실행](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#run-without-the-tool-runner)도 이 클래스에 적용되며, 다음과 같은 차이점이 있습니다:

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

### 데스크톱 드라이버 작성

이 드라이버는 [사용 가능한 작업](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/computer-use-tool#available-actions)에 나열된 17개 도구 중 5개를 구현합니다. 구현하지 않은 도구는 [드라이버 구현](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver)에서 설명하듯이 비활성화된 상태로 API에 전송됩니다. 예제에서 `display`는 VNC 클라이언트나 `xdotool`처럼 데스크톱을 제어하는 것을 감싸는 자체 래퍼를 나타내며, `ask_user` (typescript: `askUser`)는 사람에게 묻는 자체 함수를 나타냅니다.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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)
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import {
    BetaAbstractComputerToolset20260801,
    type BetaComputerCursorPositionResult,
    type BetaComputerToolsetOptions,
    type BetaScreenshotResult,
    type BetaToolsetCallContext
  } from "@anthropic-ai/sdk/helpers/beta/toolsets";
  import type {
    BetaComputerCursorPositionInput,
    BetaComputerKeyInput,
    BetaComputerLeftClickInput,
    BetaComputerScreenshotInput,
    BetaComputerTypeInput
  } from "@anthropic-ai/sdk/resources/beta";

  class MyDesktop extends BetaAbstractComputerToolset20260801 {
    constructor(
      private display: Display,
      options: BetaComputerToolsetOptions = {}
    ) {
      super(options);
    }

    protected override async screenshot(
      ctx: BetaToolsetCallContext,
      input: BetaComputerScreenshotInput
    ): Promise<BetaScreenshotResult> {
      // Claude의 좌표는 이 이미지 내의 픽셀 위치입니다.
      return { data: await this.display.pngBase64(), mediaType: "image/png" };
    }

    protected override async cursor_position(
      ctx: BetaToolsetCallContext,
      input: BetaComputerCursorPositionInput
    ): Promise<BetaComputerCursorPositionResult> {
      const { x, y } = await this.display.cursor();
      return { x, y };
    }

    protected override async left_click(
      ctx: BetaToolsetCallContext,
      input: BetaComputerLeftClickInput
    ): Promise<void> {
      // input.coordinate는 [x, y]이며, 커서 위치를 클릭하려면 null 또는 undefined입니다. input.text는
      // 누르고 있을 수정자 키(예: "shift")입니다. 반환할 값은 없으며, Claude는 "Clicked."를 읽습니다
      await this.display.click(input.coordinate, input.text);
    }

    // type 도구는 TypeScript에서 type_로 표기합니다.
    protected override async type_(
      ctx: BetaToolsetCallContext,
      input: BetaComputerTypeInput
    ): Promise<void> {
      await this.display.type(input.text);
    }

    protected override async key(
      ctx: BetaToolsetCallContext,
      input: BetaComputerKeyInput
    ): Promise<void> {
      await this.display.press(input.text, input.repeat ?? 1);
    }

    override async close(): Promise<void> {
      await super.close(); // first, so no call is still using the desktop when it closes
      await this.display.close();
    }
  }

  const client = new Anthropic();
  const desktop = new MyDesktop(display, {
    // type 및 key에 필요합니다. Run the computer toolset safely를 참조하세요
    confirm: (ctx) => askUser(`Allow the "${ctx.member}" tool?`)
  });
  try {
    const runner = client.beta.messages.toolRunner({
      model: "claude-opus-5-5",
      max_tokens: 1024,
      tools: [desktop],
      messages: [{ role: "user", content: "Open the calculator and compute 17 * 23." }]
    });
    for await (const message of runner) {
      console.log(message);
    }
  } finally {
    await desktop.close();
  }
  ```
</CodeGroup>

<Warning>
  이 예제의 `confirm`은 드라이버가 실행하는 모든 호출 전에 사람에게 묻습니다. 일회용 머신이 아닌 대상에 드라이버를 실행하기 전에 [컴퓨터 도구 세트를 안전하게 실행하기](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#run-the-computer-toolset-safely)을 읽어 보세요.
</Warning>

네 가지 도구에는 입력 필드가 없습니다: `screenshot`, `cursor_position`, `left_mouse_down`, `left_mouse_up`. 이들을 포함한 모든 입력 타입은 `anthropic.types.beta` (typescript: `@anthropic-ai/sdk/resources/beta`)에서 가져옵니다.

TypeScript에서는 도구를 화살표 함수 필드가 아닌 메서드로 작성하세요. 그 이유는 [드라이버 구현](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#implement-a-driver)에서 설명합니다.

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

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

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

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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)
  ```

  ```typescript TypeScript
  import { ToolError } from "@anthropic-ai/sdk/helpers/beta/toolsets";

  const SCREENSHOT_SIZE = { width: 1280, height: 720 }; // the size of every full screenshot
  const DISPLAY_SIZE = { width: 2560, height: 1440 };

  function toDisplay(coordinate: number[]): [number, number] {
    const [x, y] = coordinate;
    const { width, height } = SCREENSHOT_SIZE;
    if (x < 0 || x >= width || y < 0 || y >= height) {
      // 해당 좌표를 거부합니다. 범위 안으로 옮기면 Claude가 선택하지 않은 위치를 클릭하게 됩니다.
      throw new ToolError(`[${x}, ${y}] is outside the ${width}x${height} screenshot`);
    }
    return [
      Math.floor((x * DISPLAY_SIZE.width) / width),
      Math.floor((y * DISPLAY_SIZE.height) / height)
    ];
  }

  function toScreenshot(x: number, y: number): [number, number] {
    return [
      Math.floor((x * SCREENSHOT_SIZE.width) / DISPLAY_SIZE.width),
      Math.floor((y * SCREENSHOT_SIZE.height) / DISPLAY_SIZE.height)
    ];
  }

  toDisplay([640, 360]); // [1280, 720]
  ```
</CodeGroup>

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

SDK는 이미지를 읽거나 크기를 조정하지 않습니다. API는 모델의 제한을 초과하는 이미지를 거부하며, 이 경우 실행이 종료됩니다. 각 스크린샷과 `zoom` 이미지는 드라이버에서 스케일링하세요. [이미지 한도에 맞게 스크린샷 크기 조정](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/computer-use-tool#handle-coordinate-scaling-for-higher-resolutions)을 참조하세요. 긴 실행에서는 하나의 요청에 20개가 넘는 이미지가 포함될 수 있으며, 이 경우 API는 각 이미지에 더 엄격한 제한을 적용합니다. [스크린샷 기록 관리](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/computer-use-tool#manage-screenshot-history)를 참조하세요.

### 입력 검사 및 결과

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

두 SDK 모두 `duration`, `repeat` 횟수, `scroll_amount`를 제한하지 않습니다. [컴퓨터 도구 세트를 안전하게 실행하기](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#run-the-computer-toolset-safely)의 4단계에서 이를 다룹니다.

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

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

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

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

<Warning>
  `type`, `key` 또는 `hold_key`를 구현하는 드라이버에는 `confirm` 호출 가능 객체가 필요합니다. 이것이 없으면 `configs`로 해당 도구들을 끄지 않는 한 생성자가 구성 오류를 발생시킵니다. SDK 자체는 누구에게도 묻지 않습니다. `confirm`이 승인하는 모든 호출을 실행하며, `confirm`이 없으면 모든 클릭을 포함한 모든 호출을 실행합니다. 여러분이 구현하는 모든 컴퓨터 도구는 기본적으로 활성화되어 있습니다.
</Warning>

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

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단계를 적용합니다. 나머지 단계는 여러분의 드라이버와 배포 환경에 달려 있습니다. [보안 고려 사항](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/computer-use-tool#security-considerations)의 예방 조치도 함께 적용됩니다.

다음 `confirm`은 여러분의 `ask_user` (typescript: `askUser`) 함수를 통해 모든 키보드 입력 전에 사람에게 확인을 받고, 그 외의 모든 호출은 승인합니다. `context.member` (typescript: `ctx.member`)는 도구의 이름입니다. 클릭도 사람이 승인하도록 하려면 `ASK_FIRST`를 `KEYBOARD | CLICKS` (typescript: `new Set([...KEYBOARD, ...CLICKS])`)로 설정하세요.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  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)
  ```

  ```typescript TypeScript
  import type { BetaComputerConfirmContext } from "@anthropic-ai/sdk/helpers/beta/toolsets";

  const KEYBOARD = ["type", "key", "hold_key"];
  const CLICKS = [
    "left_click",
    "right_click",
    "middle_click",
    "double_click",
    "triple_click",
    "left_click_drag",
    "left_mouse_down",
    "left_mouse_up"
  ];
  const ASK_FIRST = new Set(KEYBOARD);

  async function confirm(ctx: BetaComputerConfirmContext): Promise<boolean> {
    if (!ASK_FIRST.has(ctx.member)) {
      return true;
    }
    // 표시된 것은 "Gate consequential members"의 함수입니다.
    return askUser(`Allow the "${ctx.member}" tool with this input?\n${shown(ctx.input)}`);
  }

  const desktop = new MyDesktop(display, { confirm });
  ```
</CodeGroup>

거부된 호출은 오류 결과를 받으며, 실행은 계속됩니다. 거부된 `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`이 포함되며, 도구 러너는 해당 호출을 일치하는 인스턴스로 보냅니다. 호출이 실패하면 도구 러너는 해당 턴에서 같은 도구 세트에 대한 이후 호출만 건너뛰므로, 컴퓨터 호출이 실패해도 해당 턴의 브라우저 호출은 건너뛰지 않습니다.

<CodeGroup exclude="shell:cURL, shell:CLI, csharp, go, java, php, ruby">
  ```python Python
  # 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())
  ```

  ```typescript TypeScript
  // MyBrowser, backend, urlPolicy는 고급 예제에서 가져온 것입니다.
  // confirm은 "Run the computer toolset safely"에 나온 함수입니다.
  const browser = new MyBrowser(backend, { urlPolicy });
  const desktop = new MyDesktop(display, { confirm });
  try {
    const runner = client.beta.messages.toolRunner({
      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,
      runToolsEagerly: true // so a call can start before the response ends
    });
    for await (const stream of runner) {
      console.log(await stream.finalMessage());
    }
  } finally {
    await Promise.all([browser.close(), desktop.close()]);
  }
  ```
</CodeGroup>

직접 작성하는 루프에서는 `toolset_name` (typescript: `toolsetName`)이 호출의 `toolset_name`과 일치하는 인스턴스의 `tool_result` (typescript: `toolResult`)로 각 호출에 응답하세요. 각 턴에서 도구 세트별로 실패한 호출을 따로 추적하고, 건너뛴 호출에는 해당 도구 세트 고유의 텍스트로 응답하세요. 브라우저의 텍스트는 [도구 러너 없이 실행](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-sdk#run-without-the-tool-runner)에 있으며, 컴퓨터의 텍스트는 이 섹션 상단의 건너뛴 호출 항목에 있습니다.

## 참조

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

| Python               | TypeScript     | 설정 대상                                  |
| -------------------- | -------------- | -------------------------------------- |
| `configs`            | `configs`      | 활성화할 멤버                                |
| `confirm`            | `confirm`      | 각 호출을 승인하거나 거부하는 호출 가능 객체              |
| `url_policy`         | `urlPolicy`    | URL로의 각 `navigate` 전에 SDK가 호출하는 URL 정책 |
| `file_policy`        | `filePolicy`   | 업로드 루트 및 다운로드 경로 노출                    |
| `tool_configs`       | `toolConfigs`  | `cache_control`과 같은 `tools` 항목의 필드     |
| `_browser_state` 메서드 | `browserState` | 상태 보고                                  |

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

생성 후에는 옵션을 변경할 수 없습니다. [Python 브라우저 도구 세트 가이드](https://github.com/anthropics/anthropic-sdk-python/blob/main/browser-toolset.md), [TypeScript 브라우저 도구 세트 가이드](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/browser-toolset.md), [Python 컴퓨터 도구 세트 가이드](https://github.com/anthropics/anthropic-sdk-python/blob/main/computer-toolset.md), [TypeScript 컴퓨터 도구 세트 가이드](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/computer-toolset.md)에서 각 옵션을 설명합니다. Python 가이드에서는 비동기 클래스인 `BetaAsyncAbstractBrowserToolset20260801`과 `BetaAsyncAbstractComputerToolset20260801`도 다룹니다.

## 제한 사항

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

## 다음 단계

<CardGroup cols={2}>
  <Card title="브라우저 사용 도구" icon="browser" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/browser-use-tool">
    멤버 도구, `browser_state` 블록, 그리고 도구의 보안 고려 사항.
  </Card>

  <Card title="컴퓨터 사용 도구" icon="computer" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/computer-use-tool">
    17개의 도구, 좌표 및 이미지 제한, 그리고 도구의 보안 고려 사항.
  </Card>

  <Card title="도구 러너 (SDK)" icon="arrows-clockwise" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/tool-runner">
    SDK가 루프를 실행하는 방식과 SDK가 보내는 메시지를 변경하는 방법.
  </Card>

  <Card title="탈옥 및 프롬프트 인젝션 완화" icon="shield" href="https://platform.claude.com/docs/ko/test-and-evaluate/strengthen-guardrails/mitigate-jailbreaks">
    신뢰할 수 없는 콘텐츠를 읽는 모든 애플리케이션을 위한 가드레일.
  </Card>
</CardGroup>
