Connect
The hosted endpoint is https://browselenium.online/mcp (streamable HTTP).
The browser runs on our infrastructure; your process only sends tool calls and receives
JSON, images and text back.
// opencode — opencode.json { "$schema": "https://opencode.ai/config.json", "mcp": { "browselenium-online": { "type": "remote", "url": "https://browselenium.online/mcp" } } }
// Claude Code — .mcp.json { "mcpServers": { "browselenium-online": { "type": "http", "url": "https://browselenium.online/mcp" } } }
Self-hosting: the same 32 tools run locally from this repository
(uv run browselenium-online with BROWSELENIUM_ONLINE_API_KEY set,
or the full hosted stack via BROWSELENIUM_ONLINE_HOSTED=1). Tool signatures
are identical; see the README and docs/BACKEND.md for deployment details.
Authentication
If the server has an API key configured, every browser tool errors until you call
authenticate(key=...) with the key from your dashboard.
The key is shown once at signup and can be rotated any time (rotation invalidates the old
key immediately).
- Free calls:
authenticate,get_skillandbrowser_statusnever consume quota. - Key storage for agents: save the key as a single line in
~/.config/browselenium.online/api-key(mode 600) and reuse it on later sessions instead of asking the user again. - Sessions are per connection: in hosted mode, one active browser per API key. A second connection must stop the running session first.
Core workflow
authenticate(key="bso_...")— once per session; returns a quickstart.browser_start(headless=true)— launch (or replace) the cloud browser.navigate(url, wait_seconds=5..10)— load a page; give bot checks time to resolve. Checkchallenge_detectedin the result.- Read and act —
page_structure/get_text/find, thenclick/type_text/execute_jsas needed. browser_stop()— always stop when done; a leaked session pins a browser process and burns its lifetime.
tab_new / tab_switch for parallel pages. The browser persists
across calls — cookies, history and logins survive until browser_stop or the
tier's lifetime limit kills it (just browser_start again).
Session tools
Validate an API key and open the metered session. Returns tier, daily quota and a quickstart on success. Required before any other browser tool when the server is key-gated.
| Param | Description |
|---|---|
key | Your API key (bso_...) from the dashboard. |
Returns the full agent skill: tool table, workflows (reading pages, passing bot checks, logging in, extracting tables), tier limits and ground rules. Call it once after authenticating; it teaches the agent everything else on this page.
Launch the browser, replacing any running session. Starts the lifetime clock of the current tier.
| Param | Description |
|---|---|
headless | Run without a visible window. Standalone only — hosted browsers are always headless. |
window_size | Viewport size as "WIDTHxHEIGHT". |
proxy | Hosted sessions always egress through a proxy — the operator's Cloudflare WARP proxy by default, falling back to a shared rotating public proxy — so the server's real IP is never exposed; you cannot opt out or supply a custom proxy. "public" explicitly routes through the rotating pool (refreshed hourly; failed routes are retried automatically on a different proxy — quota counts only once the routed session is up, see the TOS public-routing terms). Standalone: http(s)://host:port or socks5://host:port. |
user_agent | Override the browser's User-Agent string. |
locale | Browser locale, e.g. "en-US", "de-DE". |
extra_args | Standalone only: extra browser command-line flags. |
No-auth health check: is the browser running, current URL and title, uptime, and usage.requests_today / requests_limit. Check it when a limit error seems near.
Quit the browser and end the session. Always call it when finished — frees the hosted slot for your key and stops the lifetime clock.
Reading tools
Visible text of the page — your first look after navigating. Cheaper than HTML; prefer it for content extraction.
Full page HTML, or the outerHTML of the first element matching a CSS selector. Use for exact markup; use page_structure for orientation.
Compact accessibility-tree snapshot: roles, names, ids, link hrefs, form values and states (node count capped). The token-cheapest way to understand page state and build selectors — prefer it over get_html/get_text/screenshots for orientation.
Element summaries (tag, text, href, classes) for matches — use it to map a page before acting.
| Param | Description |
|---|---|
selector | CSS selector, or XPath when it starts with / or (. |
limit | Maximum number of matches returned. |
Block until an element matching the selector exists. Essential for SPAs and XHR-rendered content: navigate, then wait for a known content selector before reading.
Capture the page as PNG (size-capped server-side).
| Param | Description |
|---|---|
path | Standalone: server-side file path (default under ~/.cache/browselenium.online/screenshots, unique per call). Hosted: ignored — the image is returned directly in the response and the server-side copy is deleted immediately. |
full_page | Capture the entire scrollable page instead of the viewport. |
List captured network requests of the page (url, type, duration in ms, size in bytes). Premium tier only.
Acting tools
Wait until the element is clickable, click it, and return the new page state.
| Param | Description |
|---|---|
selector | CSS selector or XPath (as in find). |
timeout | Seconds to wait for clickability before failing. |
kind | "left" (default), "double", "right", or "hover" (move the mouse over the element without clicking). |
Type into an input or textarea, element by element.
| Param | Description |
|---|---|
selector | Target field (CSS or XPath). |
text | Text to type (passwords included — they are not echoed back). |
clear | Empty the field first (default true). |
press_enter | Submit with Enter afterwards — the easy way to send forms. |
Press a key combination on the focused element: "ENTER", "ESC", "TAB", "CTRL+a", "SHIFT+?", single characters. Separator-tolerant; invalid combos return an instructive error.
Scroll the page; positive is down, negative up. Use to trigger lazy loading before extracting content. With selector, scrolls that element into view.
Switch into an iframe (CSS/XPath selector, or its index among the page's iframes); no args returns to the top document. find/click/type_text then run inside the frame.
Accept (default) or dismiss a JS alert/confirm/prompt; returns its text. Stray dialogs are also auto-accepted by click/type_text/press_keys/execute_js so commands never wedge.
Read any attribute (data-*, style, custom) of the first element matching selector - beyond the fixed attribute set find() reports.
Resize the window mid-session; same clamps as browser_start.
Auto-solver for Cloudflare interstitials (clicks the Turnstile checkbox). navigate() runs it automatically when the account default is on; call this after a snapshot shows challenge_detected.
Run JavaScript in the page and return its value. Use return ... to hand data back — e.g. extract a whole table in one round-trip instead of N find calls. Runs with page privileges: never feed it content you got from an untrusted page.
Select an <option> in a <select> and fire the change event — works with framework-managed selects where clicking options does nothing.
| Param | Description |
|---|---|
value | What to match against (see by). |
by | "text" (default), "value", or "index". On a miss, the error lists the available options so you can correct it. |
Browser history of the current tab: "back", "forward", or "refresh".
State tools
Export the whole cookie jar as JSON. Save it to keep a login session across runs, machines and serverless invocations.
Restore cookies from a get_cookies export (navigate to the site first). The cheapest way to stay logged in between sessions.
Delete one cookie by name; with no name, clear the entire jar (fresh logged-out state).
List open tabs with index, title and URL.
Open a new tab (navigating to url if given) and switch to it. The way to work on several pages in one session.
Switch to the tab at index (see tab_list).
Close the current tab and switch to the first remaining one. Closing the last tab resets it to about:blank instead of closing, so the session stays alive.
Tiers & metering
Every browser tool call consumes one request of the daily quota;
authenticate, get_skill and browser_status are free.
Quota resets at midnight UTC. The browser is killed after the tier's maximum lifetime —
start a new one with browser_start.
| Tier | Price | Requests/day | Max browser life | network_requests |
|---|---|---|---|---|
| free | $0 | 50 | 1 min | — |
| pro | $5/mo | 500 | 5 min | — |
| pro+ | $7/mo | 750 | 10 min | — |
| premium | $25/mo | 2000 | 15 min | yes |
navigate + one page_structure/get_text beats
ten small calls; extract tables with a single execute_js; always
browser_stop when idle.
Errors & limits
- Quota exhausted — tools error until the next UTC day or an upgrade.
browser_statusshows usage. - Lifetime exceeded — the browser is killed at the tier's max life; just
browser_startagain. - Challenge detected —
challenge_detected: truein a navigate result means slow down, not push harder: space retries at least 5 s apart, prefer a longerwait_seconds. Standalone installs can also retry withheadless=falseor a residential proxy. - Session on another connection — hosted mode allows one active browser per key; stop it on the other connection (or wait for it to end) before starting again.
- Blocked destination — hosted browsing is public-web only: non-http(s) URLs and internal/loopback addresses are refused and flagged.
Examples
Read a page
authenticate("bso_...") browser_start(headless=true) navigate("https://target.example/pricing", wait_seconds=5) # check challenge_detected page_structure() browser_stop()
Log in and keep the session
navigate("https://app.example/login") find("input") type_text("#email", "[email protected]") type_text("#password", "••••••••", press_enter=true) get_text() # verify you are in cookies = get_cookies() # save the JSON for next time browser_stop() # next run: navigate to the site, then set_cookies(saved) — still logged in
Extract a table in one round-trip
execute_js("return JSON.stringify({ headers: [...document.querySelectorAll('table th')].map(t => t.textContent.trim()), rows: [...document.querySelectorAll('table tbody tr')].map(tr => [...tr.children].map(td => td.textContent.trim())) })")
More workflows — passing bot checks, SPA handling, search, dropdowns — live in the skill:
call get_skill() after authenticating, or read
agent-install.md to set up your agent in one paste.