bruster
A native webview browser driven by an agent over JSON lines on stdio. No Chromium download. No Node. One process of less than 1 MB.
bruster controls the engine that the operating system already ships:
WKWebView on macOS, WebKitGTK on Linux, and
WebView2 on Windows. The program is a small Rust binary built on
wry. Agents drive it with
one JSON object per line, or with the Model Context Protocol through
--mcp.
Overview
bruster reads commands from stdin and writes its native protocol to stdout. It is one process with no bundled browser and no runtime. It uses both a line-oriented JSON protocol and the Model Context Protocol. The same binary works for a scripted scrape, a test harness, or an agent tool stack.
- Native engine. WKWebView, WebKitGTK 4.1 and WebView2, selected at build time.
- Small. One release binary of about 1 MB. There is no separate browser download.
- Complete. Navigation, DOM snapshots, forms, screenshots, cookies, tabs, network and console logs.
- Portable. Prebuilt for macOS (arm64, x86_64), Linux (x86_64, arm64) and Windows (x86_64).
- Agent-ready.
--mcpexposes every method as a tool. Page events arrive as notifications.
Installation
The install script downloads the build for this OS and CPU into
~/.local/bin:
curl -fsSL https://raw.githubusercontent.com/aliilyas/bruster/main/install.sh | sh
Prebuilt archives for every platform are on the releases page. To build from source, you need a Rust toolchain:
cargo build --release
apt install libwebkit2gtk-4.1-0,
apk add webkit2gtk-4.1, or
dnf install webkit2gtk4.1. If there is no desktop session, bruster
starts again under xvfb-run -a when DISPLAY is not set.Quickstart
Send one JSON object per line to stdin. Read the results and events from stdout:
$ bruster
→ {"id":1,"method":"navigate","params":{"url":"https://example.com"}}
← {"event":"load","tab":1,"url":"https://example.com/"}
← {"id":1,"result":{"url":"https://example.com/"}}
→ {"id":2,"method":"snapshot"}
← {"id":2,"result":{"url":"...","title":"Example Domain","elements":[{"ref":"e1","role":"heading","name":"Example Domain"}]}}
→ {"id":3,"method":"click","params":{"ref":"e1"}}
← {"id":3,"result":null}
Command-line usage
bruster [--mcp] [--hidden|--visible] [--quiet] [options]
--mcp | use the Model Context Protocol on stdio instead of the native protocol. This option also sets --hidden |
|---|---|
--hidden / --visible | move the windows off-screen / show the windows |
--quiet | send stderr to /dev/null. This mutes GL, ALSA, and GTK noise |
--viewport WxH | initial window size, default 1280x800 |
--user-agent UA | replace the user agent |
--profile DIR | keep cookies and storage between runs |
--proxy URL | http://host:port or socks5://host:port |
--init-script FILE | JavaScript that runs in every page before its own scripts |
--insecure | ignore TLS errors (Linux only) |
--no-images | do not load images. Pages load about 30% faster (Linux only) |
Model Context Protocol
With --mcp, every method below becomes a tool. Page events
are delivered as notifications/message. The windows stay hidden
unless you add --visible.
opencode, Claude Desktop, Cursor, Windsurf, and Zed use the same shape:
{ "mcpServers": { "bruster": { "command": "bruster", "args": ["--mcp", "--quiet"] } } }
claude mcp add bruster -- bruster --mcp --quiet
codex mcp add bruster -- bruster --mcp --quiet
VS Code uses a servers key:
{ "servers": { "bruster": { "type": "stdio", "command": "bruster", "args": ["--mcp", "--quiet"] } } }
Native protocol
One JSON object per line, in both directions. A request carries
id, method, and optional params. The
reply carries the same id with a result or an
error. You can address elements with
{"selector": "css"} or with a ref from the last
snapshot. With format: "text", a snapshot renders as one line
per element. This is about 45% smaller than JSON:
https://example.com/
Example Domain
e1 heading "Example Domain"
e2 text "This domain is for use in..."
e3 link "Learn more" href=https://iana.org/help/example-domains
e4 checkbox "Remember me" checked=false
API reference
A selection of the available methods. The full table, with every parameter and result, is in the repository README.
| Method | Purpose |
|---|---|
navigate | load a URL, optionally waiting for load, dom or idle |
snapshot | list controls, headings, links and visible text, with refs |
find | a snapshot filtered by text or regex |
click hover | act on a selector, ref or coordinates |
fill type press | enter text and send keys |
fill_form | fill several fields in one call |
select check | drive selects and checkboxes |
upload | set files on a file input |
screenshot | PNG to a path or as base64, optional full page and clip |
eval | run JavaScript and return a JSON value |
wait_for | wait for a selector, text, URL or network idle |
network console | inspect requests, responses and console output |
cookies set_cookie | read and write cookies |
new_tab tabs switch_tab | manage tabs that share one profile |
close | exit the process |
Events
Unsolicited messages use an event field:
ready, navigating, load,
tab_opened, tab_closed, dialog,
tls_error, load_failed and
download.
Limitations
- Input uses synthetic DOM events (
isTrustedis false). You do not get:hoverstyles, native select popups, or OS shortcuts. - Network and console logs are in the page. They carry no request bodies and they reset on navigation. Use
--proxyfor real interception. - Cross-origin iframes are out of reach. Same-origin iframes and open shadow roots work.
data:andfile:pages load, but in-page calls get no reply. Serve the pages over localhost instead.- bruster does not intercept native file choosers. Use
upload. - Without
--profile, cookies and storage last for one session. - On Windows (WebView2),
screenshothas noclipand the Linux-only flags do nothing.
Performance
Live comparison, runtime footprint and measurement history. Includes Bruster, Playwright, Puppeteer and agent-browser.
Development
bin/bruster runs the development build from a checkout.
tests/run_all.sh [--rounds N] [--no-e2e] runs the full
cycle. The cycle does a build, unit tests, smoke tests, edge tests, a
leftover-process check, the MCP launch check, and the public-site
end-to-end tests. On Linux, run the script under xvfb-run -a.
CI builds and tests every platform. CI also publishes release archives on
a v* tag.
License
bruster is released under the MIT License.