> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.scrapybara.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.scrapybara.com/_mcp/server.

# Tools

## Scrapybara tools

### BashTool, ComputerTool, EditTool

`BashTool`, `ComputerTool`, and `EditTool` follow the same interface as the instance `bash`, `computer`, and `edit` methods. They each take in an `instance` parameter to interact with the instance.

* `ComputerTool` allows the agent to allows the agent to control mouse and keyboard. Supported for Ubuntu, Browser, and Windows instances.
* `BashTool` allows the agent to run bash commands. Supported only for Ubuntu instances.
* `EditTool` allows the agent to view, create, and edit files. Supported only for Ubuntu instances.

#### Python

```python
from scrapybara import Scrapybara
from scrapybara.tools import BashTool, ComputerTool, EditTool

client = Scrapybara()
instance = client.start_ubuntu()

tools = [
  BashTool(instance),
  ComputerTool(instance),
  EditTool(instance),
]
```

#### TypeScript

```typescript
import { ScrapybaraClient } from "scrapybara";
import { bashTool, computerTool, editTool } from "scrapybara/tools";

const client = new ScrapybaraClient();
const instance = await client.startUbuntu();

const tools = [
  bashTool(instance),
  computerTool(instance),
  editTool(instance),
];
```

## Define custom tools

You can define custom tools. A tool needs a `name`, `description`, `parameters` (Pydantic model for Python, Zod object for TS), and an execute function (`__call__` for Python, `execute` for TS).

#### Python

```python
from scrapybara.client import UbuntuInstance
from scrapybara.tools import Tool
from pydantic import BaseModel

class CapyParameters(BaseModel):
    # Define your parameters here
    pass

class CapyTool(Tool):
    _instance: UbuntuInstance

    def __init__(self, instance: UbuntuInstance) -> None:
        super().__init__(
            name="capy",
            description="Use a capybara",
            parameters=CapyParameters,
        )
        self._instance = instance

    def __call__(self, **kwargs: Any) -> Any:
        # Implement your tool logic here
        pass
```

#### TypeScript

```typescript
import { UbuntuInstance } from "scrapybara";
import { tool } from "scrapybara/tools";
import { z } from "zod";

export function capyTool(instance: UbuntuInstance) {
    return tool({
        name: "capy",
        description: "Use a capybara",
        parameters: z.object({}), // Define your parameters here
        execute: async () => {
            // Implement your tool logic here
        },
    });
}
```

### BrowserTool

`BrowserTool` allows the agent to interact with a browser using Playwright.

> **Error**
>
> Custom tools like BrowserTool may degrade model performance, as the models have not been trained on custom tools. For browser automation, we recommend sticking to ComputerTool.

> **Note**
>
> The BrowserTool requires the browser to be started first.

#### Python

```python
from playwright.sync_api import sync_playwright

class BrowserToolParameters(BaseModel):
"""Parameters for browser interaction commands."""

command: Literal[
    "go_to",  # Navigate to a URL
    "get_html",  # Get current page HTML
    "evaluate",  # Run JavaScript code
    "click",  # Click on an element
    "type",  # Type into an element
    "screenshot",  # Take a screenshot
    "get_text",  # Get text content of element
    "get_attribute",  # Get attribute of element
] = Field(
    description="The browser command to execute. Required parameters per command:\n"
    "- go_to: requires 'url'\n"
    "- evaluate: requires 'code'\n"
    "- click: requires 'selector'\n"
    "- type: requires 'selector' and 'text'\n"
    "- get_text: requires 'selector'\n"
    "- get_attribute: requires 'selector' and 'attribute'\n"
    "- get_html: no additional parameters\n"
    "- screenshot: no additional parameters"
)
url: Optional[str] = Field(
    None, description="URL for go_to command (required for go_to)"
)
selector: Optional[str] = Field(
    None,
    description="CSS selector for element operations (required for click, type, get_text, get_attribute)",
)
code: Optional[str] = Field(
    None, description="JavaScript code for evaluate command (required for evaluate)"
)
text: Optional[str] = Field(
    None, description="Text to type for type command (required for type)"
)
timeout: Optional[int] = Field(
    30000, description="Timeout in milliseconds for operations"
)
attribute: Optional[str] = Field(
    None,
    description="Attribute name for get_attribute command (required for get_attribute)",
)


class BrowserTool(Tool):
"""A browser interaction tool that allows the agent to interact with a browser."""

_instance: Union[UbuntuInstance, BrowserInstance]

def __init__(self, instance: Union[UbuntuInstance, BrowserInstance]) -> None:
    super().__init__(
        name="browser",
        description="Interact with a browser for web scraping and automation",
        parameters=BrowserToolParameters,
    )
    self._instance = instance

def __call__(self, **kwargs: Any) -> Any:
    params = BrowserToolParameters.model_validate(kwargs)
    command = params.command
    url = params.url
    selector = params.selector
    code = params.code
    text = params.text
    timeout = params.timeout or 30000
    attribute = params.attribute

    cdp_url = self._instance.browser.get_cdp_url().cdp_url
    if cdp_url is None:
        raise ValueError("CDP URL is not available, start the browser first")

    with sync_playwright() as playwright:
        browser = playwright.chromium.connect_over_cdp(cdp_url)
        context = browser.contexts[0]
        if not context.pages:
            page = context.new_page()
        else:
            page = context.pages[0]

        try:
            if command == "go_to":
                if not url:
                    raise ValueError("URL is required for go_to command")
                page.goto(url, timeout=timeout)
                return True

            elif command == "get_html":
                try:
                    return page.evaluate("() => document.documentElement.outerHTML")
                except Exception:
                    # If page is navigating, just return what we can get
                    return page.evaluate("() => document.documentElement.innerHTML")

            elif command == "evaluate":
                if not code:
                    raise ValueError("Code is required for evaluate command")
                return page.evaluate(code)

            elif command == "click":
                if not selector:
                    raise ValueError("Selector is required for click command")
                page.click(selector, timeout=timeout)
                return True

            elif command == "type":
                if not selector:
                    raise ValueError("Selector is required for type command")
                if not text:
                    raise ValueError("Text is required for type command")
                page.type(selector, text, timeout=timeout)
                return True

            elif command == "screenshot":
                return image_result(
                    base64.b64encode(page.screenshot(type="png")).decode("utf-8")
                )

            elif command == "get_text":
                if not selector:
                    raise ValueError("Selector is required for get_text command")
                element = page.wait_for_selector(selector, timeout=timeout)
                if element is None:
                    raise ValueError(f"Element not found: {selector}")
                return element.text_content()

            elif command == "get_attribute":
                if not selector:
                    raise ValueError(
                        "Selector is required for get_attribute command"
                    )
                if not attribute:
                    raise ValueError(
                        "Attribute is required for get_attribute command"
                    )
                element = page.wait_for_selector(selector, timeout=timeout)
                if element is None:
                    raise ValueError(f"Element not found: {selector}")
                return element.get_attribute(attribute)

            else:
                raise ValueError(f"Unknown command: {command}")

        except Exception as e:
            raise ValueError(f"Browser command failed: {str(e)}")

        finally:
            browser.close()
```

#### TypeScript

```typescript
import { chromium } from "playwright";

export function browserTool(instance: UbuntuInstance | BrowserInstance) {
    return tool({
        name: "browser",
        description: "Interact with a browser for web scraping and automation",
        parameters: z.object({
            command: z
                .enum(["go_to", "get_html", "evaluate", "click", "type", "screenshot", "get_text", "get_attribute"])
                .describe(
                    "The browser command to execute. Required parameters per command:\n- go_to: requires 'url'\n- evaluate: requires 'code'\n- click: requires 'selector'\n- type: requires 'selector' and 'text'\n- get_text: requires 'selector'\n- get_attribute: requires 'selector' and 'attribute'\n- get_html: no additional parameters\n- screenshot: no additional parameters"
                ),
            url: z.string().optional().describe("URL for go_to command (required for go_to)"),
            selector: z
                .string()
                .optional()
                .describe("CSS selector for element operations (required for click, type, get_text, get_attribute)"),
            code: z.string().optional().describe("JavaScript code for evaluate command (required for evaluate)"),
            text: z.string().optional().describe("Text to type for type command (required for type)"),
            timeout: z.number().optional().default(30000).describe("Timeout in milliseconds for operations"),
            attribute: z
                .string()
                .optional()
                .describe("Attribute name for get_attribute command (required for get_attribute)"),
        }),
        execute: async (params) => {
            const { command, url, selector, code, text, timeout = 30000, attribute } = params;

            const cdpUrl = await instance.browser.getCdpUrl();
            if (!cdpUrl.cdpUrl) {
                throw new Error("CDP URL is not available, start the browser first");
            }

            const browser = await chromium.connectOverCDP(cdpUrl.cdpUrl);
            try {
                const context = browser.contexts()[0];
                const page = context.pages().length ? context.pages()[0] : await context.newPage();

                try {
                    switch (command) {
                        case "go_to":
                            if (!url) throw new Error("URL is required for go_to command");
                            await page.goto(url, { timeout });
                            return true;

                        case "get_html":
                            try {
                                return await page.evaluate("document.documentElement.outerHTML");
                            } catch {
                                // If page is navigating, just return what we can get
                                return await page.evaluate("document.documentElement.innerHTML");
                            }

                        case "evaluate":
                            if (!code) throw new Error("Code is required for evaluate command");
                            return await page.evaluate(code);

                        case "click":
                            if (!selector) throw new Error("Selector is required for click command");
                            await page.click(selector, { timeout });
                            return true;

                        case "type":
                            if (!selector) throw new Error("Selector is required for type command");
                            if (!text) throw new Error("Text is required for type command");
                            await page.type(selector, text, { timeout });
                            return true;

                        case "screenshot":
                            const screenshot = await page.screenshot({ type: "png" });
                            return imageResult(screenshot.toString("base64"));

                        case "get_text":
                            if (!selector) throw new Error("Selector is required for get_text command");
                            const textElement = await page.waitForSelector(selector, { timeout });
                            if (!textElement) throw new Error(`Element not found: ${selector}`);
                            return await textElement.textContent();

                        case "get_attribute":
                            if (!selector) throw new Error("Selector is required for get_attribute command");
                            if (!attribute) throw new Error("Attribute is required for get_attribute command");
                            const element = await page.waitForSelector(selector, { timeout });
                            if (!element) throw new Error(`Element not found: ${selector}`);
                            return await element.getAttribute(attribute);

                        default:
                            throw new Error(`Unknown command: ${command}`);
                    }
                } catch (error: any) {
                    throw new Error(`Browser command failed: ${error?.message || String(error)}`);
                }
            } finally {
                await browser.close();
            }
        },
    });
}
```