MCP server · browser automation · v1.11.0

Give an agent a browser, and everything it saw along the way.

browse4me is a Model Context Protocol server that hands an AI agent a real Chromium session over HTTP. Not just click and type — the console, the network, the screenshots, and a set of notes the agent writes to itself and finds again on the next run.

tool surface43 × mcp
create_sessionlaunch_tabclose_tabget_current_urllist_sessionsget_sessionnavigateclick_elementclick_coordinatesinput_texthoverscrollpress_keyget_element_contentupload_fileselect_dropdownset_checkboxselect_radiowait_for_elementwaitwait_for_element_valuefind_element_by_textfind_input_by_labelfind_element_nearquery_domfind_elements_in_areatake_snapshotstart_recordingstop_recordinglist_user_recordingsget_user_recordingreplay_recordingstart_network_capturestop_network_captureget_network_entriesset_request_interceptionwait_for_requestwait_for_responseget_console_logsadd_notelist_notesget_notedelete_note
Every name carries a browser_ prefix on the wire. Highlighted: search, network, console, notes.
MCP tools
43
Browser engines
2 Playwright · Puppeteer
Search strategies
5
MCP resources
7 + live session data
Max viewport
4K 3840×2160
src/engines/

Two engines behind one interface

Pick the engine when you create the session. The tool schemas do not change, the arguments do not change, and neither does your agent’s code.

Playwright 1.56default
  • Auto-waiting on actionability — fewer explicit waits in agent code
  • Chromium out of the box, with Firefox and WebKit available to the engine
  • Native request routing, which the network capture layer builds on
  • The engine to reach for unless a site actively fights automation
Puppeteer 24.24stealth
  • puppeteer-extra-plugin-stealth strips the usual automation fingerprints
  • Direct CDP access when a tool call needs to go lower than the abstraction
  • Leaner startup and memory footprint per session
  • The engine for bot-protected targets
session setuplocale · timezone · headers
browser_create_session({
  engine: "puppeteer",        // or "playwright" — the default
  viewportWidth: 1920,       // 1920×1080 default, 3840×2160 ceiling
  viewportHeight: 1080,
  locale: "de-DE",
  timezone: "Europe/Berlin",
  headers: { "X-Feature-Flag": "checkout-v2" }
})

// Tabs inherit locale, timezone and headers from the session.
// Override any of them per tab. Ten tabs per session.
browser_launch_tab({ sessionId, url: "https://shop.example.com", locale: "fr-FR" })
src/mcp/tool-definitions.ts

All 43 tools

Each one is a JSON Schema the MCP client validates before the call ever reaches a browser. This is the complete surface — nothing here is planned or partial.

Sessions & tabs

6
  • browser_create_sessionOpen a session on a chosen engine, viewport, locale, timezone and header set.
  • browser_launch_tabNew tab in a session, inheriting or overriding its context.
  • browser_close_tabClose one tab and free its resources.
  • browser_get_current_urlWhere a tab actually ended up after redirects.
  • browser_list_sessionsPast and live sessions with status and duration, persisted in Postgres.
  • browser_get_sessionOne session in full, including every note written against it.

Navigation

1
  • browser_navigateGo to a URL with a waitUntil strategy: load, domcontentloaded or networkidle.

Interaction

8
  • browser_click_elementClick by XPath, with forceJsClick for framework handlers.
  • browser_click_coordinatesClick a pixel position — canvases, maps, custom widgets.
  • browser_input_textType into an input by XPath.
  • browser_hoverTrigger hover menus and tooltips.
  • browser_scrollScroll the page or a scrollable element.
  • browser_press_keyKeys with Ctrl, Shift and Alt modifiers.
  • browser_get_element_contentRead text, HTML or attributes off an element.
  • browser_upload_fileAttach a file to a file input, confined to the server’s upload root.

Form controls

3
  • browser_select_dropdownChoose an option by value, label or index.
  • browser_set_checkboxSet checked state explicitly rather than toggling blind.
  • browser_select_radioPick one option out of a radio group.

Waiting

3
  • browser_wait_for_elementBlock until an element is visible.
  • browser_waitA flat millisecond pause, for animations and timing.
  • browser_wait_for_element_valueWait for content or value to arrive — AJAX-populated selects and legacy screens.

Finding

5
  • browser_find_element_by_textFuzzy text match with a configurable threshold.
  • browser_find_input_by_labelResolve an input from its label, aria-label, wrapper or placeholder.
  • browser_find_element_nearSpatial search around an anchor by direction and distance.
  • browser_query_domA plain-English query parsed into a DOM filter.
  • browser_find_elements_in_areaEverything inside a pixel bounding box.

Screenshots

1
  • browser_take_snapshotFull page, viewport or a single element. Uploads to S3 and returns a URL; falls back to base64 when storage is off.

Recording & replay

5
  • browser_start_recordingBegin capturing the agent’s own actions.
  • browser_stop_recordingStop and return Chrome Recorder JSON.
  • browser_list_user_recordingsRecordings uploaded from the Chrome extension.
  • browser_get_user_recordingFetch one recording’s steps.
  • browser_replay_recordingReplay any recording in any session, server-side.

Network

6
  • browser_start_network_captureRecord requests and responses with filters.
  • browser_stop_network_captureStop and export as HAR 1.2 or JSON.
  • browser_get_network_entriesQuery captured traffic mid-session.
  • browser_set_request_interceptionBlock, rewrite or mock requests by priority-ordered rules.
  • browser_wait_for_requestBlock until a request matches a URL pattern.
  • browser_wait_for_responseBlock until its response comes back.

Console

1
  • browser_get_console_logsConsole output captured continuously per tab — log, warn, error, plus uncaught page exceptions. Filter by level.

Session notes

4
  • browser_add_noteWrite an observation, DOM quirk or workaround against the session.
  • browser_list_notesNotes in order, filterable by tag.
  • browser_get_noteOne note by id.
  • browser_delete_noteSoft-delete a note.
src/search/

Five ways to find an element

Agents do not have your CSS selectors, and pages rarely hand out stable ids. Every strategy returns the same shape: XPath, tag, text, visibility and a bounding box, ranked.

text-finder.tsfuzzy

Levenshtein distance over visible text, so a typo or a re-worded label still resolves.

browser_find_element_by_text({
  tabId,
  text: "Sbumit Order",
  threshold: 0.8,
  elementType: "button"
})
// → matches "Submit Order"
label-finder.tsassociation

Handles both HTML patterns — explicit <label for> and an input wrapped inside its label — plus aria-label and placeholder.

browser_find_input_by_label({
  tabId,
  labelText: "Email Address"
})
// → the input, however it was wired up
proximity.tsspatial

When nothing connects a field to its caption but position on screen, search outward from an anchor.

browser_find_element_near({
  tabId,
  anchorXpath: "//span[text()='Username']",
  direction: "below",
  maxDistance: 100
})
query.tsnatural language

A parser, not a model call — it reads element type, text filter, visibility and state out of the phrase.

browser_query_dom({
  tabId,
  query: "visible clickable elements with delete text"
})
// → tag:button|a + text:delete + visible
area-finder.tsbounding box

Pair it with a screenshot: the agent sees a region, then asks what is actually inside those coordinates.

browser_find_elements_in_area({
  tabId,
  x: 840, y: 120,
  width: 360, height: 480
})
combineda login, end to end
browser_create_session({ engine: "playwright" })
browser_launch_tab({ sessionId, url: "https://app.example.com/login" })

browser_find_input_by_label({ tabId, labelText: "Email" })
browser_input_text({ tabId, xpath: "//input[@id='email']", text: "agent@co.com" })

browser_click_element({ tabId, xpath: "//button[@type='submit']" })
browser_wait_for_element({ tabId, xpath: "//div[@data-view='dashboard']" })
browser_take_snapshot({ tabId, fullPage: true })
forceJsClick

Clicks that survive a synthetic event system

React, Vue, Svelte and Angular attach their own listeners. A native click can land on the page and never reach the handler — and the failure is silent, which is worse than an error.

the escape hatchone flag
// Native click — the default, and correct most of the time
browser_click_element({ tabId, xpath: "//button[@data-testid='checkout']" })

// Nothing happened? Dispatch the full sequence instead.
browser_click_element({
  tabId,
  xpath: "//button[@data-testid='checkout']",
  forceJsClick: true
})
// → mousedown → mouseup → click, each bubbling, each with real coordinates
// Also available on browser_click_coordinates.
  • Overlays stop mattering. The event goes to the element you named, not to whatever sits on top of it.
  • Shadow DOM boundaries stop mattering for the same reason.
  • Reach for it as a second attempt, not a default — a native click is still the more faithful simulation.
src/recording/

Record once, replay anywhere

Two ways in, one format out. Both produce Chrome Recorder JSON, which DevTools already reads and any agent can replay.

agent-drivenautomatic

Start a recording and keep working. Every MCP tool call the agent makes lands in the transcript — navigations, clicks, typing, key presses, scrolls and waits.

browser_start_recording({ tabId })
// ... the agent does its work ...
browser_stop_recording({ recordingId })
user-drivenchrome extension

Show the agent how it is done. Record yourself doing the flow by hand, upload it, and the agent replays your steps instead of rediscovering them.

  • Point the extension at your server and project key
  • Start, click through the flow, stop, upload
  • The agent finds it with browser_list_user_recordings
chrome recorder jsonthe shared format
{
  "title": "Checkout flow",
  "steps": [
    { "type": "navigate", "url": "https://shop.example.com/cart" },
    { "type": "click", "selectors": [["#promo-code"]] },
    { "type": "change", "selectors": [["#promo-code"]], "value": "SPRING25" },
    { "type": "click", "selectors": [["button[type=submit]"]] },
    { "type": "waitForElement", "selectors": [[".order-confirmed"]] }
  ]
}

Replay runs on the server, through the same tool layer as a direct call — so a replayed step gets the same URL checks, the same quota accounting and the same audit trail as anything else.

src/network/

The network layer is a first-class tool, not a log file

An agent that can only see the DOM is guessing about why a page misbehaved. Six tools let it watch the traffic, wait on specific calls, and change what comes back.

capture

Requests and responses with headers, timings and bodies. Filter by resource type or URL pattern so the transcript stays readable.

intercept

Block, rewrite or mock. Rules are ordered by priority, so a specific mock can sit in front of a broad block.

synchronise

Wait on the request or the response itself instead of guessing at a sleep length after a click.

export

HAR 1.2 out of browser_stop_network_capture. Opens in DevTools, Charles, or anything that reads HAR.

debugging a failing checkoutcapture → mock → har
browser_start_network_capture({
  tabId,
  resourceTypes: ["xhr", "fetch"],
  urlPatterns: ["*api.example.com*"]
})

// Force the failure path instead of waiting for it to happen
browser_set_request_interception({
  tabId,
  rules: [{
    urlPattern: "**/api/payment",
    response: { status: 502, body: { error: "upstream_timeout" }, delay: 500 },
    priority: 1
  }]
})

browser_click_element({ tabId, xpath: "//button[text()='Pay now']" })
browser_wait_for_response({ tabId, urlPattern: "**/api/payment" })
browser_get_console_logs({ tabId, level: "error" })
browser_stop_network_capture({ tabId, format: "har" })
console logs · session notes

What the agent learned, still there next time

Sessions, notes, recordings and screenshot metadata all live in Postgres. A run that ends is not a run that disappears.

console capturealways on

Console output is collected per tab from the moment it opens — no flag to set, nothing to start. That includes uncaught exceptions, which is usually the line that actually explains the failure.

browser_get_console_logs({ tabId, level: "error", limit: 50 })
session notesthe agent’s own memory

An agent that works out a page’s quirk can write it down and read it back later, tagged and searchable, instead of solving the same puzzle on every run.

browser_add_note({
  sessionId,
  content: "Cookie banner intercepts the first click. "
           "Dismiss #cc-accept before anything else.",
  tags: ["quirk", "shop.example.com"]
})

browser_list_notes({ sessionId, tag: "quirk" })
screenshotss3 pipeline
Modes
Full-page scrolling capture, viewport only, or a single element.
Storage
Uploaded to any S3-compatible bucket; the tool returns a URL. Without storage configured it returns base64 instead.
Private buckets
A proxy endpoint serves images the bucket will not expose directly, with a one-year cache header.
Retention
A cron job soft-deletes on your schedule, then hard-deletes from both database and bucket.
b4m

The same browser, from your shell

A browser session is stateful; a CLI is a series of short-lived processes. b4m bridges the two — it remembers the session and tab it opened, so you never paste back an id it just printed you.

installbrowse4.me/cli/install.sh
curl -fsSL https://browse4.me/cli/install.sh | bash

b4m login
b4m project join my-project
b4m open https://app.example.com
everydaynumbered results

find and query number what they return, so you act on !1 rather than copying an XPath out of a table.

b4m open https://app.example.com   # session + tab, remembered
b4m find "Sign in"                 # numbered matches
b4m click !1
b4m type !2 "hunter2"
b4m shot                            # prints the screenshot URL
b4m logs -l error
b4m shell https://app.example.com  # interactive
scriptingpipes & ci

-o json emits records rather than a rendered table, and colour disappears when stdout is not a terminal — so redirecting gives you a file, not escape codes.

b4m query links --json | jq -r '.[].attributes.href'
b4m run checkout-flow.json          # replay a Recorder file

# CI cannot complete a device flow — use a project key
export BROWSE4ME_API_KEY=proj_...
b4m open https://staging.example.com
anything the verbs missgenerated from the server

b4m tools is built from the live server, so it cannot drift out of date the way a hand-written list does.

b4m tools                                  # all 43
b4m tools browser_wait_for_element        # its arguments
b4m call browser_wait_for_element '{"xpath":"//h1","timeout":5000}'

b4m whoami   # which project, which URL, and where each setting came from
chrome-extension/

Teach it by doing it once

what it captures
  • Clicks, text input, scrolls and navigation, as you perform them
  • Written out as Chrome Recorder JSON — the same format the agent records
  • Uploaded to your project, scoped to your API key
getting there
  • Load the unpacked extension, set server URL and project key in options
  • Hit Start recording in the popup and use the site normally
  • Stop, then Upload
  • The agent lists and replays it over MCP
src/middleware/ · src/services/quota.ts

An agent with a browser is an agent with reach

Every endpoint that touches tenant data authenticates, and every project is isolated from every other. Sessions, tabs, notes, recordings and screenshots are only ever reachable by the project that created them.

project api keys

proj_-prefixed, bcrypt-hashed, scoped to one project. Rotate or revoke without touching anything else.

bitbot jwt

Federated identity verified against BitBot’s JWKS, with admin and user roles — and a system token for platform-to-platform calls.

ssrf guard

Navigation to loopback, private, link-local, cloud-metadata and in-cluster addresses is refused — checked before the request and again after every redirect.

url allowlisting

Restrict a project to the domains it is supposed to visit, so a misled agent cannot wander off with your data.

file uploads

Confined to a configured upload root, and switched off entirely unless you turn it on.

cors

Exact-match origin checking for credentialed requests. No wildcard reflection.

quotas & limitsdeclared, then enforced

Plan limits arrive from BitBot on the project record. What /bitbot/limits advertises is what the server actually enforces — counters live in Postgres, behind a short cache so a tool call does not pay for a query every time.

browsesPerMin
Navigations per rolling minute
sessionsPerDay
Sessions created per UTC day
screenshotsPerDay
Screenshots per UTC day
recordingsPerMonth
Recordings per UTC month
maxConcurrentSessions
Live sessions at once
networkCaptureEnabled
Feature flag — capture and interception
harExportEnabled
Feature flag — HAR export specifically

An unset limit, or -1, means unlimited. Feature flags describe what a plan includes rather than how hard it may be used, so the operator kill switch that disables rate limits deliberately does not grant them.

src/

How a tool call reaches a browser

request pathtop to bottom
clientMCP client — Claude, an SDK agent, or b4m
transportExpress 5 — POST /mcp over streamable HTTP
authProject API key · BitBot JWT via JWKS · system token · web session cookie
limitsRate limits, daily and monthly quotas, concurrency cap, feature flags
mcpMCP SDK — 43 tools, 7 doc resources, live session resources
sessionsSessionManager → BrowserManager — tabs, console capture, clipboard
engineBrowserEngine — Playwright (Chromium)  |  Puppeteer (Chrome + stealth)
servicesRecording · network capture · screenshot store · retention cron
statePostgreSQL — sessions, notes, recordings, screenshot metadata, usage  +  S3
in-client documentationMCP resources

Seven guides the agent can read without leaving the session — plus live resources for current sessions, tabs and recordings.

welcome://message
docs://best-practices
docs://search-strategies
docs://framework-clicks
docs://network-capture
docs://console-logs
docs://recording-guide
built with
Node 18+ ESMTypeScript 5.9Express 5 MCP SDK 1.20Playwright 1.56Puppeteer 24.24 PostgreSQLS3jose JWT bcryptnode-cronHAR 1.2
get started

Point an agent at it

Connect any MCP-compatible client to the server’s /mcp endpoint with a project API key, and the 43 tools show up in its tool list.

run it yourselfnode 18+
npm install
npm run build
npm start                # http://localhost:3000

# Chromium and Chrome are installed for you on postinstall.
# Configure PORT, DEFAULT_BROWSER_ENGINE, S3_* and the auth
# settings in .env before pointing anything real at it.