bob-in-browser
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@bob-in-browser Open my site https://example.com and audit design at mobile, tablet, and desktop widths."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 120x20Every 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 needVerify 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 0npm 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 | Stands in for | Notes |
| Blink | Chrome, Edge, Brave, Opera | Uses your real installed Chrome when available, else bundled Chromium |
| WebKit | Safari (macOS + iOS) | Playwright's WebKit build — Safari's engine, not Safari itself |
| 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: stickydifferences 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_URLset, 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:9222Sign in to the target app once in that window. Every subsequent inspection reuses the session.
Security note:
--remote-debugging-portlets any local process drive that browser. Use a dedicated--user-data-diras 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 |
|
| Default engine: |
| (unset) | Attach to a running Chrome at this CDP URL instead of launching one. E.g. |
|
| Set to |
|
| 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 |
| string | required |
|
| number |
| Viewport width in px |
| number |
| Viewport height in px |
| enum |
|
|
| enum |
|
|
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 |
| number | current | Resize viewport before capturing |
| boolean |
| Capture the whole scrollable page |
| string | — | Capture just one element |
| enum |
|
|
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 |
| number[] |
| Phone, tablet, desktop |
| boolean |
| Full scrollable capture at each width |
| boolean |
| Set |
audit_design
Run the automated design and accessibility checks at a single width. See What the audit catches.
Parameter | Type | Default |
| 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 |
| string | — | Case-insensitive regex filter on message text |
| enum | — |
|
| number |
| 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 |
| number |
|
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 |
| string | whole body |
| number |
|
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 |
| enum |
|
| string | Required for |
| string | Text for |
Avoid clicking controls that open native
alert/confirm/promptdialogs. 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 | 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 |
|
|
Tiny text | Rendered font size under 12px | Below comfortable reading size on any device. |
Clipped content |
| 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 pageserver.jsdeclares 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 MCPtextandimagecontent blocks.browser.jsowns the single browser/page instance, decides between launch and CDP attach, and wires theconsole,pageerror,requestfailed, andresponselisteners whose buffers backread_consoleandread_network_errors.checks.jsexports one self-contained function serialized into the page bypage.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/promptwill 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 toolsaudit_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
Scored across 9 tools
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.
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.
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.
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
Visual regression testing over MCP: upload screenshots, review diffs, approve. No CI needed.
Review frontend code and live pages against 386 quality-gated web development rules.
Screenshot, diff, audit and sitemap-capture any web page — 5 MCP tools for AI agents.
Website QA for your coding agent: audit SEO, performance, security, accessibility over MCP.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to capture screenshots, run visual diffs, accessibility audits, and batch sweep plans for web pages via MCP tools.20 npm2MIT
- AlicenseNot gradedqualityCmaintenanceMCP 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
- AlicenseNot gradedqualityCmaintenanceVisual 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 PyPIMIT
- AlicenseNot gradedqualityCmaintenanceEnables visual review of frontend UI variants by providing an MCP tool that opens, refreshes, verifies, captures, and closes review surfaces.MIT