Skip to main content
Glama
README.md
# Browserless MCP Server

[![MCP Badge](https://lobehub.com/badge/mcp/browserless-browserless-mcp?style=for-the-badge)](https://lobehub.com/mcp/browserless-browserless-mcp)

MCP (Model Context Protocol) server for [Browserless.io](https://browserless.io) — expose the Browserless smart scraper API to LLM clients like Claude Desktop, Cursor, VS Code, and Windsurf.

## Quick Start

Get an API token from [browserless.io](https://browserless.io) (free tier available), then point your MCP client at the hosted server:

```json
{
  "mcpServers": {
    "browserless": {
      "url": "https://mcp.browserless.io/mcp?token=your-token-here"
    }
  }
}
```

No local install — see [Configuration](#configuration) for per-client snippets.

## Tools

| Tool                       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `browserless_smartscraper` | Scrape a single webpage and return its content as markdown or HTML, or AI-extracted JSON and summaries. Handles JavaScript-heavy pages and anti-bot measures automatically. For content across multiple pages, use `browserless_crawl`; to list a site's URLs, use `browserless_map`.                                                                                                                                                                       |
| `browserless_search`       | Search the web using Browserless and optionally scrape each result. Supports web, news, and image search with geo-targeting and time filters.                                                                                                                                                                                                                                                                                                               |
| `browserless_map`          | Discover and map all URLs on a website. Scans via sitemaps and link extraction. Returns URLs with optional titles and descriptions. Useful for site audits and content discovery.                                                                                                                                                                                                                                                                           |
| `browserless_crawl`        | Crawl a website and scrape every discovered page. Supports depth control, path filtering, sitemap strategies, and configurable scrape options. Returns scraped content and metadata for each page.                                                                                                                                                                                                                                                          |
| `browserless_performance`  | Run Lighthouse audits on any URL. Returns category scores inline and saves the full JSON report outside the conversation: a local path (stdio) or authenticated single-use download URL (HTTP). Download to disk and inspect only needed fields with `jq`; files expire after 15 minutes or when the MCP session ends. Optionally filter by category, emulate a mobile or desktop device, supply a custom Lighthouse config, or supply performance budgets. |
| `browserless_function`     | Execute custom Puppeteer JavaScript on the Browserless cloud. The function receives a `page` object and optional `context`; return `{ data, type }` to control the payload and Content-Type.                                                                                                                                                                                                                                                                |
| `browserless_export`       | Export a webpage via the Browserless `/export` API. Fetches the URL and returns its native content (HTML, PDF, image, etc.) with automatic content-type detection.                                                                                                                                                                                                                                                                                          |
| `browserless_agent`        | Drive a persistent browser session via a ReAct loop: snapshot the page, plan, batch interactions (click, type, scroll, evaluate, etc.), and re-snapshot. Uses ref-based selectors derived from snapshots, supports multi-tab workflows, screenshots, captcha solving, live URLs, and file upload/download (captured downloads auto-surface as handles; bytes never enter context).                                                                          |
| `browserless_skill`        | Load an on-demand recipe for a non-trivial page mechanic (shadow DOM, cookie consent, modals, captchas, dynamic content, snapshot misses, screenshots, tabs). Companion to `browserless_agent`.                                                                                                                                                                                                                                                             |
| `browserless_profiles`     | List the authentication profiles saved for the current token, with cookie and origin counts. Pass a profile's name as `profile` to another tool to reuse its logged-in state.                                                                                                                                                                                                                                                                               |
| `browserless_account`      | Read the account behind the current token: plan, unit balance, billing period, and API key names. Never returns API token values.                                                                                                                                                                                                                                                                                                                           |
| `browserless_usage`        | Read request and unit consumption: successes, errors, timeouts, queueing, peak concurrency, captchas, proxy bytes and units. Optionally scoped to specific API keys.                                                                                                                                                                                                                                                                                        |
| `browserless_sessions`     | Inspect active/persistent sessions, recordings, and integrations. `action: "replay"` prepares a recording and offline player. On the full surface, MCP Apps clients also receive a `viewRef` for `browserless_show`.                                                                                                                                                                                                                                        |
| `browserless_show`         | Display an owned, previously prepared session replay, screenshot gallery, or recorded video inline. Full surface only; never repeats the capture, recording download, or browser operation.                                                                                                                                                                                                                                                                 |
| `browserless_logs`         | Read Browserless's own record of recent requests: what was attempted, whether it failed, why it stopped, how long it took and what it cost. The tool for diagnosing a run that failed on the Browserless side. Available window is plan-dependent.                                                                                                                                                                                                          |

### Inline session replay

On the full surface, an MCP Apps client uses two calls:

```text
browserless_sessions({ action: "replay", sessionId: "<recorded-session>" })
// Returns a compact summary, viewRef, expiresAt, and offline-player instructions.
browserless_show({ viewRef: "<returned-view-reference>" })
// Renders the prepared replay inline; does not repeat the recording download.
```

The player starts paused and includes play/pause, a keyboard-accessible timeline,
rewind, playback speed, skip-inactive, and fullscreen where the host permits it.
Controls follow the host's light/dark theme; the recorded page retains its own
appearance. UI scripts, styles, and rrweb are bundled, with no runtime CDN.
External assets referenced by recordings are blocked by the widget's CSP;
embedded assets remain available.

Replay events travel in widget-only metadata. Complete results above 2 MiB use
`browserless_view_data`, an app-only reader with bounded gzip/identity pages
(128 KiB maximum reply, 50 MiB maximum decoded recording). This reader does not
render a widget and should never be called by the model to inspect raw events.

References are owned by the authenticated caller/account and API scope, expire
after 15 minutes, and survive MCP transport reconnects. Every show/read rechecks
source access; deleted recordings and revoked API keys fail closed. OAuth follows
the server's existing verification-cache policy. Loading/retries never extend
the deadline. Keep the original offline-player fallback if a view expires.
Clients without MCP Apps retain the existing replay output.

Snapshots are bounded and process-local. Deploy with routing that keeps prepared
views on the owning process; shared view storage is required for arbitrary
cross-worker reads. Restart or eviction makes a view unavailable and never
reruns the original operation. Replay presentation tools/resources are absent
from the compliant surface, whose existing tool list is unchanged.

### Inline request-log explorer

On the full surface, MCP Apps clients can inspect logs without copying the
complete result into the conversation:

```text
browserless_logs({ levels: ["ERROR", "WARN"], limit: 50 })
browserless_show({ viewRef: "<returned-view-reference>" })
```

The explorer shows timestamp, severity, endpoint/event, outcome/status,
category/reason, duration, units, and region. Expand a row for every returned
field, including the complete message, target URL, request ID, and session ID.
IDs have Copy buttons when the host grants clipboard write access. URLs remain
plain text and never open automatically.

Filters cover the tool's existing time range, severity, endpoint, outcome,
category, reason, request ID, target URL, event names, API key, page size, and
ordering. Omitted time bounds use the account plan's server-defined window;
retention errors appear in the widget. Refresh, Apply filters, and Load more
call the authorized read-only logs tool through the bridge, without a new chat
turn or another widget. Overlapping pages are deduplicated. Loaded entries are
bounded to 500 rows and 2 MiB; each result page is limited to 100 rows and 1 MiB.
If a result cannot fit in a view or storage is full, the tool returns its
existing Markdown result instead of failing the log read.

When a view is prepared, rows, filter values, and opaque pagination cursors
travel in widget-only metadata. Model-visible results contain a compact
summary and view reference. The Markdown fallback includes entries and the
next-page cursor as before. A mounted explorer that receives this fallback
keeps its last successful snapshot and offers Retry.
It also explains how to request the logs as text in chat and suggests narrower
filters or fewer entries for large pages.
Prompt text and filter values are excluded from logs-tool analytics. Existing
Markdown output is unchanged for clients without MCP Apps. The compliant
connector remains unchanged.

Snapshots expire after 15 minutes and share the existing bounded, process-local
view store. Display rechecks account access. Refresh and pagination prepare a
new snapshot; the mounted explorer adopts its expiry while older references
keep their original deadline. Errors preserve the last successful result;
expiry, cancellation, and loss of access stop further reads. Host locale and
timezone control displayed timestamps. The optional model-diagnosis handoff is
not included.

### Inline account usage and active sessions

On the full surface, MCP Apps clients can display account snapshots:

```text
browserless_usage({ timeframe: "day" })
browserless_show({ viewRef: "<returned-view-reference>" })

browserless_sessions({ action: "active" })
browserless_show({ viewRef: "<returned-view-reference>" })
```

Usage shows billed units against the plan allotment, highlights overage, and
shows request counters when available. Billing-period units always cover the
whole account; the hour/day/week selector applies only to request counters and
preserves any API-key filter. Empty request counters never erase billed usage.
Missing values remain unavailable.

Active sessions shows the browser ID, browser name/type, start time, and elapsed
time at the snapshot. The API does not provide page URLs. Refresh preserves the
original limit/skip filters and never stops or reconnects a browser.

Both controls call the original data tool through the widget bridge and update
the mounted view without another chat turn or `browserless_show` call. Refresh
errors preserve the last successful snapshot with a retry option. There is no
automatic polling. Snapshots are independent, owned, bounded, and expire after
15 minutes; refreshing prepares a new reference and never renews an old one.
The mounted widget adopts the new snapshot's expiry after a successful refresh.
Showing a snapshot rechecks account access under the current credential.
Clients without MCP Apps retain their original text output. These tools and
widgets remain absent from the compliant connector.

### Inline Lighthouse scorecard

In MCP Apps clients, `browserless_performance` displays a scorecard directly
from the completed audit. Select a category and expand its initially collapsed
failing-audits section to see every failure, ordered by reported savings.
Expand a row for its description and reported savings. Five Lighthouse lab
metrics accompany performance results. Missing categories are omitted; null
scores and missing measurements remain unavailable. Scores use Lighthouse's
0–49, 50–89, and 90–100 bands and follow the host's light/dark theme.

The scorecard receives a bounded, typed summary rather than the raw report.
It works on both surfaces without adding connector tools, making another API
request, or loading external assets. The connector exposes only the scorecard's
self-contained UI resource. Clients without MCP Apps keep their existing text
output; the full surface also retains its JSON report download.

### Inline human handoff

When an agent needs a person to complete a step, keep its existing browser open
and prepare a handoff through `browserless_live_url`. The full HTTP surface and
an MCP Apps client are required:

```text
browserless_live_url({
  action: "handoff",
  sessionId: "<existing-agent-sessionId>",
  instructions: "Complete the verification, then choose Done.",
  timeout: 300000
})
browserless_show({ viewRef: "<returned-view-reference>" })
```

The widget embeds the existing interactive Live URL viewer, including its Done
and Couldn't finish controls, and offers an external-viewer link when embedding
is blocked. Its live capability stays in widget-only metadata. Reopening a view
only reads the existing handoff; it never creates another link or browser.

Browser commands are blocked while a person has control. The widget reads
`browserless_handoff_state`, an app-only tool, to confirm the backend outcome.
After the person replies in chat, call `browserless_live_url` with
`action: "handoff_status"` and the original `viewRef`. Resume with a fresh
snapshot in the same session only when the reason is `userDone`. Failure,
expiry, disconnection, and unavailable status do not count as success.
After Done is confirmed, the widget asks the person to return to the conversation
and type “Continue”. It does not send an automatic follow-up: tested ChatGPT
widget-generated turns did not invoke the browser tool, while a typed reply did.

A temporary status-check failure leaves the live viewer open and asks the person
to check status again before continuing. Confirmed closure, expiry, or loss of
access removes the viewer. Explicit session close cancels its handoff; closed or
retired widgets stop polling.

Handoffs default to five minutes, capped by the backend's browser deadline and
15-minute link limit. Creating, opening, or polling a handoff never extends the
browser deadline. References are caller-bound and process-local, with at most
16 stored views and 16 unresolved handoff guards. Expired or disconnected
handoffs still block automation and browser replacement until explicitly closed
with the original session handle; completion releases the guard. These guards
do not keep expired browsers alive. Reads recheck backend authorization, and an
old view cannot release a newer handoff. The five-tool connector is unchanged.
Clients without MCP Apps can continue using the agent's existing `liveURL`
command for external viewers.

Modern backends return `browserId` with the live capability. For older Docker
runtimes, MCP resolves the owned page through `/sessions` and replaces the old
capability before publishing a fresh viewer. Ambiguous targets fail without
launching another browser. The pause applies to commands through this MCP
process; independent clients can still control the browser. Hosted `/agent/run`
handoffs are a separate flow.

#### Local Docker and ngrok

The development runner serves MCP and proxies the existing Live URL viewer on
one origin. It uses the existing Browserless Docker instance on port 5555;
it does not start or modify the container. Build first, then run:

```sh
export BROWSERLESS_TOKEN="$(docker exec browserless-5555 printenv TOKEN)"
BROWSERLESS_API_URL=http://localhost:5555 \
BROWSERLESS_PUBLIC_URL=https://YOUR-DOMAIN.ngrok-free.dev \
PORT=8080 npm run start:local
```

In another terminal:

```sh
ngrok http http://127.0.0.1:8080 --url=https://YOUR-DOMAIN.ngrok-free.dev --inspect=false
```

Use No authentication in the MCP client and connect to
`https://YOUR-DOMAIN.ngrok-free.dev/mcp?token=<BROWSERLESS_TOKEN>`. Keep the token
private. `BROWSERLESS_PUBLIC_URL` changes only the viewer origin; backend API
requests stay on localhost. The runner disables OAuth, analytics, and compliance
mode for local testing. Its gateway listens on loopback and uses port 8088 for
the internal MCP process (`MCP_INTERNAL_PORT` can change it).

### Inline agent screenshots and recordings

On the full HTTP surface, MCP Apps clients can display screenshots and completed
WebM recordings from `browserless_agent`. Set `MCP_BASE_URL` to the server's
public HTTPS origin (HTTP loopback is supported for local testing). The agent
keeps its existing text and image output and adds an exact `viewRef` in text
when a media view is prepared. Pass that reference to `browserless_show`:

```text
browserless_agent({ commands: [{ method: "screenshot", params: {} }] })
// Keeps the captured image and returns instructions containing its viewRef.
browserless_show({ viewRef: "<returned-view-reference>" })
// Displays the stored capture without repeating browser actions.
```

For video, set `record: true` on the call that opens the browser, then start
recording, perform the requested browser actions, and call
`stopRecording`. Display the reference returned by the call that completes the
recording. For example, a user can ask: "Go to browserless.io, record the page
while scrolling to the bottom, and show me the video inline."

The gallery contains captures from one tool call, with keyboard navigation,
zoom, Save, and fullscreen controls. Recordings use native video controls.
Preview bytes load directly from the MCP server through opaque, expiring bearer
URLs in widget-only metadata. Anyone holding a preview URL can fetch that
artifact until expiry; preview and Save requests are repeatable and do not
consume the original single-use download.

Views expire after 15 minutes and share the replay store's 16-view, 128 MiB
budget. Each caller scope retains at most four views across widget kinds. New
views evict that caller's oldest views; they never evict another caller's
views. A gallery shows the first 12 valid captures, each at most 50 MiB; its
summary reports when more captures were returned. Invalid screenshots are
skipped without discarding valid captures. Preview files are removed on expiry
or eviction. Before the first media view is created, cleanup removes stale
files left by exited processes, including files with the current process's
reused PID. Storage remains process-local, so deployments must route requests to
the owning process. Without MCP Apps support or a valid public origin, the
agent retains its existing output without preparing a widget.

### Native file staging (disabled by default)

The full HTTP surface advertises `browserless_stage_file` with the native
`openai/fileParams` file descriptor. It is absent from stdio and the compliant
surface. **Native downloads are currently disabled:** the production hostname
allowlist is empty, so every staging request fails before DNS or network access.
Tool inputs and environment variables cannot enable it.

The implementation accepts `file.download_url` and `file.file_id`, with optional
`file.mime_type` and `file.file_name`. Once trusted hosts are verified and enabled
in code, successful staging returns only `{ handle, filename, mimeType, size }`.
Pass the handle to `browserless_agent`'s `uploadFile`; handles belong to the
authenticated token, remain reusable for 15 minutes, and are not tied to an MCP
transport. Stored MIME is inherited unless the upload supplies an override.

Controlled tests do not establish native ChatGPT attachment acceptance. Live
attachment acceptance and handle resolution across deployed replicas remain
unverified; the file store is process-local. Existing multipart `/upload` and
inline-content uploads are unchanged.

## Skills

The server ships with a built-in library of **Skills** — on-demand recipes the agent can load to handle tricky page mechanics. Skills auto-inject into `browserless_agent` responses when their triggers fire (e.g. the agent hits a cookie banner), and can also be loaded manually via the `browserless_skill` tool.

| Skill             | Source                                                         | Purpose                                                                                                                      |
| ----------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `shadow-dom`      | [src/skills/shadow-dom.md](src/skills/shadow-dom.md)           | Deep selectors and iframe targeting through shadow roots.                                                                    |
| `cookie-consent`  | [src/skills/cookie-consent.md](src/skills/cookie-consent.md)   | Vendor-specific dismiss recipes (OneTrust, Cookiebot, Didomi, TrustArc, etc.).                                               |
| `modals`          | [src/skills/modals.md](src/skills/modals.md)                   | Closing dialogs, alertdialogs, and overlay close-button heuristics.                                                          |
| `captchas`        | [src/skills/captchas.md](src/skills/captchas.md)               | Using the `solve` command, response semantics, and escalation paths (Cloud only).                                            |
| `profile-import`  | [src/skills/profile-import.md](src/skills/profile-import.md)   | Import the user's local Chrome/Brave/Edge sign-ins as a profile with the Browserless CLI (consent-gated, full surface only). |
| `dynamic-content` | [src/skills/dynamic-content.md](src/skills/dynamic-content.md) | Choosing the right `wait*` method for async/AJAX/SPA content.                                                                |
| `snapshot-misses` | [src/skills/snapshot-misses.md](src/skills/snapshot-misses.md) | Handling truncated/empty snapshots and image-rendered content.                                                               |
| `screenshots`     | [src/skills/screenshots.md](src/skills/screenshots.md)         | When to screenshot vs. snapshot, scope and format choices.                                                                   |
| `tabs`            | [src/skills/tabs.md](src/skills/tabs.md)                       | Multi-tab workflows and peek-without-switching via `targetId`.                                                               |

Load a skill explicitly:

```jsonc
{
  "method": "tools/call",
  "params": {
    "name": "browserless_skill",
    "arguments": { "id": "cookie-consent" },
  },
}
```

### Built-in proxy (`browserless_agent`)

Pass a top-level `proxy` object on `browserless_agent` to route the session through datacenter or residential IPs. Datacenter is cheaper per MB; residential is less likely to be blocked.

```jsonc
{
  "method": "tools/call",
  "params": {
    "name": "browserless_agent",
    "arguments": {
      "method": "goto",
      "params": { "url": "https://example.com" },
      "proxy": {
        "proxy": "residential",
        "proxyCountry": "us",
        "proxySticky": true,
      },
    },
  },
}
```

| Field                 | Notes                                                                                                                                         |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `proxy`               | `"datacenter"` for lower cost or `"residential"` when targets block datacenter traffic.                                                       |
| `proxyCountry`        | ISO-2 country code (`"us"`, `"de"`). Auto-normalized to lowercase. Non-letter values are rejected.                                            |
| `proxyState`          | US state name with whitespace replaced by underscores (`"new_york"`). Paid-plan gated — non-eligible tokens get a 401.                        |
| `proxyCity`           | City target. Paid/enterprise plan gated — non-eligible tokens get a 401.                                                                      |
| `proxySticky`         | Stable IP while the underlying WebSocket stays open. Reconnects (idle drop, network blip, browser crash) allocate a new sticky id and new IP. |
| `proxyLocaleMatch`    | Match `navigator` locale to the proxy IP country.                                                                                             |
| `proxyPreset`         | Residential-only named preset (e.g. `"px_amazon01"`). Available presets are plan-dependent — ask Browserless support for your list.           |
| `externalProxyServer` | Bring-your-own upstream, e.g. `http://user:pass@host:port`. Must be `http://` or `https://`.                                                  |

> **Note:** Geo, sticky, and locale options require either a built-in `proxy` tier or `externalProxyServer`; `proxyPreset` requires `proxy: "residential"`. The MCP rejects unsupported combinations instead of letting the API silently ignore them.

The `proxy` object is read once at session creation. To change it, call `close` and start a new session — the agent client keys sessions on the proxy fingerprint, so passing a different config will land on a fresh WebSocket.

### OS persona (`browserless_agent`)

Agent sessions can opt into a coherent OS persona with top-level creation options:

| Field               | Notes                                                                                     |
| ------------------- | ----------------------------------------------------------------------------------------- |
| `emulationOs`       | `"windows"`, `"macos"`, `"linux"`, or `"android"`. Enables platform spoofing.             |
| `emulatedDevice`    | Android device slug; used only with `emulationOs: "android"`.                             |
| `screen`            | Desktop screen in `WIDTHxHEIGHT` form.                                                    |
| `deviceScaleFactor` | Desktop device pixel ratio: `1` or `1.25`.                                                |
| `deviceSlot`        | Non-negative stable desktop-device slot; the server validates the account-specific range. |

Set persona options on the first call before navigation and reuse the returned
`sessionId` afterward. Persona is fixed for the life of that browser session;
close it before selecting a different persona.

## Configuration

The server is hosted at `https://mcp.browserless.io/mcp`. Authenticate via headers (preferred) or a `?token=` query parameter.

Installing via an AI agent? See [install.md](install.md) for agent-readable setup instructions.

**Using headers** (recommended for clients that support them):

```json
{
  "mcpServers": {
    "browserless": {
      "url": "https://mcp.browserless.io/mcp",
      "headers": {
        "Authorization": "Bearer your-token-here"
      }
    }
  }
}
```

**Using URL query parameters** (for clients like Claude.ai custom connectors that only accept a URL):

```text
https://mcp.browserless.io/mcp?token=your-token-here
```

To connect to a specific Browserless regional endpoint, add the `x-browserless-api-url` header or the `browserlessUrl` query parameter:

```json
{
  "mcpServers": {
    "browserless": {
      "url": "https://mcp.browserless.io/mcp",
      "headers": {
        "Authorization": "Bearer your-token-here",
        "x-browserless-api-url": "https://production-lon.browserless.io"
      }
    }
  }
}
```

```text
https://mcp.browserless.io/mcp?token=your-token-here&browserlessUrl=https://production-lon.browserless.io
```

When both headers and query parameters are present, headers take precedence.

API URL overrides are limited to `browserless.io`, its subdomains, the configured `BROWSERLESS_API_URL` origin (same scheme, hostname, and port), and hosts listed in `MCP_ALLOWED_API_URL_HOSTS`. Paths are allowed, but credentials, query strings, and fragments (including bare `?` or `#`) are not. Without an override, the operator-configured URL is used unchanged.

### Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "browserless": {
      "url": "https://mcp.browserless.io/mcp?token=your-token-here"
    }
  }
}
```

### Cursor

Add to your Cursor MCP settings:

```json
{
  "mcpServers": {
    "browserless": {
      "url": "https://mcp.browserless.io/mcp?token=your-token-here"
    }
  }
}
```

### VS Code

Add to your VS Code settings (`settings.json`):

```json
{
  "mcp": {
    "servers": {
      "browserless": {
        "url": "https://mcp.browserless.io/mcp",
        "headers": {
          "Authorization": "Bearer your-token-here"
        }
      }
    }
  }
}
```

### Windsurf

Add to your Windsurf MCP configuration:

```json
{
  "mcpServers": {
    "browserless": {
      "url": "https://mcp.browserless.io/mcp?token=your-token-here"
    }
  }
}
```

## Self-Hosting

The server can also be run locally — useful for air-gapped deployments or pointing at a self-hosted Browserless instance. Clone this repo and build the Docker image:

```bash
docker build -f docker/Dockerfile -t browserless-mcp .

docker run \
  -e BROWSERLESS_TOKEN=your-token \
  -e BROWSERLESS_API_URL=https://your-browserless-instance.example.com \
  -p 8080:8080 \
  browserless-mcp
```

Then point your MCP client at `http://localhost:8080/mcp` using the same header/query-parameter auth as above.

### Self-hosted environment variables

| Variable                    | Required | Default                                 | Description                                                                                                                                                                                                                                                               |
| --------------------------- | -------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BROWSERLESS_TOKEN`         | Yes      | —                                       | Your Browserless API token                                                                                                                                                                                                                                                |
| `BROWSERLESS_API_URL`       | No       | `https://production-sfo.browserless.io` | API endpoint (for self-hosted Browserless)                                                                                                                                                                                                                                |
| `MCP_ALLOWED_API_URL_HOSTS` | No       | —                                       | Comma-separated hosts allowed for client-supplied API URL overrides, in addition to Browserless and the configured API origin                                                                                                                                             |
| `BROWSERLESS_API_SERVER`    | No       | `https://api.browserless.io`            | Account API host — backs `browserless_account`, `_usage`, `_sessions` and `_logs`. A different host from `BROWSERLESS_API_URL`, which is a browser runtime. Trusted: replay downloads fetch the signed link this API returns, so point it only at an endpoint you operate |
| `TRANSPORT`                 | No       | `stdio`                                 | Transport type: `stdio` or `httpStream`                                                                                                                                                                                                                                   |
| `PORT`                      | No       | `8080`                                  | HTTP server port (only for `httpStream` transport)                                                                                                                                                                                                                        |
| `BROWSERLESS_TIMEOUT`       | No       | `30000`                                 | Request timeout in milliseconds                                                                                                                                                                                                                                           |
| `BROWSERLESS_MAX_RETRIES`   | No       | `3`                                     | Max retry attempts for failed requests                                                                                                                                                                                                                                    |
| `BROWSERLESS_CACHE_TTL`     | No       | `60000`                                 | Cache TTL in milliseconds (0 to disable)                                                                                                                                                                                                                                  |
| `AMPLITUDE_API_KEY`         | No       | —                                       | Amplitude project API key. Sends MCP usage analytics — SDK lifecycle events plus our own tool/skill events                                                                                                                                                                |
| `MCP_COMPLIANCE_MODE`       | No       | unset (full surface)                    | Serve the reduced, directory-compliant surface. Fails closed: any set value except `false`/`0`/`no`/`off` enables it                                                                                                                                                      |

### Hosted OpenTelemetry

On `httpStream`, set `OTEL_ENABLED=true` and `OTEL_EXPORTER_OTLP_ENDPOINT`
to a trusted collector's base URL. Metrics and stderr logs are exported over
OTLP HTTP/protobuf; tracing is disabled. `stdio` never starts this SDK.
`OTEL_METRIC_EXPORT_INTERVAL` controls the export interval in milliseconds
(default 60000, minimum 1000).

NodeSDK manages the providers and standard Node runtime instrumentation emits
`nodejs.*` and `v8js.*` metrics. Application session, tool, account and Redis
metrics remain under `browserless.mcp.*`. All duration metrics use seconds
(`s`), memory uses bytes (`By`), and request counters use `{request}`;
application callers still supply
milliseconds, converted once by the metric recorders.

`browserless.mcp.auth.duration` and `.auth.in_flight` cover POST authentication,
not whole request execution. `browserless.mcp.session.cleanup.age` covers tracked
MCP idle reaps and explicit/idle/cap agent cleanup, not every session close.
Sweep-discovered MCP age starts at discovery; agent age resets on reconnect.
`browserless.mcp.accounts.active` counts distinct token hashes per collection
window. Top-account counts are an INFO log with body event
`browserless.mcp.account.window`, `active_accounts`, and `top_accounts` (at most
20 `{hash, count}` entries), not persistent per-account metric series.
An idle window emits no account log. Stderr text is INFO, `console.warn` is WARN,
and arguments containing an `Error` are ERROR; stderr records carry
`log.iostream=stderr`. `OTEL_SDK_DISABLED=true` also disables hosted telemetry.

For dashboards based on the initial telemetry PR: replace custom event-loop
metrics with `nodejs.eventloop.*`, GC pauses with `v8js.gc.duration`, and active
resources with `v8js.resource.active`. Drop `_ms` from tool/upstream/Redis
duration names, use the auth and cleanup names above, and divide millisecond
thresholds by 1000. Replace `account.requests` queries with the window logs.
Duration histogram boundaries are also in seconds, covering sub-second Redis
operations through two-hour sessions. Amplitude event fields and timeout
configuration remain in milliseconds.

### Skill retrieval diagnostics

`Skill Retrieval Completed` emits once per actual remote skill fetch through
the existing analytics queue (when `ANALYTICS_ENABLED`, `SQS_QUEUE_URL`, and
`SQS_REGION` are configured). It is not duplicated through the SDK's
`AMPLITUDE_API_KEY` transport. Cache hits and concurrent callers sharing a fetch
do not emit another completion. Failed retrievals remain retryable on the next
call; this instrumentation adds no retries.

Fields are `result=hit|miss|error`, normalized `domain`, UUID `request_id`,
`source`, `attempt`, integer `duration_ms`, `stage=fetch|decode|validate`, and
available `http_status`. `skill_count` appears only on valid responses: positive
for hits, zero for misses. Errors carry `error_category=timeout|network_error|http_error|invalid_json|invalid_shape`.
Sources are `cli_agent`, `script_builder`, `autologin`, `agent_run`, `mcp_client`,
or `unknown`. Each fetch currently has `attempt=1`; no run identifier is
available at these call sites, so `run_id` is omitted.
Domains outside the bounded hostname format become `invalid` without dropping
the completion from the denominator.

Set `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` to a trusted collector's full `/v1/logs`
URL to export matching `skill.retrieval.failed` WARN records as OTLP/HTTP JSON.
The default is disabled. Exports have a one-second deadline, at most 16 in-flight
requests, and no retry. Caught queue/skill analytics errors produce
`skill.telemetry.delivery_failed` with `originating_event` and the fixed
diagnostic category `delivery_error`, at most once per minute per process.
Exporter failures are swallowed without recursively reporting themselves.
No new log contains tokens, prompts, full URLs, response bodies, or recipe text.
The queue retains its existing authentication field, separately from log fields.

Example failure attributes:

```json
{
  "event.name": "skill.retrieval.failed",
  "result": "error",
  "domain": "shop.example",
  "request_id": "416e0409-25e2-4399-a3fa-6939f43a75e0",
  "source": "mcp_client",
  "attempt": 1,
  "stage": "fetch",
  "error_category": "http_error",
  "http_status": 429,
  "duration_ms": 17
}
```

Retrieval error rate is error completions / all completions. Hit rate is hit
completions / valid completions. Do not add the separate server `Skill Lookup`
events to either denominator. Tests use local/mock sinks; a configured exporter
or console message is not proof of remote receipt.

### Failure diagnostics

`MCP Tool Request` retains `analytics_version=2`, the existing coarse
`error_category`, `status_code`, timing and tool-specific properties. These
additive diagnostic fields are failure-only; a successful retry has none of them.

| Property               | Meaning                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `error_reason`         | `selector_miss`, `invalid_params`, `unknown_method`, `script_error`, `unauthorized`, `forbidden`, `not_found`, `server_error`, `session_lost`, `navigation_failed`, `timeout`, `rate_limited`, `no_results`, `response_too_large`, or `unknown`. Agent errors retain their existing detailed classification; local URL/batch validation is `invalid_params`. |
| `error_source`         | `validation`, `script`, `target_website`, `api`, `transport`, or `unknown`. This identifies the observed failure boundary, not blame. A 403 alone does not identify its source.                                                                                                                                                                              |
| `failed_command_index` | Zero-based index in the invocation's command batch, not the session-wide command counter. Omitted when setup/validation fails before a command starts.                                                                                                                                                                                                       |
| `failed_method`        | The failed command's recognized typed method name. Unrecognized free-form method names are omitted to avoid emitting arbitrary input; the index still identifies the command.                                                                                                                                                                                |
| `error_code`           | Allowlisted structured codes: the uppercase reason names above, `SELECTOR_NOT_FOUND`, `BROWSER_CRASHED`, `ECONNRESET`, `ECONNREFUSED`, `ENOTFOUND`, `EAI_AGAIN`, `ETIMEDOUT`. Opaque/unrecognized codes are omitted, not copied into messages.                                                                                                               |
| `error_status_code`    | An integer HTTP status (100–599) carried by structured error metadata. Never extracted from error prose.                                                                                                                                                                                                                                                     |
| `error_status_origin`  | `api` for an observed API response/upgrade, `target_website` for a failed navigation result, otherwise `unknown`. Omitted when no structured status is available.                                                                                                                                                                                            |
| `error_message`        | A synthesized summary capped at 500 characters. Raw error messages, response bodies, HTML, scripts, selectors, credentials, cookies, authorization headers and URLs are never copied into this field.                                                                                                                                                        |

For `browserless_function`, `browserless_export` and `browserless_smartscraper`,
a failed result's recorded `status_code` sets the reason: 400 from the function
tool → `script_error` (source `script`), other 400 and 422 → `invalid_params`,
401 → `unauthorized`, 403 → `forbidden`, 404 → `not_found`, 408 → `timeout`,
429 → `rate_limited`, 5xx → `server_error`, anything else → `unknown`. Function and export statuses are the Browserless API's
(`error_status_origin=api`); smart scraper statuses are the target website's
(`error_status_origin=target_website`, `error_source=target_website`). A function
text result over the size limit is `response_too_large` (source `script`). Without
a status, the generic codes `INTERNAL_ERROR` and `TAB_NOT_FOUND` map to
`server_error` and `not_found`.

`status_code` keeps its original tool-specific meaning; the new status fields
do not replace it or turn successful target-page HTTP responses into failures.
HTTP failures retain API response status even when thrown. Codes are retained
when already available in structured errors or the JSON body read by the existing
4xx error handler; diagnostics do not read additional bodies on 5xx failures.

An unsuccessful search classifies the backend's `errorCode` (source `api`, kept
in `error_code`): `upstream_timeout` → `timeout`, `upstream_unavailable` →
`server_error`, `no_results` → `no_results`. One without structured evidence
reports `error_reason=unknown`
and `error_message="Unclassified search failure."`. Its legacy `user_error`
category remains for chart compatibility, not as evidence of caller fault.

Example breakdowns: filter `success=false` and group by `tool → error_reason`;
for agent calls, group by `failed_method → error_reason`; for HTTP failures,
group by `error_status_origin → error_status_code`. Missing fields in older
events mean unavailable instrumentation, not an `unknown` failure. There is no
historical backfill. Verify representative received events after deployment
before treating these properties as available in production.

## Live URL instructions

`browserless_live_url` creates viewer links over REST for an existing browser.
Set `action: "create"`, `browserId`, and `interactable: true` for a human handoff:

```json
{
  "action": "create",
  "browserId": "running-browser-id",
  "interactable": true,
  "instructions": {
    "layout": "panel",
    "format": "markdown",
    "title": "Complete sign in",
    "summary": "Enter your **verification code**.",
    "steps": ["Open your inbox", "Enter the code"],
    "currentStep": 2
  },
  "doneLabel": "Continue",
  "failLabel": "Need help",
  "requesterName": "Support"
}
```

Interactive `browserless_agent` live URLs and `browserless_live_url` interactive creates
and inline handoffs default to "Complete the step shown on screen, then use the
completion button." when `instructions` is omitted. Pass specific instructions
when possible. Agent links with `interactable: false` and view-only REST creates have no default
instructions. An explicit empty or whitespace-only string disables the card and
Done button for agent links and REST creates. Inline `handoff` requires nonblank
plain-text instructions when supplied; blank text is rejected.

Plain string instructions still work. With a string, use top-level
`instructionsTitle`, `steps`, `currentStep`, and `instructionsFormat`.
With an object, put these values inside `instructions` as `title`, `steps`,
`currentStep`, and `format`. Button labels and requester name stay top-level.
Titles allow 80 characters; up to 10 non-empty steps allow 300 characters each;
button labels allow 40 characters; requester names allow 60 characters.
`currentStep` is one-based and must refer to a supplied step. Summary text is
trimmed and capped at 2048 characters by the server. Steps-only cards are
supported. Markdown links must use HTTP or HTTPS. The server validates
cross-field constraints; deployments must support rich REST instructions.

## MCP Resources

| Resource URI                     | Description                                                                                                                     |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `browserless://api-docs`         | Smart scraper API documentation                                                                                                 |
| `browserless://status`           | Live service health status                                                                                                      |
| `ui://browserless/view/v12.html` | Bundled replay, screenshot, video, handoff, account usage, active sessions, and request-log explorer widget (full surface only) |

The previous `ui://browserless/view/v1.html` through `ui://browserless/view/v11.html`
resources remain readable as aliases of the current widget. Newly discovered
tools advertise v12 so hosts discover the request-log explorer.

## MCP Prompts

| Prompt            | Description                                 |
| ----------------- | ------------------------------------------- |
| `scrape-url`      | Scrape a webpage and summarize its content  |
| `extract-content` | Extract specific information from a webpage |

## Development

```bash
npm install
npm run build
npm test
npm run coverage
```

### Tests

The test suite uses [Mocha](https://mochajs.org/) with [Chai](https://www.chaijs.com/) and [Sinon](https://sinonjs.org/). Specs live alongside the code in `test/` (`test/lib/`, `test/tools/`, `test/prompts/`, `test/resources/`, `test/integration/`) and run against the compiled output in `build/`.

- `npm test` — compiles TypeScript and runs every `*.spec.js` under `build/test/`. No external services or `BROWSERLESS_TOKEN` are required; the API client is stubbed.
- `npm run coverage` — runs the suite under [c8](https://github.com/bcoe/c8) with the thresholds configured in `package.json` (lines ≥ 80%, branches ≥ 70%, functions ≥ 80%).

Tests run automatically on every pull request via the [Test workflow](.github/workflows/test.yml) on Node 24.x and 26.x. Development and publishing use Node 26 from `.nvmrc`; with nvm, run `nvm install` and `nvm use`. PRs must keep the suite green before they can merge.

The published package retains its Node.js >=24 engine requirement; Node 22 is not supported. Use an LTS release (24 or 26). Docker uses a digest-pinned Node 26 Trixie image, with weekly Dependabot digest updates and Node-major upgrades excluded. Official Linux Node 26 binaries require `libatomic`; the image includes `libatomic1`. Container publishing targets Linux amd64 and arm64.

## API Token

Get your API token at [browserless.io](https://browserless.io). The token authenticates all requests to the Browserless API.

## License

SSPL-1.0

TDQS

A4/5.0

Scored across 19 tools

Disambiguation4/5

The set covers distinct surfaces (agent automation, single-page scrape, crawl, map, export, custom function, observability, payments) and most descriptions explicitly cross-reference the right alternative. A few boundaries remain fuzzy: browserless_show vs browserless_view_data vs the replay action in browserless_sessions all concern rendered views, and browserless_account vs browserless_usage vs browserless_logs split one observability domain across three tools. The descriptions mostly resolve these, but an agent could still misselect on the view/replay trio.

Naming Consistency5/5

Every tool uses the same browserless_ snake_case prefix with predictable noun or verb_noun suffixes (browserless_crawl, browserless_map, browserless_logs, browserless_link_checkout). There are no camelCase or stylistic outliers. The convention is uniform and readable throughout.

Tool Count3/5

At 19 tools the server sits in the heavy 16-25 band, driven by a broad platform surface plus niche entries such as browserless_link_connect, browserless_link_checkout, and browserless_view_data. Most tools map to a real capability, but the set is larger than a typical agent needs for routine browser tasks. It is defensible for a full Browserless platform but borderline in count.

Completeness4/5

Coverage is broad: agent-driven automation, scraping (single, crawl, map, search, export, custom Puppeteer), performance audits, sessions/replays, profiles, usage/logs, account, live URLs, and Stripe Link. Minor gaps exist: profiles are list-only with no create/delete tool, and session termination is only reachable through the agent close command rather than an explicit session tool. These are workable around but not fully closed loops.

Maintenance

ActivityActive
ResponsivenessOutdated data