Skip to main content
Glama
bob-in-browser
by bashkid

bob-in-browser

A browser-inspection MCP server. It gives any MCP client the ability to open a website, look at it, and judge whether it actually renders correctly — not just whether it returns 200.

Most browser automation tools are built for end-to-end testing: assert that a button exists, that a form submits. This one is built for visual and design review. It answers the question "does this page look right?" with screenshots at real breakpoints plus a deterministic audit of the things that most often look wrong.

Viewport 390px — 7 issue(s)

OVERFLOW (1): div.wide runs 1050px past the 390px viewport
SMALL TAP TARGET (2): button.smallbtn "x" is 24x24; a.smallbtn "y" is 6x12
LOW CONTRAST (1): p.faint "This grey text fails WCAG AA contrast" 1.92:1 (needs 4.5:1)
MISSING ALT (1): img logo.png
TINY TEXT (1): p.tiny "This text is only 9 pixels tall" at 9px
CLIPPED (1): div.clip content 410x20 in box 120x20

Every finding names the element and its text, so it is actionable — not a score, not a count.


Contents


Related MCP server: Refract

Why this exists

Anthropic's Claude Code ships a "Claude in Chrome" extension that lets the model drive your real browser. It is genuinely useful for design work, but it cannot be reused by other clients: it is bound by Chrome Native Messaging to a single whitelisted extension ID, talking over stdio to the claude binary specifically. There is no open port and no documented protocol for anything else to connect to.

bob-in-browser is a clean-room equivalent built on Playwright that speaks standard MCP over stdio, so any MCP-capable client can use it. It also keeps the one genuinely distinctive capability of the extension — inspecting pages using your already logged-in browser session — via Chrome DevTools Protocol attach.


Requirements

Node.js

18 or newer (developed and tested on Node 26)

Browser

Chrome/Chromium, Firefox, and WebKit — Playwright installs them

OS

macOS, Linux, or Windows


Install

Clone or unzip, then from inside the folder:

npm install
npx playwright install chromium firefox webkit   # or just the engines you need

Verify before wiring anything up

Two self-contained checks, neither of which needs an MCP client:

npm run smoke          # audits a deliberately broken page; expects 7 findings
./test/mcp-probe.sh    # full JSON-RPC handshake over stdio; exits 0

npm run smoke loads test/broken.html, a fixture with one instance of every defect the audit detects, and prints what it found. If you see 7 issues across 6 categories, the audit engine works.

./test/mcp-probe.sh speaks raw MCP at the server exactly as a client would — initialize, tools/list, tools/call, close_browser — and exits cleanly. If that passes, any MCP client can talk to it.


Connect it to your MCP client

This is a standard stdio MCP server. Add it to your client's MCP configuration:

{
  "mcpServers": {
    "browser": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/bob-in-browser/server.js"],
      "env": {
        "BOB_BROWSER_HEADLESS": "true"
      }
    }
  }
}

Use an absolute path. MCP clients do not expand ~ and do not resolve paths relative to your shell's working directory. This is the single most common setup failure.

If your client uses a different top-level key — some use servers, some use a TOML block — keep the command / args / env triple intact. That part is universal across MCP implementations.


Cross-browser support

All three major engines are supported, and the audit produces identical results on each — verified against the bundled fixture.

engine

Engine

Stands in for

Notes

chromium (default)

Blink

Chrome, Edge, Brave, Opera

Uses your real installed Chrome when available, else bundled Chromium

webkit

WebKit

Safari (macOS + iOS)

Playwright's WebKit build — Safari's engine, not Safari itself

firefox

Gecko

Firefox

Playwright's patched Gecko build, not your installed Firefox

Set the default with BOB_BROWSER_ENGINE, or switch per call:

open_page { url: "https://example.com", engine: "webkit" }
audit_design {}
open_page { url: "https://example.com", engine: "firefox" }
audit_design {}

Switching engines restarts the browser, so console and network buffers reset — which is what you want, since each engine gets a clean log. Screenshots and audit output are labelled with the engine that produced them, so cross-engine comparisons are never ambiguous.

How faithful is each one?

Being precise, because this matters for design review:

  • Chromium is exact for Chrome, because it can drive your actual Chrome install.

  • WebKit is Safari's engine, but is not Safari. Layout, CSS, and font rendering behave like Safari — which is what catches the flexbox and position: sticky differences that bite in practice. It does not carry Safari's UI chrome, its exact version, or Safari-only features. Treat it as a very good proxy, not a substitute for a final check on a real device.

  • Firefox is a patched Gecko build, matched to the Playwright version rather than to the Firefox you have installed.

Engine differences are real and worth catching. During development of this tool, WebKit collapsed a broken <img> to 0x0 where Chromium gave it a 16x16 box — exactly the kind of divergence that makes a layout break in one browser only.

Two browser modes

1. Launch (default)

The server starts its own browser instance. Clean state every run, no interference with your daily browsing. This is what you want for local development and public URLs.

Uses your installed Google Chrome when available for the most accurate rendering, and falls back to Playwright's bundled Chromium otherwise. The mode in use is reported by open_page so you always know what rendered your screenshot.

2. Attach to a running Chrome

Inspect pages behind a login without re-authenticating, by attaching to a Chrome you have already signed into. This is the capability a plain headless browser cannot give you.

Chromium only. The Chrome DevTools Protocol is a Chromium feature; Firefox and WebKit have no equivalent. With BOB_BROWSER_CDP_URL set, requesting another engine returns a clear error rather than silently falling back.

# 1. Quit Chrome completely, then start it with the DevTools port open.
#    The separate --user-data-dir keeps this isolated from your main profile.
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --remote-debugging-port=9222 \
  --user-data-dir="$HOME/.chrome-inspect-profile"

# 2. Point the server at it.
export BOB_BROWSER_CDP_URL=http://localhost:9222

Sign in to the target app once in that window. Every subsequent inspection reuses the session.

Security note: --remote-debugging-port lets any local process drive that browser. Use a dedicated --user-data-dir as shown, close the window when finished, and do not enable it on a shared or untrusted machine.


Configuration

All configuration is environment variables — no config file.

Variable

Default

Purpose

BOB_BROWSER_ENGINE

chromium

Default engine: chromium, firefox, or webkit. Overridable per call via open_page.

BOB_BROWSER_CDP_URL

(unset)

Attach to a running Chrome at this CDP URL instead of launching one. E.g. http://localhost:9222.

BOB_BROWSER_HEADLESS

true

Set to false to watch the browser work in a visible window. Useful when debugging a selector.

BOB_BROWSER_OUT

$TMPDIR/bob-in-browser-shots

Directory for full-resolution PNG screenshots.


Tool reference

Nine tools. open_page first, close_browser last, anything in between.

open_page

Navigate to a URL and begin capturing console output and network failures. Call this first — the capture buffers reset on every call, so each page gets a clean log.

Parameter

Type

Default

Notes

url

string

required

http(s)://, file://, and localhost all work

width

number

1280

Viewport width in px

height

number

900

Viewport height in px

wait_until

enum

networkidle

load, domcontentloaded, or networkidle

engine

enum

chromium

chromium, firefox, or webkit. Switching restarts the browser.

Returns the final URL, page title, HTTP status, active engine, and browser mode.

screenshot

Capture the page as an image. Returns it inline as JPEG (cheap in context) and simultaneously saves a full-resolution PNG to disk, returning the path.

Parameter

Type

Default

Notes

width

number

current

Resize viewport before capturing

full_page

boolean

false

Capture the whole scrollable page

selector

string

—

Capture just one element

format

enum

jpeg

png is sharper; jpeg is far cheaper in tokens

check_responsive

The core tool. Screenshots and audits the page at several widths in one call, so you can see precisely which breakpoint breaks rather than guessing.

Parameter

Type

Default

Notes

widths

number[]

[390, 768, 1280]

Phone, tablet, desktop

full_page

boolean

false

Full scrollable capture at each width

include_images

boolean

true

Set false for findings only — much cheaper

audit_design

Run the automated design and accessibility checks at a single width. See What the audit catches.

Parameter

Type

Default

width

number

current viewport

read_console

Console messages and uncaught page errors captured since open_page. A JS error is the most common reason a page renders wrong.

Parameter

Type

Default

Notes

pattern

string

—

Case-insensitive regex filter on message text

level

enum

—

log, info, warning, error, debug, pageerror

limit

number

50

Most recent N

read_network_errors

Failed requests and HTTP 4xx/5xx responses — catches missing fonts, images, and stylesheets that silently degrade a design without throwing any error.

Parameter

Type

Default

limit

number

50

get_page_text

The rendered text of the page. Use it to check copy, heading hierarchy, and that content actually loaded rather than silently failing.

Parameter

Type

Default

selector

string

whole body

max_chars

number

4000

interact

Drive the page to a state worth inspecting — open a menu, submit a form, scroll to a section. Returns any new console errors the action triggered.

Parameter

Type

Notes

action

enum

click, fill, hover, press, scroll, wait

selector

string

Required for click, fill, hover

value

string

Text for fill, key for press, px for scroll, ms for wait

Avoid clicking controls that open native alert / confirm / prompt dialogs. They block the page and stall further commands.

close_browser

Close the browser and release resources. Takes no parameters.


What the audit catches

Six deterministic checks, each capped at 8 findings per run so output stays readable. All thresholds come from published standards rather than taste.

Check

Rule

Why it matters

Horizontal overflow

Element extends past the viewport edge, excluding position: fixed

The single most common responsive bug. Causes the whole page to scroll sideways on phones.

Small tap targets

Interactive element under 44×44 px

WCAG 2.5.5 and the Apple HIG minimum. Below this, people miss the target.

Low contrast

Below WCAG AA — 4.5:1 body, 3:1 large text

Large text is ≥24px, or ≥18.66px bold. Unreadable for low-vision users and in sunlight.

Missing alt

<img> with no alt attribute at all

alt="" is correctly treated as a valid decorative marker and is not flagged.

Tiny text

Rendered font size under 12px

Below comfortable reading size on any device.

Clipped content

overflow: hidden with content larger than its box

Text silently cut off — often only at certain widths or in certain languages.

On contrast specifically: the check resolves the actual background by walking up the ancestor chain until it finds a colour with alpha > 0.5, so inherited and transparent backgrounds are handled correctly rather than assumed to be white. Interactive elements are matched by role as well as tag ([role="button"], [role="link"], [onclick]), so custom components are covered.

Elements that are display:none, visibility:hidden, opacity:0, or zero-sized are skipped throughout — hidden content is not judged.


Use cases

Catch a responsive break before it ships

open_page        { url: "http://localhost:3000/pricing" }
check_responsive { widths: [360, 390, 768, 1024, 1440] }

One call, five viewports, screenshots plus findings for each. When the pricing table overflows at 768px but not 1024px, you see exactly that — with the offending selector named.

Debug a page that renders wrong

open_page           { url: "https://staging.example.com" }
read_console        { level: "error" }
read_network_errors {}
screenshot          { full_page: true }

Covers the three usual culprits in order: a JS exception that halted rendering, an asset that failed to load, and finally what the page actually looks like.

Accessibility pass before a release

open_page    { url: "https://example.com/checkout", width: 390 }
audit_design {}

Contrast, tap targets, alt text, and text size in one pass, at the viewport where these problems are worst.

Inspect a page behind a login

Start Chrome with --remote-debugging-port=9222, sign in once, set BOB_BROWSER_CDP_URL, then inspect authenticated pages normally. Dashboards, admin panels, and internal tools become reviewable without scripting a login flow or storing credentials.

Review a state that only exists after interaction

open_page  { url: "https://example.com" }
interact   { action: "click", selector: "nav .menu-toggle" }
screenshot { }
audit_design {}

Mobile menus, modals, and expanded accordions are frequently where layout breaks, precisely because they are not visible in a static screenshot.

Check the same page across Chrome, Safari, and Firefox

open_page    { url: "https://example.com", engine: "chromium", width: 390 }
audit_design {}
open_page    { url: "https://example.com", engine: "webkit",   width: 390 }
audit_design {}
open_page    { url: "https://example.com", engine: "firefox",  width: 390 }
audit_design {}

Three engines, same viewport, labelled output. Where the findings diverge, you have found a browser-specific bug — usually flexbox, position: sticky, or a font fallback.

Compare a build against a reference

Point it at a production URL and a local build at the same widths, and compare the saved PNGs. Every capture returns its path, so the files are there to diff by eye or by tool.


How it works

MCP client  ──stdio JSON-RPC──▶  server.js      tool schemas, dispatch, result shaping
                                     │
                                     ▼
                                 browser.js     Playwright lifecycle, console/network capture
                                     │
                                     ▼
                                 checks.js      audit function, executed inside the page
  • server.js declares the nine tools as raw JSON Schema — deliberately not zod — so the server does not impose a validation-library version on the client. Results are shaped into MCP text and image content blocks.

  • browser.js owns the single browser/page instance, decides between launch and CDP attach, and wires the console, pageerror, requestfailed, and response listeners whose buffers back read_console and read_network_errors.

  • checks.js exports one self-contained function serialized into the page by page.evaluate(). It closes over nothing, so it runs correctly in the browser context.

open_page falls back from networkidle to domcontentloaded automatically, so pages that poll continuously do not hang the call — a common failure with naive Playwright wrappers.


Troubleshooting

The client shows no tools. Almost always a path problem. Confirm the server runs standalone with ./test/mcp-probe.sh, then check that args uses an absolute path with no ~.

browserType.launch: Executable doesn't exist. Playwright has no browser installed. Run npx playwright install chromium.

CDP attach fails with ECONNREFUSED. Chrome is not running with the debugging port, or was already running when you launched it with the flag. Quit Chrome completely first — the flag is ignored if an instance is already up.

Screenshots are blank or half-rendered. The page is still loading. Use wait_until: "networkidle", or add interact { action: "wait", value: "1500" } before capturing.

The server seems to hang. It is a long-lived stdio server; staying alive is correct. It exits when stdin closes, provided the browser has been released — call close_browser when done.

Everything is flagged as low contrast. Usually a page that sets colours via a framework stylesheet that failed to load. Check read_network_errors first.


Limitations

  • One page at a time. A single browser and page instance; no parallel tabs or sessions.

  • The audit is deterministic, not aesthetic. It finds measurable defects — overflow, contrast, size. It does not judge whether a design is good. Pair the screenshots with a model's visual judgment for that.

  • No visual regression diffing. Screenshots are saved with paths returned, but comparing them across runs is left to you.

  • No native dialog handling. alert / confirm / prompt will block the page.

  • WebKit is not Safari, and Gecko is not your Firefox. Both are Playwright's builds of the respective engines. Excellent proxies for layout and CSS behaviour; not a replacement for a final pass on real devices, especially for iOS Safari.

  • CDP attach is Chromium-only, so inspecting a logged-in session is not available in WebKit or Firefox.

  • Contrast checking assumes solid backgrounds. Text over an image or gradient is resolved to the nearest solid ancestor colour, which may not reflect what a reader actually sees.


License

MIT

Available Tools

9 tools
audit_designA

Run automated design and accessibility checks on the current page: horizontal overflow, tap targets under 44x44, WCAG AA text contrast, images missing alt, text under 12px, and content clipped by overflow:hidden.

Inputs: width.

check_responsiveA

THE core design check. Screenshots the page at several widths and runs a layout audit at each one, so you can see exactly which breakpoint breaks. Reports horizontal overflow, which is the most common responsive bug.

Inputs: widths, full_page, include_images.

close_browserA

Close the browser and free resources. Call when the inspection is finished.

Inputs: no named parameters.

get_page_textA

Read the rendered text of the page, to check copy, hierarchy, and that content actually loaded.

Inputs: selector, max_chars.

interactA

Drive the page to reach a state worth inspecting: open a menu, submit a form, scroll to a section. Returns any new console errors the action triggered. Avoid elements that open native alert/confirm dialogs.

Inputs: action (required), value, selector.

open_pageA

Open a URL in the browser and start capturing console + network errors. Call this first. Returns page title, HTTP status, and the active engine. Set engine to re-open the same URL in a different browser engine and compare rendering.

Inputs: url (required), width, engine, height, +1 more.

read_consoleA

Read console messages and uncaught page errors captured since the last open_page. JS errors are the usual cause of a page that renders wrong.

Inputs: level, limit, pattern.

read_network_errorsA

List failed requests and HTTP 4xx/5xx responses. Catches missing fonts, images, and stylesheets that silently degrade a design.

Inputs: limit.

screenshotA

Capture the current page as an image so you can visually judge layout, spacing, and alignment. Also saves a full-resolution PNG to disk and returns its path.

Inputs: width, format, selector, full_page.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 9 tools

    Disambiguation4/5

    Most tools have clear, distinct purposes, but check_responsive and audit_design both include horizontal overflow checks, which could lead to minor confusion. Overall descriptions are specific enough to tell them apart.

    Naming Consistency4/5

    Most tool names follow a verb_noun snake_case pattern (check_responsive, read_console, open_page). Two deviations exist: 'screenshot' is a bare noun and 'interact' is a bare verb, but they still fit the general style.

    Tool Count5/5

    9 tools is well within the ideal 3-15 range. Each tool covers a distinct inspection or interaction task, and none feel redundant or unnecessary for the server's browser auditing purpose.

    Completeness4/5

    The tool set covers the core workflow: open, inspect visually, check responsive/design/accessibility, read console/network, extract text, interact, and close. Minor gaps like explicit viewport resizing or element-level queries exist, but they are workaroundable.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI assistants to capture screenshots, run visual diffs, accessibility audits, and batch sweep plans for web pages via MCP tools.
      20 npm
      2
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      MCP server for rendering responsive screenshots of URLs at multiple viewports. Enables agents to capture screenshots and detect visual issues like overflow, clipped elements, and missing alt text.
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Visual frontend accessibility inspector MCP server. WCAG contrast checking, touch target validation, heading hierarchy audits, responsive screenshots, and S+ grading across mobile and desktop viewports.
      21 PyPI
      MIT