Claude Platform Docs
Messagesツール

SDKツールセットによるブラウザ使用とコンピュータ使用

PythonまたはTypeScript SDKからブラウザ使用ツールまたはコンピュータ使用ツールを実行します。SDKはループと設定したチェックを実行し、ブラウザまたはデスクトップはユーザー側で用意します。

PythonおよびTypeScript SDKのブラウザツールセットとコンピュータツールセットを使用すると、Webブラウザ上またはデスクトップ上で作業するエージェントを構築できます。エージェントは、プログラムから操作するためのインターフェースを持たないアプリケーションも使用できます。ブラウザ使用ツールを通じて、ClaudeはWebページを移動し、読み取り、操作します。コンピュータ使用ツールを通じて、Claudeはスクリーンショットでデスクトップを確認し、マウスとキーボードを使用します。

SDKはClaudeをブラウザまたはデスクトップに接続します。SDKは、navigateに対するURLポリシーや承認コールバックなど、設定したチェックを実行します。チェックを通過した各呼び出しを「driver」(ドライバー)に送信し、ドライバーが返した内容からtool_resultを構築します。ツールランナーを使えば、エージェントループを代わりに実行させることができます。

ブラウザまたはデスクトップを操作するドライバーは、ユーザーが用意します。まずはプロバイダーのドライバーから始めてください。それを使えば、数行のコードでエージェントを作成できます。適したものがない場合は、Playwrightなどのブラウザ自動化ライブラリを使って独自のドライバーを作成してください。

このページの大部分はブラウザツールセットについて説明しています。コンピュータツールセットでは、異なる点を説明します。

独自のドライバーの作成は高度なケースであり、ドライバーを実装するで説明しています。claude-quickstartsリポジトリには、PythonとTypeScriptによるブラウザドライバーの例もあります。これは「Chrome DevTools Protocol」、すなわち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のエントリとして渡します。ランナーはツールセットを閉じないため、1つのインスタンスで複数の実行に対応できます。使い終わったら閉じてください。

独自のドライバーを作成するには、ドライバーを実装するを参照してください。

パートナー統合

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を送出します(TypeScriptではthrowします)。

executeをオーバーライドすると、Claudeに提示されるメンバーが変わります。SDKはすべてのメンバーを実装済みとみなすため、デフォルトで有効なすべてのメンバーがClaudeに提示されます。高度な例のMyBrowserは3つのメンバーに対応しているため、次の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、zoomBetaScreenshotResult1つの画像ブロック
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)
その他すべてのメンバー何も返さない、または1行のテキスト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 }です。

最後の2つはAPIの状態変化ではありません。SDKはこれらをbrowser_stateブロックの外側のテキストとしてClaudeに報告します。拒否されたすべてのナビゲーションについては1行(URLは含まれません)、閉じられたダイアログについては最初の3つそれぞれに1行ずつ、その後に残りの件数が報告されます。

いずれかのタブが開いている場合、アクティブなタブはちょうど1つでなければなりません。tab_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を送出してください(TypeScriptではthrowします)。

レスポンスのストリーミング中に呼び出しを開始する

デフォルトでは、ツールランナーはClaudeのレスポンスが終了した後にそのターンの呼び出しを実行します。「early start」(早期開始)を使用すると、レスポンスがまだストリーミング中でもブラウザまたはコンピュータの呼び出しを開始できます。

有効にするには、高度な例のように、stream=Trueとrun_tools_eagerly=Trueをツールランナーに渡します。streamを指定すると、ランナーに対するループの各反復で、メッセージではなくストリームが得られます。

早期開始によって変わるのは呼び出しを開始できるタイミングであり、ツールセットがそれを実行する方法ではありません。

  • チェックは引き続き先に実行されます: SDKは呼び出しを実行する前に、URLポリシー、ファイルポリシー、confirmを呼び出します。早期開始では、レスポンスがまだストリーミング中にconfirmを呼び出すことがあります。
  • 呼び出しは引き続き1つずつ実行されます: ツールセットは、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、およびバッチアクションで求められる正確なテキストが含まれます。ツールランナーも同じ応答を送信します。

高度な例:独自のドライバーを作成する

この例では、ドライバーを実装するの主要な部分を1つのドライバー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:
    # これは例であり、本番用のポリシーではありません。「URL ポリシーを書く」を参照してください。
    if url.lower() == "about:blank":
        return True
    with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
    # ブラウザは Web 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の次のアクションは、読み取るページに依存します。ページ、またはページに注入されたテキストは、内部サービスへのアクセスやホストからのファイルの持ち出しを試みる可能性があります。また、実際の影響を伴うアクションを引き起こそうとする可能性もあります。使い捨てのブラウザ以外に対してドライバーを実行する前に、次の6つの手順を実行してください:

  1. タスクに不要なすべてのnavigate URLを拒否するURLポリシーを記述します。
  2. ドライバーでリクエストをインターセプトし、同じルールで各リクエストをチェックします。
  3. コンテナに「egress policy」(エグレスポリシー)を適用し、ドライバーが認識できないものをネットワークでブロックします。
  4. アップロードとダウンロードを制限するか、アップロードを無効のままにします。
  5. confirmで影響の大きいメンバーを制限します。
  6. セッションごとに専用のコンテナまたはVMでブラウザホストを分離します。

SDKは、渡されたポリシーと呼び出し可能オブジェクトを通じて手順1、4、5を適用します。手順2、3、6はドライバーとデプロイメント側の責任です。セキュリティに関する考慮事項の注意事項も適用されます。

URLポリシーを記述する

URLポリシーは、url_policyとして渡す関数です。ポリシーはユーザー自身が記述し、本番環境に対応させる責任もユーザーにあります。navigateが実行される前に、SDKは呼び出しのコンテキストと、Claudeが書いたとおりのURLを引数としてポリシーを呼び出します。

URLを許可するには何も返しません。拒否するにはToolErrorを送出します(TypeScriptではthrowします)。Claudeはエラーのメッセージを読み取り、navigateは実行されません。ポリシーは各navigate呼び出しのURLのみをチェックします。ネットワーク制御ではありません。

これは高度な例のポリシーです。about:blankに加えて、2つのサイトとそのサブドメインを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}"
    # ブラウザは Web 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チェックでも認識できないものに対する、コンテナのエグレスルール。コンテナにエグレスポリシーを適用するを参照してください。

url_policy=Noneを渡すと、SDKはURLへのすべてのnavigateを拒否します。そのため、設定から読み込まれたNoneによって、誤ってURLがチェックされないままになることはありません。TypeScriptでは、undefinedはオプションを未設定のままにし、渡さない場合と同じになります。

ドライバーでリクエストをインターセプトする

URLポリシーが判断するのは、navigate呼び出しのURLだけです。Claudeがクリックするリンク、送信するフォーム、リダイレクト、または許可されたページ上の画像やスクリプトは、ポリシーが拒否するはずのホストに到達する可能性があります。それらのホストをブロックするにはエグレスルールを使用してください。

リクエストインターセプトを備えたドライバーは、リクエストフック(自動化ライブラリが各リクエストの前に呼び出す関数)でそれらのリクエストをチェックすることもできます。ルールを一度だけ記述すれば済むように、ポリシーが使用する関数を呼び出してください。

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ハンドシェイク、Service Workerのリクエスト、リダイレクトのホップを認識しません。Service Workerはブロックしてください。WebSocketハンドシェイクとリダイレクトのホップは、それらを認識できるフックでチェックしてください。ポリシーの例はws://とwss://のURLを拒否するため、WebSocket URLをis_allowedに渡す前に、ws://をhttp://に、wss://をhttps://に変更してください。

コンテナにエグレスポリシーを適用する

インターセプトではブラウザが行うすべてのリクエストを認識できず、URLポリシーは名前の解決先を認識しません。コンテナのネットワークが適用するエグレスポリシーは、その両方をカバーします:

  • IPv4とIPv6のリンクローカルおよびプライベートアドレス範囲をブロックします。これにはクラウドメタデータアドレス169.254.169.254も含まれます。
  • タスクに必要なホストへのアウトバウンド接続のみを許可します。ルールがIPアドレスで照合する場合は、コンテナの起動時に許可するホスト名を解決してください。
  • DNSはコンテナのリゾルバーへのみ許可します。

Kubernetes NetworkPolicyやクラウドファイアウォールなど、ブラウザのコンテナ外で適用されるルールは、コンテナ内のループバックを認識しません。それらのルールだけでは、ページはそこでリッスンしているあらゆるものに到達できます。これにはDevToolsポートも含まれ、そこではブラウザの開いているタブの一覧と、各タブを制御するアドレスが表示されます。

アップロードとダウンロードを制限する

file_uploadはデフォルトで無効です。file_policyがない場合、SDKはパスまたはドキュメントIDを指定するすべてのアップロードを拒否します。アップロードを有効にするには、タスクのファイルのみを保持する1つのアップロードディレクトリを指定した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関数を通じて2つの制御対象メンバーについて問い合わせ、残りは承認します。

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()を呼び出すたびに、承認を持たない呼び出し可能オブジェクトが返されます。ツールセットごとに1回呼び出し、各ユーザーに専用のツールセットを割り当ててください。

承認の対象は、最後の状態レポートが示した時点のページであり、呼び出しが実行される前にページが変わる可能性があります。購入、メッセージの送信、規約への同意は、left_clickやtypeなどの通常のメンバーを通じて行われるため、confirmはそれらを名前で識別できません。人にそれらを承認させるには、それらのメンバーについても確認してください。

エグレスルールによってブラウザのアウトバウンド接続がタスクに必要なホストに制限されていない限り、javascript_execを有効にしないでください。スクリプトは、ブラウザが到達できる任意のアドレスにページのコンテンツを送信できます。

navigate呼び出し内のjavascript: URLも、javascript_execが無効であっても、現在のページでスクリプトを実行します。confirmはその呼び出しをnavigateとして認識するため、この例のconfirm呼び出し可能オブジェクトはそれを承認してしまいます。ドライバーはjavascript: URLを拒否する必要があります。この例では、confirmが実行される前にURLポリシーがそれらを拒否します。

ブラウザホストを分離する

セッションごとに、専用の最小権限のコンテナまたはVMでブラウザを実行します:

  • 非rootユーザーとして実行し、ブラウザが許容する場合は読み取り専用のルートファイルシステムを使用します。
  • 設定したアップロードおよびダウンロードディレクトリ(ある場合)以外は、ホストから何もマウントしません。
  • 認証情報を環境に含めず、新しいブラウザプロファイルから開始します。
  • Claudeが呼び出せる他のツールとファイルシステムを共有しません。

APIを呼び出すコードは、APIキーと会話を保持しているため、ブラウザのコンテナの外で実行してください。ツールランナーとtool_resultはどちらもそのコードのプロセス内でツールセットを実行するため、ブラウザはツールセットのファイルシステムを共有しません。ブラウザはリモートとして扱ってください。リモートブラウザとホスト型ブラウザが適用されます。

ページテキスト、スクリーンショット、コンソールとネットワークのエントリ、タブのタイトル、ダウンロード名など、ページが返すものはすべて信頼できないものとして扱ってください。

リモートブラウザとホスト型ブラウザ

一部のブラウザは、SDKを実行するプロセスとファイルシステムを共有しません。例としては、別のコンテナ内のブラウザ、DevTools URLでアクセスするブラウザ、ホスト型ブラウザサービスのブラウザがあります。これらの場合でも、URLポリシー、リクエストのインターセプト、confirmは引き続き自分のプロセス内で実行されます。

ホスト型ブラウザでは、プロバイダーがエグレスとホストの分離を制御します。独自のエグレスポリシーはそこには適用されないため、ドライバーのリクエストフックがブラウザのリクエストに対する唯一のチェックになります。ブラウザのネットワークが何にアクセスできるかを確認してください。

同梱のパスチェックはリモートブラウザを保護しません。BetaLocalFilePolicyはSDKを実行するプロセスのファイルシステム上のパスをチェックしますが、ブラウザは自身のファイルシステムを読み書きします。

SDKはブラウザがリモートであることを検出できません。そのため、リモートブラウザの場合は、ドライバーが独自のBetaFilePolicyを使ってブラウザが実行される場所でアップロードパスをチェックしない限り、file_uploadを無効のままにしてください。BetaFilePolicyは、各アップロードのパスとドキュメントIDを検査し、Claudeがダウンロードのパスを見られるかどうかを決定します。

リモートブラウザ自体のホストにアップロードとダウンロードを制限するのダウンロード設定がない限り、リモートブラウザにはダウンロードを拒否させてください。

プロバイダーのAPIキーと、キーを含む可能性のあるセッションの接続URLは、ログ、ツール結果、エラーテキストに含めないでください。プロバイダーがセッションを記録する場合、その記録はClaudeが見て入力したすべてのもののもう1つのコピーとなり、プロバイダーの保持条件が適用されます。

コンピュータツールセット

BetaAbstractComputerToolset20260801はコンピューター使用ツール用のクラスです。ドライバーは、screenshotやleft_clickなど、ツールごとに1つのメソッドを持つサブクラスです。パートナー統合にあるパートナーのドライバーから始めてください。独自に作成する場合は、claude-quickstartsリポジトリにPythonとTypeScriptの最小限のVNCの例があります。これはVNC経由でデスクトップを制御するもので、本番用のコードではありません。

このクラスはconfigs、confirm、tool_configsを受け取り、ブラウザクラスと同じように渡します。ドライバーをカスタマイズする、エラーを処理する、レスポンスのストリーミング中に呼び出しを開始する、ツールランナーなしで実行するもこのクラスに適用されますが、次の相違点があります。

  • ブラウザ専用のオプション: このクラスにはURLポリシー、ファイルポリシー、状態レポートはありません。
  • configs: 実装したすべてのツールはデフォルトで有効です。ツールごとの設定はツールパラメータに記載されています。
  • confirm: クラスがtype、key、またはhold_keyを実装している場合、confirm呼び出し可能オブジェクトを渡すか、configsでそれらのツールを無効にしない限り、コンストラクターは設定エラーを送出します。executeをオーバーライドするクラスは、3つすべてを実装しているとみなされます。
  • スキップされた呼び出し: コンピュータ呼び出しが失敗した後、ツールランナーはそのターンの後続のコンピュータ呼び出しに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を送出してください(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の4つのツールには入力フィールドがありません。これらを含むすべての入力型は、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画像はドライバー内でスケーリングしてください。画像の制限に収まるようにスクリーンショットのサイズを調整するを参照してください。長時間の実行では、1つのリクエストに20枚を超える画像が含まれることがあり、その場合APIは各画像により厳しい制限を適用します。スクリーンショット履歴を管理するを参照してください。

入力チェックと結果

呼び出しの入力チェックをSDKに頼らないでください。ドライバーで使用するフィールドをチェックし、実行できない呼び出しは拒否してください。その際はToolErrorを送出します(TypeScriptではthrowします)。Claudeはそのメッセージを読み、修正した呼び出しを送信できます。

どちらのSDKも、duration、repeatの回数、scroll_amountを制限しません。これらについてはコンピュータツールセットを安全に実行するのステップ4で説明しています。

ツールが返すものによって、Claudeが読む内容が決まります。

ツール戻り値Claudeが読む内容
screenshot、zoomBetaScreenshotResult1つの画像ブロック
cursor_positionBetaComputerCursorPositionResultX={x},Y={y}
その他すべてのツールなし、または1行のテキストClicked.などの短い確認メッセージと、それに続く独立したテキストブロック内の返された行

コンピュータツールセットを安全に実行する

画面に表示されている内容が、Claudeの次のアクションを左右します。ページや通知が、Claudeに誤ったウィンドウへ入力させたり、実際の影響を伴うアクションを実行させたりしようとする可能性があります。

使い捨てのマシン以外に対してドライバーを実行する前に、次の5つのステップを実行してください。

  1. デスクトップを隔離します。セッションごとに、認証情報やホストマウントのない、専用の最小権限のコンテナまたはVMで実行してください。タスクに必要なアプリケーションのみをインストールします。APIを呼び出すコードはその外部で実行してください。
  2. 実際の影響を伴うアクションをconfirmで制御します。購入やメッセージ送信は通常のleft_click、type、またはkeyであり、confirmが受け取るのはツールとその入力であって、画面ではありません。そのため、Claudeがデスクトップ上でアクセスできるアプリケーションに基づいて、承認が必要なツールを選択してください。
  3. タスクで必要な場合を除き、ターミナル、実行ダイアログ、ランチャーをデスクトップに置かないでください。これらのいずれかにキーボードフォーカスがあると、type、key、hold_keyはClaudeが入力したものを何でも実行し、クリックでプログラムが起動する可能性があります。タスクでこれらが必要な場合は、キーボード入力とクリックの前に人に確認し、コマンドがタスクに必要な範囲を超えて到達できないようにデスクトップを隔離してください。
  4. スクリーンショット外の点、数分間のwait、非常に大きなrepeatやscroll_amountなど、デスクトップが受け付けるべきでない入力は拒否してください。値を範囲内に収めるのではなく、ToolErrorを送出してください(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に含まれないすべてのツールを承認します。フェイルクローズにするには、デスクトップに影響を与えないツール(screenshot、zoom、cursor_position、wait)のみを承認し、その他のすべてのツールについて確認を求めてください。

1つのリクエストで両方のツールセットを使用する

両方のインスタンスをtoolsに渡します。各呼び出しにはtoolset_name(browserまたはcomputer)が含まれており、ツールランナーは呼び出しを対応するインスタンスに送信します。呼び出しが失敗した後、ツールランナーはそのターン内の同じツールセットへの後続の呼び出しのみをスキップするため、コンピュータ呼び出しが失敗しても、そのターンのブラウザ呼び出しはスキップされません。

# 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ポリシー。SDKはURLへの各navigateの前にこれを呼び出します
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は呼び出しが実行される前にページを再度チェックしません。
  • 1つのツールセットに対する呼び出しは一度に1つずつ実行されます: これを無効にすることはできません。
  • SDKはタブIDが一意であるかどうかやタブの数をチェックせず、アクティブなタブが1つであることも常にチェックするわけではありません: APIはこれらのルールに違反するレポートを拒否します。
  • コンピュータ呼び出しの場合、confirmが受け取るのはツールとその入力であって、画面ではありません: それらからは、特定の点でのクリックが何を行うかを判断できません。
  • SDKはコンピュータツールセットの画像をリサイズしません: APIはモデルの制限を超える画像を拒否します。座標のスケーリングと画像のサイズ調整を参照してください。

次のステップ

メンバーツール、browser_stateブロック、およびツールのセキュリティに関する考慮事項。

17個のツール、座標と画像の制限、およびツールのセキュリティに関する考慮事項。

SDKがループを実行する方法と、送信するメッセージを変更する方法。

信頼できないコンテンツを読み取るあらゆるアプリケーションのためのガードレール。

Was this page helpful?