browserr-mcp 0.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ /target/
2
+ engine/target/
3
+ chrome/target/
4
+ Cargo.lock
5
+ *.tmp
6
+ dist/
7
+
8
+ engine*.log
9
+ chrome-boot-test.log
10
+
11
+ *.zip
12
+ *.tar.gz
13
+
14
+ chrome-driver-test.log
15
+
16
+ tools/__pycache__/
17
+ __pycache__/
18
+ mcp-crash.log
19
+ mcp-wrap.log
20
+ tools/mcp-shots/
21
+ tools/mcp-evidence/
22
+
23
+ # icon build artifacts: sources live in scripts/, outputs are regenerable
24
+ scripts/node_modules/
25
+ scripts/package-lock.json
26
+ scripts/icon-*.png
27
+ scripts/extracted-exe-icon.png
28
+ scripts/ci-*.log
29
+
30
+ # terminal test captures and caches
31
+ tests/__pycache__/
32
+ term/__pycache__/
33
+ term/*.log
34
+ term/native-frame.png
35
+
36
+ # one-off smoke probes and binary captures (reusable harnesses are committed)
37
+ .smoke/gh-logs/
38
+ .smoke/*.bin
39
+ .smoke/sixel_probe.sh
40
+ .smoke/tmux_query_test.py
41
+ .smoke/tmux_decision_probe.py
42
+ .smoke/tmux_env_probe.py
43
+ .smoke/tmux_img_probe.sh
44
+ browserr-term-engine.log
45
+ browserr-term-geometry.log
46
+ tools/*.html
47
+ tools/*.bin
48
+ tools/*-shot.png
49
+ tools/filepicker-picked.txt
50
+ tools/mcp-picker.txt
51
+ tools/mcp-engine.log
52
+ tools/net-test.html
53
+ tools/net-api.json
54
+ tools/net-xhr.txt
55
+ tools/frames-test.html
56
+ tools/frames-child.html
57
+ tools/intercept-test.html
58
+ tools/form-test.html
59
+ tools/form-flow.html
60
+ tools/annotate-test.html
61
+ tools/annotate-shot.png
62
+ tools/annotate-preview.png
63
+ tools/handoff-test.html
64
+ tools/handoff-clean.html
65
+ tools/pay-test.html
66
+ tools/sw-render.html
67
+ tools/compat-probe.html
68
+ tools/compat-probe2.html
69
+ tools/probe_mod.js
70
+ tools/probe_run.py
71
+ tools/probe2_run.py
72
+
73
+ tools/trust-evidence/
74
+ tools/mcp-profiles/
75
+ tools/mcp-evidence/
76
+ tools/mcp-shots/
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.5
2
+ Name: browserr-mcp
3
+ Version: 0.7.1
4
+ Summary: MCP server for Browserr, the Servo-based agent browser. One command; the platform engine binary is downloaded and digest-verified on first run.
5
+ Project-URL: Homepage, https://browserr.dev/
6
+ Project-URL: Repository, https://github.com/Omodaka9375/browserr
7
+ Project-URL: Changelog, https://github.com/Omodaka9375/browserr/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/Omodaka9375/browserr/issues
9
+ Author: Omodaka9375
10
+ License-Expression: Apache-2.0
11
+ Keywords: agents,ai,browser,browser-automation,headless,mcp,model-context-protocol,servo
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Rust
19
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+
24
+ # browserr-mcp
25
+
26
+ [MCP](https://modelcontextprotocol.io) server for
27
+ [Browserr](https://browserr.dev) — the Servo-based browser engine built
28
+ for AI agents. One command; the platform engine binary is downloaded and
29
+ sha256-verified on first run.
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "browserr": {
35
+ "command": "uvx",
36
+ "args": ["browserr-mcp"]
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ or, installed directly:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "browserr": { "command": "browserr-mcp" }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## What you get
53
+
54
+ 42 tools covering navigation, token-cheap page reads, structured page
55
+ inventories with stable element refs (`e7`), type-aware form filling,
56
+ annotated screenshots, fail-closed request interception, HTTP-auth and
57
+ file-picker events, evidence capture, and human handoff (pause the
58
+ page, let a person handle the CAPTCHA, resume).
59
+
60
+ The engine is a single Rust binary (Servo 0.8): CPU rendering by
61
+ default, EasyList adblock built in, no Chromium, no CDP, no driver
62
+ downloads. It runs on Windows, Linux (x64/ARM64) and macOS.
63
+
64
+ Browserr fails closed by design: uncertain requests are blocked, not
65
+ guessed; credentials are never logged; every run leaves timestamped
66
+ evidence.
67
+
68
+ ## Environment variables
69
+
70
+ | Variable | Default | Purpose |
71
+ | --- | --- | --- |
72
+ | `BROWSERR_VERSION` | latest release | pin a release tag, e.g. `v0.7.1` |
73
+ | `BROWSERR_HOME` | `~/.browserr/versions/<version>` | install dir; an existing unpack with the engine binary skips the download |
74
+
75
+ ## First run
76
+
77
+ The bootstrap queries the official GitHub release API, downloads the
78
+ platform asset, verifies it against the sha256 digest GitHub reports
79
+ for that asset, and unpacks it under `~/.browserr/versions/<version>`.
80
+ A missing digest is a hard stop — the digest is the corruption guard;
81
+ TLS only covers the transport.
82
+
83
+ Prefer to install ahead of time?
84
+
85
+ ```sh
86
+ uvx browserr-install
87
+ ```
88
+
89
+ ## Links
90
+
91
+ - Browserr: https://browserr.dev/
92
+ - Source: https://github.com/Omodaka9375/browserr
93
+ - Changelog: https://github.com/Omodaka9375/browserr/blob/main/CHANGELOG.md
94
+
95
+ Apache-2.0. Engine and Python tooling are stdlib-only; this bootstrap
96
+ adds no third-party Python dependencies.
@@ -0,0 +1,73 @@
1
+ # browserr-mcp
2
+
3
+ [MCP](https://modelcontextprotocol.io) server for
4
+ [Browserr](https://browserr.dev) — the Servo-based browser engine built
5
+ for AI agents. One command; the platform engine binary is downloaded and
6
+ sha256-verified on first run.
7
+
8
+ ```json
9
+ {
10
+ "mcpServers": {
11
+ "browserr": {
12
+ "command": "uvx",
13
+ "args": ["browserr-mcp"]
14
+ }
15
+ }
16
+ }
17
+ ```
18
+
19
+ or, installed directly:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "browserr": { "command": "browserr-mcp" }
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## What you get
30
+
31
+ 42 tools covering navigation, token-cheap page reads, structured page
32
+ inventories with stable element refs (`e7`), type-aware form filling,
33
+ annotated screenshots, fail-closed request interception, HTTP-auth and
34
+ file-picker events, evidence capture, and human handoff (pause the
35
+ page, let a person handle the CAPTCHA, resume).
36
+
37
+ The engine is a single Rust binary (Servo 0.8): CPU rendering by
38
+ default, EasyList adblock built in, no Chromium, no CDP, no driver
39
+ downloads. It runs on Windows, Linux (x64/ARM64) and macOS.
40
+
41
+ Browserr fails closed by design: uncertain requests are blocked, not
42
+ guessed; credentials are never logged; every run leaves timestamped
43
+ evidence.
44
+
45
+ ## Environment variables
46
+
47
+ | Variable | Default | Purpose |
48
+ | --- | --- | --- |
49
+ | `BROWSERR_VERSION` | latest release | pin a release tag, e.g. `v0.7.1` |
50
+ | `BROWSERR_HOME` | `~/.browserr/versions/<version>` | install dir; an existing unpack with the engine binary skips the download |
51
+
52
+ ## First run
53
+
54
+ The bootstrap queries the official GitHub release API, downloads the
55
+ platform asset, verifies it against the sha256 digest GitHub reports
56
+ for that asset, and unpacks it under `~/.browserr/versions/<version>`.
57
+ A missing digest is a hard stop — the digest is the corruption guard;
58
+ TLS only covers the transport.
59
+
60
+ Prefer to install ahead of time?
61
+
62
+ ```sh
63
+ uvx browserr-install
64
+ ```
65
+
66
+ ## Links
67
+
68
+ - Browserr: https://browserr.dev/
69
+ - Source: https://github.com/Omodaka9375/browserr
70
+ - Changelog: https://github.com/Omodaka9375/browserr/blob/main/CHANGELOG.md
71
+
72
+ Apache-2.0. Engine and Python tooling are stdlib-only; this bootstrap
73
+ adds no third-party Python dependencies.
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "browserr-mcp"
7
+ version = "0.7.1"
8
+ description = "MCP server for Browserr, the Servo-based agent browser. One command; the platform engine binary is downloaded and digest-verified on first run."
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Omodaka9375" }]
13
+ keywords = [
14
+ "mcp",
15
+ "model-context-protocol",
16
+ "browser",
17
+ "browser-automation",
18
+ "servo",
19
+ "ai",
20
+ "agents",
21
+ "headless",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "Operating System :: Microsoft :: Windows",
27
+ "Operating System :: POSIX :: Linux",
28
+ "Operating System :: MacOS",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Programming Language :: Rust",
31
+ "Topic :: Internet :: WWW/HTTP :: Browsers",
32
+ "Topic :: Software Development :: Testing",
33
+ ]
34
+ dependencies = []
35
+
36
+ [project.urls]
37
+ Homepage = "https://browserr.dev/"
38
+ Repository = "https://github.com/Omodaka9375/browserr"
39
+ Changelog = "https://github.com/Omodaka9375/browserr/blob/main/CHANGELOG.md"
40
+ Issues = "https://github.com/Omodaka9375/browserr/issues"
41
+
42
+ [project.scripts]
43
+ browserr-mcp = "browserr_bootstrap:main"
44
+ browserr-install = "browserr_bootstrap:install_main"
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["src/browserr_bootstrap"]
@@ -0,0 +1,209 @@
1
+ """Browserr MCP bootstrap: one-command install of the agent browser.
2
+
3
+ The MCP server itself is stdlib-only Python shipped in the Browserr
4
+ release archives; this package exists so an MCP client can be configured
5
+ with a single command (`browserr-mcp` / `uvx browserr-mcp`) and the
6
+ platform engine binary is fetched and verified automatically on first
7
+ run.
8
+
9
+ How it works (all stdlib):
10
+
11
+ 1. Resolve the install dir:
12
+ - $BROWSERR_HOME if set (explicit override),
13
+ - else ~/.browserr/versions/<version> (per-version, atomic updates).
14
+ 2. If the engine binary is missing there, download the platform asset
15
+ from the official GitHub release, verify its sha256 against the
16
+ digest GitHub reports for the asset, and unpack it in place.
17
+ 3. Exec browserr_mcp.py from the unpack in-process (runpy): it speaks
18
+ newline JSON-RPC on stdio and needs no third-party packages.
19
+
20
+ Fail-closed rules (the whole product is fail-closed; so is this):
21
+ - No digest published for the asset -> refuse to install (the digest is
22
+ the corruption/asset-swap guard; TLS only covers the transport).
23
+ - Non-GitHub download URL -> refuse (a broken API response cannot send
24
+ the download off-site).
25
+ - Unreasonably large asset -> refuse.
26
+
27
+ $BROWSERR_VERSION pins a release tag ("v0.7.1" or "0.7.1"); the default
28
+ is the latest release. $BROWSERR_HOME points at an existing unpack or
29
+ any directory to use as the install root (skips the download when the
30
+ engine binary is already there, e.g. a manually unpacked archive).
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import hashlib
36
+ import json
37
+ import os
38
+ import platform
39
+ import runpy
40
+ import shutil
41
+ import stat
42
+ import sys
43
+ import tempfile
44
+ import urllib.request
45
+ import zipfile
46
+
47
+ __version__ = "0.7.1"
48
+
49
+ REPO = "Omodaka9375/browserr"
50
+ API = "https://api.github.com"
51
+ MAX_ASSET_BYTES = 500 * 1024 * 1024 # sanity ceiling, matches update.ps1/sh
52
+ ALLOWED_HOSTS = {"github.com", "objects.githubusercontent.com"}
53
+
54
+
55
+ def _platform_asset(version: str) -> str:
56
+ machine = platform.machine().lower()
57
+ system = platform.system().lower()
58
+ if system == "windows":
59
+ arch = "x64" if machine in ("amd64", "x86_64") else None
60
+ ext = "zip"
61
+ elif system == "linux":
62
+ arch = {"x86_64": "x64", "amd64": "x64", "aarch64": "arm64"}.get(machine)
63
+ ext = "tar.gz"
64
+ elif system == "darwin":
65
+ arch = "arm64" if machine == "arm64" else "x64"
66
+ ext = "tar.gz"
67
+ else:
68
+ arch = None
69
+ if not arch:
70
+ raise SystemExit(
71
+ f"browserr-mcp: no release asset for {system}/{machine}")
72
+ return f"browserr-{version}-{system}-{arch}.{ext}"
73
+
74
+
75
+ def _get_json(url: str):
76
+ req = urllib.request.Request(url, headers={
77
+ "User-Agent": "browserr-mcp-bootstrap",
78
+ "Accept": "application/vnd.github+json",
79
+ })
80
+ with urllib.request.urlopen(req, timeout=30) as r:
81
+ return json.loads(r.read().decode("utf-8"))
82
+
83
+
84
+ def _resolve_release() -> tuple[str, dict]:
85
+ pin = os.environ.get("BROWSERR_VERSION", "").strip().lstrip("v")
86
+ if pin:
87
+ rel = _get_json(f"{API}/repos/{REPO}/releases/tags/v{pin}")
88
+ else:
89
+ rel = _get_json(f"{API}/repos/{REPO}/releases/latest")
90
+ tag = rel.get("tag_name", "").lstrip("v")
91
+ if not tag:
92
+ raise SystemExit("browserr-mcp: release has no tag_name")
93
+ return tag, rel
94
+
95
+
96
+ def _install_dir(version: str) -> str:
97
+ home = os.environ.get("BROWSERR_HOME")
98
+ if home:
99
+ return os.path.abspath(home)
100
+ base = os.path.join(os.path.expanduser("~"), ".browserr", "versions")
101
+ return os.path.join(base, version)
102
+
103
+
104
+ def _engine_present(d: str) -> bool:
105
+ exe = "engine.exe" if os.name == "nt" else "engine"
106
+ return os.path.isfile(os.path.join(d, exe))
107
+
108
+
109
+ def _unpack(path: str, dest: str) -> None:
110
+ if path.endswith(".zip"):
111
+ with zipfile.ZipFile(path) as z:
112
+ z.extractall(dest)
113
+ for info in z.infolist():
114
+ if info.external_attr >> 28 == 0o3:
115
+ target = os.path.join(dest, info.filename)
116
+ if os.path.exists(target):
117
+ os.chmod(target, os.stat(target).st_mode | stat.S_IXUSR)
118
+ else:
119
+ import tarfile
120
+ with tarfile.open(path) as t:
121
+ t.extractall(dest)
122
+ for m in t.getmembers():
123
+ if m.mode & 0o111 and m.isfile():
124
+ target = os.path.join(dest, m.name)
125
+ if os.path.exists(target):
126
+ os.chmod(target, os.stat(target).st_mode | stat.S_IXUSR)
127
+
128
+
129
+ def ensure_engine() -> str:
130
+ """Return the unpack dir; download+verify the engine if missing."""
131
+ tag, release = _resolve_release()
132
+ dest = _install_dir(tag)
133
+ if _engine_present(dest):
134
+ return dest
135
+
136
+ asset_name = _platform_asset(tag)
137
+ asset = next((a for a in release.get("assets", [])
138
+ if a.get("name") == asset_name), None)
139
+ if not asset:
140
+ raise SystemExit(f"browserr-mcp: release v{tag} has no asset {asset_name}")
141
+
142
+ url = asset.get("browser_download_url", "")
143
+ host = urllib.request.urlparse(url).hostname or ""
144
+ if urllib.request.urlparse(url).scheme != "https" or host not in ALLOWED_HOSTS:
145
+ raise SystemExit(f"browserr-mcp: refusing non-GitHub download URL: {url}")
146
+ if asset.get("size", 0) > MAX_ASSET_BYTES:
147
+ raise SystemExit("browserr-mcp: refusing implausibly large asset")
148
+
149
+ expected = (asset.get("digest") or "").removeprefix("sha256:").lower()
150
+ if not expected:
151
+ raise SystemExit(
152
+ "browserr-mcp: release asset carries no sha256 digest; refusing "
153
+ "to install unverified (the digest is the corruption guard; "
154
+ "TLS only covers the transport)")
155
+
156
+ os.makedirs(dest, exist_ok=True)
157
+ tmp = tempfile.mkdtemp(prefix="browserr-bootstrap-")
158
+ try:
159
+ archive = os.path.join(tmp, asset_name)
160
+ print(f"browserr-mcp: downloading {asset_name} "
161
+ f"({asset.get('size', 0) // (1024*1024)} MB)...", file=sys.stderr)
162
+ req = urllib.request.Request(url, headers={"User-Agent": "browserr-mcp-bootstrap"})
163
+ with urllib.request.urlopen(req, timeout=600) as r, open(archive, "wb") as f:
164
+ shutil.copyfileobj(r, f)
165
+
166
+ h = hashlib.sha256()
167
+ with open(archive, "rb") as f:
168
+ for chunk in iter(lambda: f.read(1024 * 1024), b""):
169
+ h.update(chunk)
170
+ actual = h.hexdigest()
171
+ if actual != expected:
172
+ raise SystemExit(f"browserr-mcp: sha256 mismatch: expected {expected}, got {actual}")
173
+
174
+ # unpack to a staging dir, then move into place (all-or-nothing)
175
+ stage = os.path.join(tmp, "unpacked")
176
+ os.makedirs(stage)
177
+ _unpack(archive, stage)
178
+ if not _engine_present(stage):
179
+ raise SystemExit("browserr-mcp: archive has no engine binary; not a browserr release?")
180
+ for entry in os.listdir(stage):
181
+ shutil.move(os.path.join(stage, entry), os.path.join(dest, entry))
182
+ return dest
183
+ finally:
184
+ shutil.rmtree(tmp, ignore_errors=True)
185
+
186
+
187
+ def main() -> None:
188
+ """MCP server entry point: ensure the engine, then run browserr_mcp."""
189
+ dest = ensure_engine()
190
+ mcp = os.path.join(dest, "browserr_mcp.py")
191
+ if not os.path.isfile(mcp):
192
+ raise SystemExit(f"browserr-mcp: browserr_mcp.py missing in {dest}")
193
+ os.environ["BROWSERR_ENGINE"] = os.path.join(
194
+ dest, "engine.exe" if os.name == "nt" else "engine")
195
+ sys.argv = [mcp] + sys.argv[1:]
196
+ sys.path.insert(0, dest)
197
+ runpy.run_path(mcp, run_name="__main__")
198
+
199
+
200
+ def install_main() -> None:
201
+ """Standalone installer entry point: download + verify, then exit."""
202
+ tag, _ = _resolve_release()
203
+ dest = _install_dir(tag)
204
+ if _engine_present(dest):
205
+ print(f"browserr-install: v{tag} already present at {dest}")
206
+ return
207
+ ensure_engine()
208
+ print(f"browserr-install: v{tag} installed at {dest}")
209
+ print('MCP config: {"command": "browserr-mcp"}')