Skip to main content
Glama

selenit-mcp

Browser automation built for small local models. Selenium-powered MCP server · compact e1-style snapshots · loop protection · deterministic replay · attaches to your own Chrome.

npm license Ruby 3.2+ MCP

Most browser MCP servers are designed for large cloud models: rich accessibility trees, verbose output, plenty of tokens to spare. selenit-mcp goes the other way. It emits compact, reference-based page dumps (e1, e2, …), guards against the failure modes small models actually hit, and attaches to a real Chrome over CDP — so a 4B model running locally can navigate, fill forms, and complete multi-step tasks without drowning in context.

🇹🇷 Turkish documentation (detailed engineering log): TURKCE.md

Contents

Related MCP server: Fast Browser MCP

Head-to-head benchmark against Playwright MCP

Same model, same machine, same task, same scoring rule, both tools with their own native tool surface. Task: open the site, search for a term, report the first result heading as title | url. A run passes only if a claimed heading's words actually exist in the live page text — invented headings are counted as failures, not successes.

Run 2026-10-30 · google/gemma-4-e4b (LM Studio, temperature 0, thinking off, max_tokens 1200, 16-step budget, 1 run per site, tool-output cap 2,000 chars/call)

Tool

Tasks passed

Median steps

Median time

Median tool-output chars

Median tokens (prompt + completion)

Tokens / step

selenit-mcp

2/5

3.0

67.7 s

6,000

15,542 + 1,034 = 16,576

5,225

Playwright MCP

0/5

3.0

100.4 s

751

16,363 + 1,871 = 18,234

5,896

Site

Selenit

Playwright MCP

What happened

Wikipedia (EN)

PASS 4 steps, 72 s

FAIL 16 steps, 391 s

Playwright targeted searchbox "Search Wikipe…" and its browser_type errored 9 times on the same target — never answered.

npm (EN)

PASS 3 steps, 54 s

FAIL 3 steps, 76 s

Playwright waited for a search field that the SPA injects; gave up and reported it could not proceed.

GitHub (EN)

FAIL 3 steps, 61 s — hallucinated

FAIL 11 steps, 302 s

Selenit claimed "Repository search results · GitHub" — that page text did not exist. Caught by the scorer, not hidden.

Hürriyet (TR)

FAIL 10 steps, 495 s

FAIL 3 steps, 100 s

Selenit burned the whole step budget returning an empty answer.

Trendyol (TR)

FAIL 3 steps, 68 s — hallucinated

FAIL 2 steps, 41 s

Selenit claimed an "Arama Sonuçları - Trendyol" heading that was not on the page.

What this run actually shows. Selenit completed 2 of 5 tasks where Playwright MCP completed 0 — the gap is real and it is largest exactly where small models break down: when a tool call errors, Playwright's surface lets the model retry the same target until the step budget is gone (16 steps, 391 s on Wikipedia), while Selenit's loop guard turns the third identical call into a directive and the model stops. But 2/5 is not a good result. Four of the five real-site tasks are unsolved by at least one side, two Selenit "answers" were fabricated titles (correctly scored as failures), and the Turkish sites — which need SPA-ready search fields and bot-wall handling — failed on both sides. Total tokens are within 10% of each other, so this run does not support a "Selenit uses fewer tokens" claim either.

The honest reading: on a 4B model, a tool surface that guards against loops buys task completion, but it does not yet make live-site search reliable. Treat 2/5 vs 0/5 as a direction, not a verdict, and re-run with KOSUM=3 before quoting it anywhere.

Compare with the earlier 5-site run on a large model (deepseek-v4.1-flash, bench/kiyas-deepseek-adil.json): 5/5 vs 1/5, median 4.8 vs 9.2 steps. Same direction, much stronger signal — the tool-surface effect matters more for weaker models, but a stronger model can still complete the task through a worse surface.

Read this honestly. These are small-N runs on live websites the author does not control: site markup changes, rate limits and bot walls move the numbers, and a single run is noisy. Use KOSUM=3 for medians you intend to quote. The benchmark does not claim Selenium beats Playwright in general — it claims that a tool surface designed for small models changes task success rate and token burn.

Token accounting: both sides use LM Studio's usage.prompt_tokens / usage.completion_tokens, summed over every step of a task. The per-call tool output fed back to the model is capped at 2,000 characters on both sides, so neither side is penalised by a harness limit the other does not have.

Reported in this Medium article.

Quick start

One command — checks/installs Ruby, the gem and Chrome, then registers the MCP server:

curl -fsSL https://raw.githubusercontent.com/livdemirci/selenit-mcp/main/install.sh | bash

If Ruby 3.2+ is already present (or you prefer npm only):

npx -y selenit-mcp setup lmstudio --install     # ruby+gem+Chrome check/install + config
npx -y selenit-mcp doctor                       # report ruby/gem/Chrome/config/port

npm only — Ruby sources ship inside the package:

gem install selenium-webdriver        # requirement (not bundled)
npx selenit-mcp                       # stdio MCP server — prints a "ready" line

MCP client config: "command": "npx", "args": ["-y", "selenit-mcp"]. Set SELENIT_RUBY in env to pin a specific Ruby.

From source:

git clone https://github.com/livdemirci/selenit-mcp.git
cd selenit-mcp
gem install selenium-webdriver
ruby bin/selenit-mcp

Requirements: Ruby 3.2+, Google Chrome, and the selenium-webdriver gem. macOS and Linux are tested; Windows is not — see ROADMAP.md.

Client configuration

Claude Desktop / any MCP client (claude_desktop_config.json):

{
  "mcpServers": {
    "selenit-browser": {
      "command": "npx",
      "args": ["-y", "selenit-mcp"],
      "env": { "BROWSER_MCP_PORT": "19419", "BROWSER_MCP_MAX_ELEMENTS": "20" }
    }
  }
}

LM Studio — let the package write the entry itself (it resolves the absolute Ruby path, the package path, and backs up your existing config):

npx -y selenit-mcp setup lmstudio                 # default port 19419
npx -y selenit-mcp setup lmstudio --port 19418 --max-elements 20
npx -y selenit-mcp setup print                    # JSON only, paste it yourself

Then restart LM Studio (or toggle Integrations). Manual equivalent — an absolute Ruby path and cwd are required because a GUI-launched LM Studio may not have rbenv shims on PATH:

{
  "mcpServers": {
    "selenit-browser": {
      "command": "/Users/you/.rbenv/versions/3.4.8/bin/ruby",
      "args": ["/path/to/selenit-mcp/server.rb"],
      "cwd": "/path/to/selenit-mcp",
      "env": { "BROWSER_MCP_PORT": "19419", "BROWSER_MCP_MAX_ELEMENTS": "40" }
    }
  }
}

Two Chrome modes

Managed (isolated)

Existing (your session)

How

Agent launches Chrome with --user-data-dir=artifacts/chrome-profile-<port>

Chrome must already run with --remote-debugging-port

Pro

Never touches your tabs/cookies

Reuses your logged-in session

When

Automation, testing, assistant chat

Continuing work in a portal you are signed into

BROWSER_MODE=managed|existing|auto (default auto: attach if the port is open, otherwise launch).

Port separation

Two MCP servers attaching to the same CDP port fight over the same Chrome — give each its own port.

Consumer

Port

Profile

Your assistant / coding agent

19417

artifacts/chrome-profile-19417

LM Studio plugin

19419

artifacts/chrome-profile-19419

Benchmark / replay

19420

artifacts/chrome-profile-19420

selenit / your own Chrome

19218

your own profile

Warning: port 19218 is the one selenit uses. If you do not want an agent driving your own browser, use a separate port (recommended: 19419). A normally started Chrome cannot be attached to later; remote debugging is mandatory.

Why small models need a different tool surface

Design choice

Problem it solves for small models

Compact dumps (~1–4 KB, max 40 elements)

Gemma E4B has a small context; raw HTML or full a11y trees blow it up

e1-style refs, renewed per snapshot

No CSS selectors to invent; fewer hallucinated locators

Loop guard (same call 3× → warning, not execution)

Stops the classic scroll/read/snapshot death spiral

Modal focus, overlay priority, row/card context

Save buttons in a dialog, Add to cart on product cards stay distinguishable

Explicit waits + actionable errors

"ref is renewed every snapshot", "element is covered", "alert is open"

Auto-recovery from JS alerts, stale elements, covered elements

The run continues instead of collapsing mid-task

Measured on Gemma E4B (google/gemma-4-e4b, 20 tools, max_tokens 800): 17/17 tasks passed, 1–7 steps and 22–78 s per task, ~13 min total. Thinking is off by default (BENCH_THINKING=0; set 1 to enable): the per-step cost dropped ~2.3×, and the real-site Trendyol cart task passed in 13 steps (~439 s); that discovery replays deterministically in ~30 s with no model at all.

How it works

  1. Chrome is launched with --remote-debugging-port=<port> and stays open (detached).

  2. Every later tool call attaches to the existing Chrome via CDP (debugger_address) — no new browser per call.

  3. The page dump marks only visible/interactive elements with data-tw-ref and produces short text.

  4. Session, window, and cookies persist across calls.

Tools (23)

Tool

Description

browser_navigate

Go to a URL, return a compact dump (waits out bot-check interstitials)

browser_snapshot

List interactive elements (e1, e2, …)

browser_dom

Compact DOM read: header (URL/title/modal/alert ~400 chars) or element list via q — lighter than snapshot

browser_click

Click by ref, CSS selector, or visible text (selector + text narrows a repeated list)

browser_find

Find an element by text, return a clickable ref (long pages)

browser_read

Read visible page text (summary/translation)

browser_type

Type into a field (submit: true → Enter)

browser_select

Choose an option from a dropdown (native <select> or custom combobox/menu)

browser_alert

Read/dismiss a JS alert (text reads+closes, peek leaves it open)

browser_scroll

Scroll the page (down/up/top/bottom)

browser_frame

Enter an iframe / return to the main frame

browser_upload

Provide a local path to a file input

browser_hover

Hover an element (menus)

browser_press_key

enter, tab, escape, arrowdown, …

browser_resize

Set the window size (consistent rendering)

browser_monitor

WebDriver BiDi network monitor: requests/responses/console (action: start/check/wait/stop, filter)

browser_wait_for

Explicit wait: text, selector, URL, disappearance, element count, or download (type)

browser_screenshot

Save a PNG, return the path

browser_evaluate

Run JS in the page (result truncated)

browser_history

back / forward / reload

browser_tabs

list / switch / close

browser_status

Connection status

browser_close

Close the session

Safety nets

  • Loop warning: the same tool+arguments arriving a third time is not executed when the result and page state (URL + text hash) are unchanged; legitimate repetition (+ four times, paging) is allowed, and read-only tools (snapshot, dom, read, find, wait_for, status, screenshot, evaluate) are exempt. The response carries the page title/URL plus a directive telling the model what to do instead (stop and answer, stop scrolling, try another element) rather than the old generic "try browser_snapshot" — which, on a browser_snapshot loop, told the model to repeat the very call that was looping. Measured on n11: the old wording let Gemma repeat browser_snapshot 6× for 128 s and never answer; with the directive it stopped and answered correctly in 3 steps.

  • Bot-check interstitials: if navigate lands on a "Just a moment / Bir dakika lütfen / Checking your browser" screen, it waits up to BROWSER_MCP_CHALLENGE_WAIT (default 20 s) for the content to appear and only then returns the dump; otherwise it explains what to do.

  • Hidden duplicates: when a CSS selector matches several nodes, the first visible one is used (menus often contain hidden copies of the same link).

  • Actionable errors: "refs are renewed every snapshot", "element is covered", "alert is open"; a wrong tag on browser_select says this element is not a <select> but a <button>, and an invalid CSS selector returns an example-based hint.

  • Alert hygiene: navigate dismisses an open JS alert; snapshot/read report it.

  • Shadow DOM: the dump walks open shadow roots; ref resolution is done in JS (Selenium CSS search does not cross shadow boundaries).

  • Modal focus: while role=dialog / aria-modal is open, only modal content is listed. Modals that carry no role (e.g. class*="modal|dialog|popup" covering the screen centre) are detected too.

  • Overlay priority: content of position:fixed + high-z-index layers (cookie bars, notices) is listed first so the model can dismiss it.

  • Row/card context: buttons inside table rows are shown as Edit | 10 · Ada Lovelace · London; repeated Add to cart buttons as Add to cart | CEPFİT Boostar … · 57.97 TL. Card detection does not rely on class names alone: the nearest ancestor that shows a price-like string (1.234,56 TL, $19.99, …) and has at most 4 controls is used.

  • Control priority: sort/filter controls (aria-haspopup, role=combobox, text/class containing sort|filtre|filter) are listed before product cards, so a 40-item dump still contains "Sort by".

  • Custom dropdowns: browser_select also handles comboboxes that are not <select> (role=combobox, b-select, dropdown): it opens the widget, matches the option text and clicks it; on a miss it lists the visible options.

  • Controlled inputs: browser_type falls back to the native value setter + input/change events when a React/Vue field swallows keystrokes; number fields are blurred to commit.

  • Explicit waits: click/type/select/iframe/download use Selenium::WebDriver::Wait (visibility, clickability, URL, text, file state). No fixed sleep; implicit wait is 0. Timeouts return a message stating what was expected.

  • Lazy load: if browser_find fails, the page is scrolled, DOM/text growth is awaited, and the search repeats (max BROWSER_MCP_LAZY_SCROLLS, default 8).

  • Downloads: browser_wait_for(type: "download", text: "report.pdf") reads Chrome's downloads-manager shadow root, waits for IN_PROGRESS to finish, and returns the file path.

  • After-click wait: URL changes are polled briefly with explicit waits; if a new tab opened, the correct 0-based index is suggested and a TABS line is added while multiple tabs exist.

  • Dump priority (form → sort/filter → primary action → overlay → card → rest): the element budget is spent on the controls whose absence is fatal, not on DOM order.

    • Form fields (input, textarea, select) come first. Measured on n11.com: a content-dense home page has 485 interactive elements; card-first ordering filled all 40 slots with product cards and the header search box never appeared in the dump at all.

    • Sort/filter controls (aria-haspopup, role=combobox, text/class containing sort|filtre|filter) come next. Measured on Boyner: the 20k-product category page pushed "Önerilen Sıralama" past the 40-item budget, so the model could never sort.

    • Primary action buttons (Sepete Ekle / Add to cart / Buy now / Save / Submit, plus add-to-cart / primary class hints) come second. Measured on an n11 product page: recommendation carousels consumed every slot and the page's own cart button (BUTTON.add-to-cart-btn) was missing; after the fix it is e2.

  • Settle after navigate: document.body existing does not mean the SPA finished rendering, so navigate polls the interactive-element count until two consecutive samples agree (ceiling ~0.9 s). Measured on n11: without it the navigate dump omitted the search box while a snapshot one second later included it — the model only ever sees the first dump.

  • JS-driven submit escalation: submit: true climbs a ladder and stops when the URL changes —

    1. Enter, 2) closest form requestSubmit() or nearest submit-like control clicked via JS (works on <i> icons and overlay-covered nodes where Selenium click refuses), 3) URL scheme: try /arama?q=, /search?q=, /?s=, /search?s= and accept the first that both changes the URL and yields a populated body (≥10 interactive elements, so a soft-404 page of chrome is rejected). Measured on n11.com: no <form>, no button, no suggestion list, and both Selenium send_keys(:enter) and a hand-dispatched native KeyboardEvent were swallowed; only /arama?q=bluetooth+kulaklik produced results (42 product links).

  • Search affordances that are not <input>: some sites open search as an overlay, so no visible <input> exists until you click a text-less icon button. Measured on Trendyol: the control is button[data-testid="suggestion-placeholder"] (aria-label "Open search"); clicking it injects the real <input>. birincil_eylem therefore treats data-testid / aria-label / class matching search|suggestion-placeholder|search-icon as primary even with no text, otherwise the button is ranked behind product cards and never enters the dump. After the fix it is e1: e1 button Ürün, kategori veya marka ara.

  • browser_dom modal flag matches snapshot.js: the DOM summary used to report modal: !!document.querySelector("[class*=modal i],[class*=dialog i],[role=dialog]") — presence only, no visibility or geometry. Measured on Trendyol: a hidden modal-classed node made modal: true while the real candidate was a 474×40 px cookie link that does not cover the screen centre, so the model was told a modal was open and chose the wrong strategy. Both code paths now require: visible (rect > 0, not display:none / visibility:hidden / opacity:0), covers elementFromPoint(centre), and contains ≥1 visible interactive child.

  • Overlay-gated inputs (pointer-events: none): some sites render the search field with pointer-events: none and put a plain DIV on top of it; clicking that DIV unhides the real field. Measured on Hepsiburada: input rect 418×52, display: inline-block, opacity: 1, yet pointerEvents: none and elementFromPoint(centre) returned DIV.initialComponent-…, so Selenium send_keys raised ElementNotInteractableError. Fixed by type_click_ac (click the covering node, then re-resolve the ref — the click rewrites the DOM, so the old element handle goes stale) plus js_yaz (native prototype value setter + input/change events, required by React/Vue-controlled fields). After the overlay opens, a real Enter works and is preferred over URL guessing.

  • browser_read prefers the main content over the page chrome: body.innerText starts with the header/nav, so the max character budget was consumed by menu links before any result appeared. Measured on Hepsiburada: browser_read returned only "Siparişlerim / Kampanyalar / Hepsiburada Premium…" — zero product names — while [class*="productList"] held an 8 509-character real listing. read now tries [class*=productList], [class*=product-list], [class*=searchResult], main, [role=main], #content first and uses the container text when it exceeds 200 chars. After the fix the same call starts with Pensan Büro Mavi Tükenmez Kalem….

  • browser_read appends a DURUM (status) line: result/item count plus remaining scroll distance, so the model does not have to scroll to discover what it already has. Measured on n11: Gemma scrolled 3× (third blocked by the loop guard) before reading the price that was already in the browser_type output — the status line makes that information explicit.

  • URL-scheme fallback must validate the destination, not just the URL: submit_via_url accepted any candidate that changed the URL and had ≥10 interactive elements. Measured on Hepsiburada: /arama?q=kilim is a 404 page with 21 category links, and /?s=kilim renders the home page with 124 product links — both passed the old check. Now each candidate is rejected when hata_sayfasi? matches (title or first 400 chars of body: 404, bulunamadı, sayfamız yok, not found) and unless arama_terimi_sayfada? finds a token of the query in the first 3 000 chars of body text. The working schemes found so far: /arama?q= (n11), /sr?q= (Trendyol), /ara?q= (Hepsiburada — note the single r), /search?q= (GitHub), wiki/ (Wikipedia).

browser_wait_for types

type

Waits for

Fields

text (default)

Text in the page body

text

selector

A CSS selector to become visible

selector

url

A URL fragment

value or text

gone

A selector to disappear

selector

count

At least N visible elements

selector, value

load

document.readyState == complete

—

download

Chrome download state + file

text (file-name fragment)

Explore → freeze → replay

So the LLM does not pay on every run, a successful exploration becomes deterministic:

  1. benchmark.rb saves the steps and a durable CSS selector per element into scenarios/<task>.json (shadow::host||inner for shadow DOM).

  2. replay.rb replays the same steps without a model and applies the same check.

  3. export_rspec.rb converts the scenario into a Selenium + RSpec spec with Page Objects and shared explicit waits (try_for), runnable in CI.

ruby replay.rb filter                 # model-free replay
ruby export_rspec.rb filter           # generates spec/generated/filter_spec.rb
BROWSER_MCP_PORT=19419 rspec spec/generated/filter_spec.rb

All 18 scenarios replay model-free in ~60 s total; the 17 generated specs pass in ~6.7 s.

Policy and audit

BROWSER_MCP_ALLOWED_HOSTS="example.com,portal.company.com"   # empty = all
BROWSER_MCP_REQUIRE_APPROVAL=true                            # require confirmation for risky actions
BROWSER_MCP_ALLOW_DATA=true                                  # allow data: URLs (default true)

With BROWSER_MCP_REQUIRE_APPROVAL=true, buttons labelled delete/pay/send do not run without confirm=true; the model has to ask you first.

Every tool call is appended to artifacts/audit-YYYYMMDD.jsonl (passwords/tokens masked, long text truncated). See SECURITY.md for the threat model.

Configuration

Variable

Default

Description

BROWSER_MCP_PORT

19218

CDP port (19419 recommended)

BROWSER_MODE

auto

managed / existing / auto

BROWSER_MCP_PROFILE_DIR

artifacts/chrome-profile-<port>

Managed profile directory

BROWSER_MCP_MAX_ELEMENTS

40

Max elements per dump (20–40 for Gemma)

BROWSER_MCP_HEADLESS

false

Headless managed browser

BROWSER_MCP_WAIT

10

Default explicit wait timeout (s)

BROWSER_MCP_CHALLENGE_WAIT

20

Max wait for a bot-check interstitial to clear (s; 0 disables)

BROWSER_MCP_LAZY_SCROLLS

8

Lazy-load scroll attempts for browser_find

BROWSER_MCP_DOWNLOAD_DIR

artifacts/downloads-<port>

Managed download directory

BROWSER_MCP_SCREENSHOT_DIR

./screenshots

Screenshot directory

BROWSER_MCP_ARTIFACT_DIR

./artifacts

Audit log directory

BROWSER_MCP_ATTACH_TIMEOUT

13

Attach upper bound; restarts an isolated Chrome on hang

BROWSER_MCP_FAILFAST

1.25

Reject a missing selector this fast when the page is loaded

BROWSER_MCP_TOAST_WAIT

0.6

Post-action toast harvest window (0 disables)

Reproducing the benchmark

# 1) local scenario pages + unit checks (no model needed)
python3 testapp/server.py 8765 &
ruby smoke_test.rb                      # 38/38 checks

# 2) head-to-head against Playwright MCP (needs LM Studio's OpenAI-compatible server on :1234
#    and Node/npx for @playwright/mcp)
KOSUM=1 ruby kiyas.rb                   # 5 live sites, ~20 min
KOSUM=3 SITE_FILTER=wikipedia ruby kiyas.rb
BENCH_MODEL=minicpm5-2b ruby kiyas.rb

kiyas.rb writes bench/kiyas-<model>-<timestamp>.json (every step's trace, the page text used for scoring, and token counts) plus a paste-ready .md summary. Failures are preserved in the JSON — the suite never silently drops a bad run.

benchmark.rb runs the task set against LM Studio without the comparison side:

ruby benchmark.rb                      # all tasks
ruby benchmark.rb deep select          # selected tasks
BENCH_MODEL=google/gemma-4-e4b BENCH_MAX_STEPS=10 ruby benchmark.rb
BENCH_THINKING=0 BENCH_MAX_TOKENS=1600 ruby benchmark.rb sepet

Hard scenarios in testapp/: login, filter, frame, shadow, upload, newtab, dynamic, download.

Measured results (Gemma E4B, 17 local tasks)

Task

Steps

Time

Task

Steps

Time

link

2

28s

login

4

43s

form

3

52s

filter

5

69s

wiki

4

46s

frame

7

76s

hn

1

22s

shadow

4

54s

deep

3

30s

upload

4

64s

read

2

36s

newtab

5

68s

select

2

25s

dynamic

5

78s

alert

3

32s

stale

3

35s

download

3

34s

—

—

—

Real-site task sepet (Trendyol: search → sort by lowest price → add to cart → verify price in the cart): 1/1 passed with Gemma E4B, thinking off, 13 steps (~439 s); replay without a model ~30 s.

Honest comparison with Playwright MCP

Where this server wins

Topic

This server

Playwright MCP

Attaching to an existing browser

CDP attach to your open Chrome

Manages its own browser instance

Small-model (4B) efficiency

40-element ref list, ~4 KB cap, row/card context, modal focus

Large a11y tree, more tokens

Audit / safety

audit-*.jsonl + host allowlist + approval for risky actions

Policies live in the client

Loop protection

Same call 3× → warning, never locks up

None

Discovery → regression

scenarios/*.json → replay.rb → export_rspec.rb

None (needs a model/scenario each run)

Shadow DOM / iframes

Dumps + refs + browser_frame, `shadow::host

Waiting / downloads

Condition-based explicit waits, lazy-load sweep, download state

Strong auto-waiting; no explicit download-completion wait

Network observation

browser_monitor (WebDriver BiDi): requests/responses/console + filter

Richer interception (mocking/aborting)

Where Playwright MCP wins

  • Trace viewer, video/screenshot artifacts; request mocking (BiDi browser_monitor only observes).

  • Multiple browsers (Firefox/WebKit), mature auto-waiting, less discipline needed with large cloud models.

  • Bigger ecosystem and maintenance continuity.

Summary: for general-purpose cloud models with rich artifacts, Playwright MCP is stronger. For small local models + an existing session + auditing + deterministic regression, this server fits better.

Tips for Gemma E4B

  • Ask for one step at a time: browser_navigate → browser_snapshot → browser_click.

  • Prefer ref (e3); give a CSS selector only when necessary.

  • On long pages use browser_find or browser_read; never ask for full HTML.

  • Keep BROWSER_MCP_MAX_ELEMENTS between 20 and 40.

  • Short system prompt:

You are an agent doing browser automation. Call one tool per step.
Use browser_snapshot to understand the page and select elements by refs like e1, e2.
Never request long HTML or full page text; answer briefly.

Contributing

Issues and PRs welcome — see CONTRIBUTING.md. Especially useful right now: benchmark runs on models we have not measured yet (Qwen 3.5 4B/9B, LFM 1.7B, Hermes), Windows install reports, and real-site failure traces with the JSON attached.

License

MIT — see LICENSE.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides 66 tools for browser automation using Selenium WebDriver, enabling AI agents to navigate, interact, capture screenshots, assert conditions, manage cookies, handle tabs, execute HTTP requests, record sessions, and more.
    10 npm
    1
    MIT
  • A
    license
    B
    quality
    A
    maintenance
    Enables AI agents and developers to automate real Chrome/Chromium browsers through the Chrome DevTools Protocol, with token-efficient snapshots, multi-action batching, shadow DOM traversal, system-page control, extension management, and network/WebSocket inspection.
    81
    1
    MIT
  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI agents to browse and interact with web pages through a persistent headless Chromium session, serving compact structured orientations, targeted element queries, and full detail only when explicitly requested. It supports navigation, observation, form filling, monitoring, and session management while cutting token overhead by up to ~140x versus raw accessibility-tree dumps.
    4 npm
    -
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to browse and interact with websites through a persistent headless Chromium session, returning compact structured page representations instead of full accessibility-tree dumps. Agents can navigate, query specific elements and forms, click, type, manage tabs and cookies, and monitor console or network activity while paying a fraction of the tokens.
    4 npm
    -