https://calpano.github.io/smth/
A local MCP server that gives Claude a full browser: screenshots, DOM inspection, WCAG color analysis, and page interaction — all via a persistent Puppeteer/Chromium session running in Docker.
smth exposes 21 MCP tools across four areas:
See — read and analyze any web page without interacting with it
browser_see_visual · browser_read_text · browser_see_dom · browser_see_fonts · browser_see_colors · browser_see_color_pairs
Act — drive the browser like a user
browser_launch · browser_goto · browser_click · browser_hover · browser_type · browser_select_option · browser_press_key
Compare — capture and diff DOM snapshots
browser_remember_dom · browser_doms · browser_dom_compare · fetch_dom_content · browser_list_devices
Check — answer one question about a page in a single call
browser_check_console · browser_check_imprint · browser_check_favicon
- Docker (Desktop or Engine)
- Claude Code (or any MCP client supporting Streamable HTTP or SSE transport)
git clone https://github.com/Calpano/smth.git
cd smth
docker compose up -dThe container starts on port 3000. Add .mcp.json to any project that should use it:
{
"mcpServers": {
"smth": { "type": "http", "url": "http://localhost:3000/mcp" }
}
}Then restart Claude Code. The mcp__smth__* tools will be available immediately.
To let smth open local HTML files from a project directory:
PAGES_DIR=/path/to/your/project docker compose up -d --force-recreateFiles are mounted read-only at /pages. Open them with their host path or file:///pages/filename.html.
browser_launch url="https://example.com"
browser_see_color_pairs
Returns every text/background color pair on the page with WCAG 2.2 contrast ratios and AA/AAA pass/fail flags. Instantly shows which combinations fail accessibility requirements.
fetch_dom_content url="https://example.com/article/123"
Opens the URL, automatically finds a peer page on the same site, computes what's shared (navigation, header, footer, sidebar), and returns only the unique content of the target page. No manual CSS exclusions needed.
browser_launch url="https://example.com"
browser_list_devices
browser_see_visual device="iPhone 15 Pro"
Emulates an iPhone 15 Pro (viewport, pixel ratio, user-agent) and returns an embedded screenshot directly visible to the model.
browser_launch url="https://example.com/login"
browser_see_dom lens=["code"]
browser_type id="username" text="myuser"
browser_type id="password" text="mypass"
browser_click selector="input[type=submit]"
browser_read_text
The code lens shows form fields, names, actions, and event handlers without layout noise. After filling and submitting, browser_read_text reads the resulting page.
browser_launch url="https://example.com/settings"
browser_remember_dom name="before" lens=["text"]
browser_click selector="#enable-toggle"
browser_remember_dom name="after" lens=["text"]
browser_dom_compare a="before" b="after"
Shows exactly which text appeared or disappeared after clicking the toggle — useful for verifying that a UI action had the expected effect.
Full documentation for every tool lives in doc/smth.md.
| Tool | Read-only | Description |
|---|---|---|
browser_list_devices |
yes | Lists device names for browser_see_visual |
browser_launch |
no | Starts a session, opens a URL |
browser_goto |
no | Navigates to a new URL in the existing session |
browser_read_text |
yes | Page text as Markdown + interactive element selectors |
browser_see_fonts |
yes | All fonts grouped by family, size, weight |
browser_see_colors |
yes | All computed colors with usage counts |
browser_see_color_pairs |
yes | Text/background pairs with WCAG contrast ratios |
browser_see_dom |
yes | Lens-filtered compact HTML or text search |
browser_click |
no | Clicks an element by id or CSS selector |
browser_hover |
no | Hovers and returns a before/after diff |
browser_type |
no | Types text into a form field |
browser_select_option |
no | Picks an option from a <select> by value or label |
browser_press_key |
no | Presses a key, with optional modifiers and repeat count |
browser_remember_dom |
no | Saves a named DOM snapshot |
browser_doms |
yes | Lists all saved snapshots |
browser_dom_compare |
yes | Diffs two snapshots or subtracts a background |
fetch_dom_content |
no | Opens a URL and returns only its foreground content |
browser_see_visual |
yes | Screenshot (viewport or full page, any device) |
browser_check_console |
no | Loads a URL and returns the console errors and page exceptions |
browser_check_imprint |
no | Checks a site for a §5 TMG / §18 MStV conformant imprint |
browser_check_favicon |
no | Checks that a site serves at least one valid favicon |
docker compose up -d # start
docker compose down # stop
docker compose up -d --build # restart after code changes
docker compose logs -f # tail logsAfter a --force-recreate, the MCP session token is invalidated. The first tool call will get a Session not found error — just retry once; Claude Code re-establishes the session automatically.
Use the URL you would type into your own browser:
browser_launch url="http://localhost:4000/"
The browser inside the container resolves localhost and 127.0.0.1 to the host, so
the address and the Host header stay as you wrote them. That matters for dev servers
that check the header: vite, webpack-dev-server and Next answer an unexpected Host
with "Blocked request. This host is not allowed", which is what rewriting the URL to
host.docker.internal used to produce. host.docker.internal still works if you prefer
to name the host explicitly.
What each release changed is in CHANGELOG.md.
smth runs entirely on your local machine. It does not transmit any data to external services. The browser session is isolated inside a Docker container. No telemetry, no analytics, no network calls beyond what the pages you visit make themselves.