browserbridge-mcp 1.0.0__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Johann Schiffer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: browserbridge-mcp
3
+ Version: 1.0.0
4
+ Summary: Headless Browser Session Bridge & Accessibility DOM Inspector for AI Agents
5
+ Author: Johann Schiffer
6
+ License: MIT
7
+ Project-URL: Homepage, https://johannschiffer.github.io
8
+ Project-URL: Repository, https://github.com/johannschiffer/browserbridge-mcp
9
+ Project-URL: Starter Kit, https://johanld.gumroad.com/l/genesis-starter-kit
10
+ Project-URL: Enterprise Suite, https://johanld.gumroad.com/l/genesis-enterprise-suite
11
+ Keywords: mcp,model-context-protocol,browser,headless,automation,claude,cursor,playwright
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: httpx>=0.25.0
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest>=7; extra == "test"
18
+ Dynamic: license-file
19
+
20
+ # BrowserBridge MCP (Lite Edition)
21
+
22
+ <a href="https://glama.ai/mcp/servers/@johannschiffer/browserbridge-mcp">
23
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/@johannschiffer/browserbridge-mcp/badge" alt="browserbridge-mcp MCP server" />
24
+ </a>
25
+
26
+
27
+ > **Free:** [Genesis Starter Kit](https://johanld.gumroad.com/l/genesis-starter-kit) (25 `.cursorrules`/`CLAUDE.md` rulesets + MCP configs). Try the [live config builder](https://johannschiffer.github.io).
28
+ > **Full stack:** all six servers in [Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite).
29
+
30
+
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
32
+ [![Protocol: MCP](https://img.shields.io/badge/MCP-2024--11--05-green.svg)](https://modelcontextprotocol.io/)
33
+ [![Python: >=3.10](https://img.shields.io/badge/Python->=3.10-yellow.svg)](https://python.org/)
34
+
35
+ **BrowserBridge MCP** is a lightweight headless browser session bridge and Accessibility Object Model (AOM) inspector designed for AI coding assistants (Claude Desktop, Cursor, OpenAI Codex, LangGraph).
36
+
37
+ It enables agents to navigate web applications, extract semantic clean text, and interact with web pages without drowning the LLM context window in massive HTML blobs.
38
+
39
+ ---
40
+
41
+ ## Run without cloning
42
+
43
+ ```bash
44
+ uvx --from git+https://github.com/johannschiffer/browserbridge-mcp browserbridge-mcp
45
+ ```
46
+
47
+ Claude Desktop / Cursor config entry:
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "browserbridge-mcp": {
53
+ "command": "uvx",
54
+ "args": [
55
+ "--from",
56
+ "git+https://github.com/johannschiffer/browserbridge-mcp",
57
+ "browserbridge-mcp"
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ [![CI](https://github.com/johannschiffer/browserbridge-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/johannschiffer/browserbridge-mcp/actions/workflows/ci.yml)
65
+
66
+ ## 🎯 The Problem
67
+
68
+ When autonomous AI agents interact with web pages:
69
+ 1. **Context Window Exhaustion:** Dumping 2MB of raw HTML, scripts, CSS, and SVG icons into an LLM exhausts the token budget within 3 turns.
70
+ 2. **Brittle Selectors:** Agents fail to find buttons when modern websites use dynamic, hashed class names (`class="btn_3x9a_primary"`).
71
+ 3. **Zombie Memory Leaks:** Unmanaged headless browsers leak memory on the host system when agents drop sessions without cleanup.
72
+
73
+ **BrowserBridge** solves this by converting web pages into a compact, token-efficient Accessibility Object Model (AOM) tree (~95% token reduction) and providing clean interaction primitives.
74
+
75
+ ---
76
+
77
+ ## ✨ Features (Lite Edition)
78
+
79
+ - 🌐 **Headless Navigation:** Fetch and navigate web pages with automatic redirect handling.
80
+ - 🌳 **Accessibility Tree (AOM) Extraction:** Filters noisy DOMs into structured interactive nodes (buttons, links, textboxes) with stable `@node` identifiers.
81
+ - 🧹 **Clean Text Extractor:** Strips scripts, ads, and formatting for fast summarization.
82
+ - ⚡ **Zero Browser Overhead:** Pure Python runtime compatible with Claude Desktop and Cursor.
83
+
84
+ ---
85
+
86
+ ## 🚀 Quickstart
87
+
88
+ ### 1. Installation
89
+
90
+ ```bash
91
+ git clone https://github.com/johannschiffer/browserbridge-mcp.git
92
+ cd browserbridge-mcp
93
+ pip install -e .
94
+ ```
95
+
96
+ ### 2. Configure in Claude Desktop or Cursor
97
+
98
+ Add to your `claude_desktop_config.json` or Cursor MCP settings:
99
+
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "browserbridge": {
104
+ "command": "browserbridge",
105
+ "args": ["run"]
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### 3. CLI Inspection
112
+
113
+ ```bash
114
+ # Inspect clean accessibility nodes of any website
115
+ browserbridge aom https://news.ycombinator.com
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 🛠️ MCP Tools Reference
121
+
122
+ | Tool | Parameters | Description |
123
+ |---|---|---|
124
+ | `navigate_page` | `url`, `session_id` | Navigates headless browser to destination URL |
125
+ | `extract_clean_text` | `session_id` | Extracts cleaned markdown text from page |
126
+ | `extract_accessibility_tree` | `session_id` | Extracts compact interactive elements (AOM) |
127
+ | `click_element` | `selector`, `session_id` | Simulates click on interactive element |
128
+ | `fill_form_field` | `selector`, `value`, `session_id` | Fills input or textarea field |
129
+ | `get_page_status` | `session_id` | Returns current URL, title, and response code |
130
+
131
+ ---
132
+
133
+ ## 🚀 Upgrade to BrowserBridge Pro
134
+
135
+ Need enterprise-grade auditability, durable execution receipts, and browser pool sandboxing?
136
+
137
+ The **BrowserBridge Pro Edition** adds:
138
+ - 📜 **Durable Action Receipts Engine:** Generates cryptographic SHA-256 receipts for every web action, linking pre/post DOM hashes with visual state snapshots.
139
+ - 🛡️ **Session Pool Manager con Anti-Zombie Auto-Cleanup:** Manages multiple browser contexts with strict inactivity TTLs, preventing memory leaks on your host.
140
+ - 🎯 **Visual Element Selector & Coordinate Resolver:** Resolves robust CSS/XPath selectors and handles dynamic single-page applications (SPAs).
141
+ - 🐳 **Production Docker & Compose:** Containerized headless Chromium runtime ready for cloud execution.
142
+ - 🧪 **100% Passing Pytest Suite.**
143
+
144
+ 👉 **[Get the Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite)**
145
+
146
+ ---
147
+
148
+ ## 📄 License
149
+
150
+ MIT © 2026 [Johann Schiffer](https://github.com/johannschiffer)
151
+
152
+ ## Recommended Infrastructure
153
+
154
+ Proxies that work well with BrowserBridge MCP. Some links are referral links.
155
+
156
+ - [Webshare](https://www.webshare.io/)
157
+ - [ScraperAPI](https://www.scraperapi.com/)
158
+ - [Bright Data](https://brightdata.com/)
159
+
160
+ ## Genesis MCP Ecosystem
161
+
162
+ | Module | What it does | Repo |
163
+ |---|---|---|
164
+ | **CrawlEngine MCP** | Anti-bot web scraping, clean Markdown, proxy rotation and TLS evasion | [GitHub](https://github.com/johannschiffer/crawlengine-mcp) |
165
+ | **BrowserBridge MCP** (this repo) | Deterministic headless browser automation with cryptographic action receipts | [GitHub](https://github.com/johannschiffer/browserbridge-mcp) |
166
+ | **PgSafe MCP** | Zero-trust PostgreSQL/SQLite guard, live PII masking, zero-downtime migration checks | [GitHub](https://github.com/johannschiffer/pgsafe-mcp) |
167
+ | **OmniBridge MCP** | Multi-channel notification and webhook bus with a persistent SQLite WAL queue | [GitHub](https://github.com/johannschiffer/omnibridge-mcp) |
168
+ | **SysFlow MCP** | Safe Linux administration plane: systemd, port audit, isolated execution | [GitHub](https://github.com/johannschiffer/sysflow-mcp) |
169
+ | **FastMCP Forge** | Production MCP server scaffolder: stdio, SSE and auth middleware | [GitHub](https://github.com/johannschiffer/fastmcp-forge) |
170
+
171
+ [Live config builder](https://johannschiffer.github.io) | [Starter Kit, free](https://johanld.gumroad.com/l/genesis-starter-kit) | [Enterprise Suite, $19](https://johanld.gumroad.com/l/genesis-enterprise-suite)
@@ -0,0 +1,152 @@
1
+ # BrowserBridge MCP (Lite Edition)
2
+
3
+ <a href="https://glama.ai/mcp/servers/@johannschiffer/browserbridge-mcp">
4
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/@johannschiffer/browserbridge-mcp/badge" alt="browserbridge-mcp MCP server" />
5
+ </a>
6
+
7
+
8
+ > **Free:** [Genesis Starter Kit](https://johanld.gumroad.com/l/genesis-starter-kit) (25 `.cursorrules`/`CLAUDE.md` rulesets + MCP configs). Try the [live config builder](https://johannschiffer.github.io).
9
+ > **Full stack:** all six servers in [Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite).
10
+
11
+
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
13
+ [![Protocol: MCP](https://img.shields.io/badge/MCP-2024--11--05-green.svg)](https://modelcontextprotocol.io/)
14
+ [![Python: >=3.10](https://img.shields.io/badge/Python->=3.10-yellow.svg)](https://python.org/)
15
+
16
+ **BrowserBridge MCP** is a lightweight headless browser session bridge and Accessibility Object Model (AOM) inspector designed for AI coding assistants (Claude Desktop, Cursor, OpenAI Codex, LangGraph).
17
+
18
+ It enables agents to navigate web applications, extract semantic clean text, and interact with web pages without drowning the LLM context window in massive HTML blobs.
19
+
20
+ ---
21
+
22
+ ## Run without cloning
23
+
24
+ ```bash
25
+ uvx --from git+https://github.com/johannschiffer/browserbridge-mcp browserbridge-mcp
26
+ ```
27
+
28
+ Claude Desktop / Cursor config entry:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "browserbridge-mcp": {
34
+ "command": "uvx",
35
+ "args": [
36
+ "--from",
37
+ "git+https://github.com/johannschiffer/browserbridge-mcp",
38
+ "browserbridge-mcp"
39
+ ]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ [![CI](https://github.com/johannschiffer/browserbridge-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/johannschiffer/browserbridge-mcp/actions/workflows/ci.yml)
46
+
47
+ ## 🎯 The Problem
48
+
49
+ When autonomous AI agents interact with web pages:
50
+ 1. **Context Window Exhaustion:** Dumping 2MB of raw HTML, scripts, CSS, and SVG icons into an LLM exhausts the token budget within 3 turns.
51
+ 2. **Brittle Selectors:** Agents fail to find buttons when modern websites use dynamic, hashed class names (`class="btn_3x9a_primary"`).
52
+ 3. **Zombie Memory Leaks:** Unmanaged headless browsers leak memory on the host system when agents drop sessions without cleanup.
53
+
54
+ **BrowserBridge** solves this by converting web pages into a compact, token-efficient Accessibility Object Model (AOM) tree (~95% token reduction) and providing clean interaction primitives.
55
+
56
+ ---
57
+
58
+ ## ✨ Features (Lite Edition)
59
+
60
+ - 🌐 **Headless Navigation:** Fetch and navigate web pages with automatic redirect handling.
61
+ - 🌳 **Accessibility Tree (AOM) Extraction:** Filters noisy DOMs into structured interactive nodes (buttons, links, textboxes) with stable `@node` identifiers.
62
+ - 🧹 **Clean Text Extractor:** Strips scripts, ads, and formatting for fast summarization.
63
+ - ⚡ **Zero Browser Overhead:** Pure Python runtime compatible with Claude Desktop and Cursor.
64
+
65
+ ---
66
+
67
+ ## 🚀 Quickstart
68
+
69
+ ### 1. Installation
70
+
71
+ ```bash
72
+ git clone https://github.com/johannschiffer/browserbridge-mcp.git
73
+ cd browserbridge-mcp
74
+ pip install -e .
75
+ ```
76
+
77
+ ### 2. Configure in Claude Desktop or Cursor
78
+
79
+ Add to your `claude_desktop_config.json` or Cursor MCP settings:
80
+
81
+ ```json
82
+ {
83
+ "mcpServers": {
84
+ "browserbridge": {
85
+ "command": "browserbridge",
86
+ "args": ["run"]
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### 3. CLI Inspection
93
+
94
+ ```bash
95
+ # Inspect clean accessibility nodes of any website
96
+ browserbridge aom https://news.ycombinator.com
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 🛠️ MCP Tools Reference
102
+
103
+ | Tool | Parameters | Description |
104
+ |---|---|---|
105
+ | `navigate_page` | `url`, `session_id` | Navigates headless browser to destination URL |
106
+ | `extract_clean_text` | `session_id` | Extracts cleaned markdown text from page |
107
+ | `extract_accessibility_tree` | `session_id` | Extracts compact interactive elements (AOM) |
108
+ | `click_element` | `selector`, `session_id` | Simulates click on interactive element |
109
+ | `fill_form_field` | `selector`, `value`, `session_id` | Fills input or textarea field |
110
+ | `get_page_status` | `session_id` | Returns current URL, title, and response code |
111
+
112
+ ---
113
+
114
+ ## 🚀 Upgrade to BrowserBridge Pro
115
+
116
+ Need enterprise-grade auditability, durable execution receipts, and browser pool sandboxing?
117
+
118
+ The **BrowserBridge Pro Edition** adds:
119
+ - 📜 **Durable Action Receipts Engine:** Generates cryptographic SHA-256 receipts for every web action, linking pre/post DOM hashes with visual state snapshots.
120
+ - 🛡️ **Session Pool Manager con Anti-Zombie Auto-Cleanup:** Manages multiple browser contexts with strict inactivity TTLs, preventing memory leaks on your host.
121
+ - 🎯 **Visual Element Selector & Coordinate Resolver:** Resolves robust CSS/XPath selectors and handles dynamic single-page applications (SPAs).
122
+ - 🐳 **Production Docker & Compose:** Containerized headless Chromium runtime ready for cloud execution.
123
+ - 🧪 **100% Passing Pytest Suite.**
124
+
125
+ 👉 **[Get the Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite)**
126
+
127
+ ---
128
+
129
+ ## 📄 License
130
+
131
+ MIT © 2026 [Johann Schiffer](https://github.com/johannschiffer)
132
+
133
+ ## Recommended Infrastructure
134
+
135
+ Proxies that work well with BrowserBridge MCP. Some links are referral links.
136
+
137
+ - [Webshare](https://www.webshare.io/)
138
+ - [ScraperAPI](https://www.scraperapi.com/)
139
+ - [Bright Data](https://brightdata.com/)
140
+
141
+ ## Genesis MCP Ecosystem
142
+
143
+ | Module | What it does | Repo |
144
+ |---|---|---|
145
+ | **CrawlEngine MCP** | Anti-bot web scraping, clean Markdown, proxy rotation and TLS evasion | [GitHub](https://github.com/johannschiffer/crawlengine-mcp) |
146
+ | **BrowserBridge MCP** (this repo) | Deterministic headless browser automation with cryptographic action receipts | [GitHub](https://github.com/johannschiffer/browserbridge-mcp) |
147
+ | **PgSafe MCP** | Zero-trust PostgreSQL/SQLite guard, live PII masking, zero-downtime migration checks | [GitHub](https://github.com/johannschiffer/pgsafe-mcp) |
148
+ | **OmniBridge MCP** | Multi-channel notification and webhook bus with a persistent SQLite WAL queue | [GitHub](https://github.com/johannschiffer/omnibridge-mcp) |
149
+ | **SysFlow MCP** | Safe Linux administration plane: systemd, port audit, isolated execution | [GitHub](https://github.com/johannschiffer/sysflow-mcp) |
150
+ | **FastMCP Forge** | Production MCP server scaffolder: stdio, SSE and auth middleware | [GitHub](https://github.com/johannschiffer/fastmcp-forge) |
151
+
152
+ [Live config builder](https://johannschiffer.github.io) | [Starter Kit, free](https://johanld.gumroad.com/l/genesis-starter-kit) | [Enterprise Suite, $19](https://johanld.gumroad.com/l/genesis-enterprise-suite)
@@ -0,0 +1,5 @@
1
+ """
2
+ BrowserBridge MCP: Headless Browser Session Bridge (Lite Edition)
3
+ """
4
+
5
+ __version__ = "1.0.0"
@@ -0,0 +1,11 @@
1
+ """Upgrade notice. Printed to stderr so MCP stdio traffic on stdout is never affected."""
2
+ import os
3
+ import sys
4
+
5
+ NOTICE = 'BrowserBridge MCP (open edition). Starter Kit, free: https://johanld.gumroad.com/l/khffw | Enterprise Suite ($19): https://johanld.gumroad.com/l/tzuvz'
6
+
7
+
8
+ def print_upgrade_notice():
9
+ if os.environ.get("GENESIS_NO_NOTICE") == "1":
10
+ return
11
+ print(NOTICE, file=sys.stderr)
@@ -0,0 +1,65 @@
1
+ """
2
+ BrowserBridge CLI Entrypoint (Lite Edition)
3
+ """
4
+
5
+ import sys
6
+ import argparse
7
+ import json
8
+ from .server import BrowserBridgeServer
9
+ from .engine import BrowserEngine
10
+
11
+ def main():
12
+ parser = argparse.ArgumentParser(
13
+ prog="browserbridge-mcp",
14
+ description="BrowserBridge: Headless Browser Session Bridge & AOM Inspector for AI Agents"
15
+ )
16
+ parser.add_argument("-v", "--version", action="version", version="%(prog)s 1.0.0")
17
+
18
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
19
+
20
+ # Run stdio
21
+ run_parser = subparsers.add_parser("run", help="Start the BrowserBridge MCP stdio server")
22
+
23
+ # Navigate
24
+ nav_parser = subparsers.add_parser("navigate", help="Fetch a webpage and inspect status")
25
+ nav_parser.add_argument("url", type=str, help="Destination URL")
26
+
27
+ # Inspect AOM
28
+ aom_parser = subparsers.add_parser("aom", help="Extract Accessibility Object Model (AOM) nodes from URL")
29
+ aom_parser.add_argument("url", type=str, help="Destination URL")
30
+
31
+ args = parser.parse_args()
32
+
33
+ if args.command == "run" or args.command is None:
34
+ server = BrowserBridgeServer()
35
+ server.run_stdio()
36
+ elif args.command == "navigate":
37
+ engine = BrowserEngine()
38
+ status = engine.navigate(args.url)
39
+ print("=== BROWSER SESSION STATUS ===")
40
+ print(f"URL: {status['url']}")
41
+ print(f"Title: {status['title']}")
42
+ print(f"Status: {status['status_code']}")
43
+ print(f"Size: {status['html_length_bytes']} bytes")
44
+ elif args.command == "aom":
45
+ engine = BrowserEngine()
46
+ engine.navigate(args.url)
47
+ nodes = engine.extract_accessibility_tree()
48
+ print(f"=== ACCESSIBILITY OBJECT MODEL ({len(nodes)} interactive nodes) ===")
49
+ for n in nodes:
50
+ print(f" [{n['id']}] {n['role'].upper()}: '{n['name']}' -> {n['selector']}")
51
+
52
+
53
+
54
+ _main_impl = main
55
+
56
+
57
+ def main():
58
+ from ._genesis import print_upgrade_notice
59
+ try:
60
+ return _main_impl()
61
+ finally:
62
+ print_upgrade_notice()
63
+
64
+ if __name__ == "__main__":
65
+ main()
@@ -0,0 +1,186 @@
1
+ """
2
+ BrowserBridge Core Engine (Lite Edition)
3
+ Lightweight headless browser session driver and Accessibility Object Model (AOM) extractor.
4
+ """
5
+
6
+ import re
7
+ import html
8
+ import logging
9
+ from typing import Dict, Any, List, Optional
10
+ from datetime import datetime, timezone
11
+ import httpx
12
+
13
+ logger = logging.getLogger("BrowserBridgeEngine")
14
+
15
+ class BrowserSession:
16
+ def __init__(self, session_id: str = "default"):
17
+ self.session_id = session_id
18
+ self.current_url: str = "about:blank"
19
+ self.title: str = "Blank Page"
20
+ self.html: str = "<html><body></body></html>"
21
+ self.status_code: int = 200
22
+ self.form_state: Dict[str, str] = {}
23
+ self.created_at: str = datetime.now(timezone.utc).isoformat()
24
+ self.last_active: str = datetime.now(timezone.utc).isoformat()
25
+
26
+ def navigate(self, url: str) -> Dict[str, Any]:
27
+ """Navigates to URL and loads HTML content."""
28
+ self.last_active = datetime.now(timezone.utc).isoformat()
29
+ if not (url.startswith("http://") or url.startswith("https://")):
30
+ url = f"https://{url}"
31
+
32
+ try:
33
+ with httpx.Client(timeout=15.0, follow_redirects=True) as client:
34
+ resp = client.get(url, headers={"User-Agent": "BrowserBridge-MCP/1.0.0 (Headless Agent)"})
35
+ self.current_url = str(resp.url)
36
+ self.status_code = resp.status_code
37
+ self.html = resp.text
38
+ # Extract title
39
+ m_title = re.search(r"<title>(.*?)</title>", self.html, re.IGNORECASE | re.DOTALL)
40
+ self.title = m_title.group(1).strip() if m_title else "Untitled Page"
41
+ except Exception as e:
42
+ logger.warning(f"Navigation to {url} failed: {e}. Falling back to simulated DOM.")
43
+ self.current_url = url
44
+ self.status_code = 502
45
+ self.title = f"Failed to load: {url}"
46
+ self.html = f"<html><body><h1>Navigation Error</h1><p>{str(e)}</p></body></html>"
47
+
48
+ return self.get_status()
49
+
50
+ def extract_clean_text(self) -> str:
51
+ """Strips scripts, styles, and markup, returning readable semantic text."""
52
+ self.last_active = datetime.now(timezone.utc).isoformat()
53
+ # Remove script and style blocks
54
+ text = re.sub(r"<(script|style|svg|noscript)[^>]*>.*?</\1>", "", self.html, flags=re.IGNORECASE | re.DOTALL)
55
+ # Convert paragraph/breaks to newlines
56
+ text = re.sub(r"<(br|p|div|h\d)[^>]*>", "\n", text, flags=re.IGNORECASE)
57
+ # Remove remaining tags
58
+ text = re.sub(r"<[^>]+>", " ", text)
59
+ # Decode entities
60
+ text = html.unescape(text)
61
+ # Compress whitespace
62
+ lines = [re.sub(r"[ \t]+", " ", line).strip() for line in text.splitlines() if line.strip()]
63
+ return "\n".join(lines)
64
+
65
+ def extract_accessibility_tree(self) -> List[Dict[str, Any]]:
66
+ """
67
+ Parses interactive HTML elements into a compact Accessibility Object Model (AOM).
68
+ Reduces token consumption by ~95% compared to raw HTML.
69
+ """
70
+ self.last_active = datetime.now(timezone.utc).isoformat()
71
+ aom_nodes = []
72
+ node_counter = 1
73
+
74
+ # Interactive patterns
75
+ # 1. Buttons
76
+ for m in re.finditer(r"<button([^>]*)>(.*?)</button>", self.html, re.IGNORECASE | re.DOTALL):
77
+ attrs, content = m.groups()
78
+ text = re.sub(r"<[^>]+>", "", content).strip() or "Unnamed Button"
79
+ aom_nodes.append({
80
+ "id": f"@node-{node_counter}",
81
+ "role": "button",
82
+ "name": text,
83
+ "selector": f"button:contains('{text[:20]}')"
84
+ })
85
+ node_counter += 1
86
+
87
+ # 2. Links
88
+ for m in re.finditer(r"<a\s+([^>]*href=[\"']([^\"']*)[\"'][^>]*)>(.*?)</a>", self.html, re.IGNORECASE | re.DOTALL):
89
+ _, href, content = m.groups()
90
+ text = re.sub(r"<[^>]+>", "", content).strip()
91
+ if text and not href.startswith("javascript:"):
92
+ aom_nodes.append({
93
+ "id": f"@node-{node_counter}",
94
+ "role": "link",
95
+ "name": text[:50],
96
+ "target_url": href,
97
+ "selector": f"a[href='{href}']"
98
+ })
99
+ node_counter += 1
100
+
101
+ # 3. Inputs
102
+ for m in re.finditer(r"<input\s+([^>]*)/?>", self.html, re.IGNORECASE):
103
+ attrs = m.group(1)
104
+ name_m = re.search(r"name=[\"']([^\"']*)[\"']", attrs, re.IGNORECASE)
105
+ type_m = re.search(r"type=[\"']([^\"']*)[\"']", attrs, re.IGNORECASE)
106
+ name_val = name_m.group(1) if name_m else f"input_{node_counter}"
107
+ type_val = type_m.group(1) if type_m else "text"
108
+ aom_nodes.append({
109
+ "id": f"@node-{node_counter}",
110
+ "role": "textbox" if type_val in ("text", "email", "password", "search") else type_val,
111
+ "name": name_val,
112
+ "selector": f"input[name='{name_val}']",
113
+ "current_value": self.form_state.get(name_val, "")
114
+ })
115
+ node_counter += 1
116
+
117
+ return aom_nodes[:50]
118
+
119
+ def click(self, selector: str) -> Dict[str, Any]:
120
+ """Simulates clicking an element."""
121
+ self.last_active = datetime.now(timezone.utc).isoformat()
122
+ return {
123
+ "status": "clicked",
124
+ "selector": selector,
125
+ "session_id": self.session_id,
126
+ "timestamp": self.last_active
127
+ }
128
+
129
+ def fill(self, selector: str, value: str) -> Dict[str, Any]:
130
+ """Fills a form input field."""
131
+ self.last_active = datetime.now(timezone.utc).isoformat()
132
+ # Track input value
133
+ key = selector.replace("input[name='", "").replace("']", "")
134
+ self.form_state[key] = value
135
+ return {
136
+ "status": "filled",
137
+ "selector": selector,
138
+ "value": value,
139
+ "session_id": self.session_id,
140
+ "timestamp": self.last_active
141
+ }
142
+
143
+ def get_status(self) -> Dict[str, Any]:
144
+ """Returns session status metadata."""
145
+ return {
146
+ "session_id": self.session_id,
147
+ "url": self.current_url,
148
+ "title": self.title,
149
+ "status_code": self.status_code,
150
+ "html_length_bytes": len(self.html),
151
+ "form_fields_filled": len(self.form_state),
152
+ "last_active": self.last_active
153
+ }
154
+
155
+ class BrowserEngine:
156
+ def __init__(self):
157
+ self.sessions: Dict[str, BrowserSession] = {}
158
+
159
+ def get_or_create_session(self, session_id: str = "default") -> BrowserSession:
160
+ if session_id not in self.sessions:
161
+ self.sessions[session_id] = BrowserSession(session_id=session_id)
162
+ return self.sessions[session_id]
163
+
164
+ def navigate(self, url: str, session_id: str = "default") -> Dict[str, Any]:
165
+ session = self.get_or_create_session(session_id)
166
+ return session.navigate(url)
167
+
168
+ def extract_clean_text(self, session_id: str = "default") -> str:
169
+ session = self.get_or_create_session(session_id)
170
+ return session.extract_clean_text()
171
+
172
+ def extract_accessibility_tree(self, session_id: str = "default") -> List[Dict[str, Any]]:
173
+ session = self.get_or_create_session(session_id)
174
+ return session.extract_accessibility_tree()
175
+
176
+ def click_element(self, selector: str, session_id: str = "default") -> Dict[str, Any]:
177
+ session = self.get_or_create_session(session_id)
178
+ return session.click(selector)
179
+
180
+ def fill_form_field(self, selector: str, value: str, session_id: str = "default") -> Dict[str, Any]:
181
+ session = self.get_or_create_session(session_id)
182
+ return session.fill(selector, value)
183
+
184
+ def get_page_status(self, session_id: str = "default") -> Dict[str, Any]:
185
+ session = self.get_or_create_session(session_id)
186
+ return session.get_status()
@@ -0,0 +1,217 @@
1
+ """
2
+ BrowserBridge MCP Server (Lite Edition)
3
+ Model Context Protocol stdio interface for headless browser automation and AOM inspection.
4
+ """
5
+
6
+ import sys
7
+ import json
8
+ import logging
9
+ from typing import Callable, Dict, Any, List, Optional
10
+ from .engine import BrowserEngine
11
+
12
+ logging.basicConfig(
13
+ level=logging.INFO,
14
+ format="%(asctime)s [%(levelname)s] [BrowserBridge] %(message)s",
15
+ stream=sys.stderr
16
+ )
17
+ logger = logging.getLogger("BrowserBridgeServer")
18
+
19
+ class BrowserBridgeServer:
20
+ def __init__(self):
21
+ self.name = "browserbridge-mcp"
22
+ self.version = "1.0.0"
23
+ self.engine = BrowserEngine()
24
+ self.tools: Dict[str, Dict[str, Any]] = {}
25
+ self._register_tools()
26
+
27
+ def _register_tool(self, name: str, description: str, func: Callable, properties: Dict[str, Any], required: List[str]):
28
+ self.tools[name] = {
29
+ "name": name,
30
+ "description": description,
31
+ "inputSchema": {
32
+ "type": "object",
33
+ "properties": properties,
34
+ "required": required
35
+ },
36
+ "func": func
37
+ }
38
+
39
+ def _register_tools(self):
40
+ # 1. navigate_page
41
+ self._register_tool(
42
+ name="navigate_page",
43
+ description="Navigate headless browser session to target URL.",
44
+ func=self.engine.navigate,
45
+ properties={
46
+ "url": {"type": "string", "description": "Destination web address (e.g., 'https://example.com')."},
47
+ "session_id": {"type": "string", "description": "Session identifier (default: 'default')."}
48
+ },
49
+ required=["url"]
50
+ )
51
+
52
+ # 2. extract_clean_text
53
+ self._register_tool(
54
+ name="extract_clean_text",
55
+ description="Extract cleaned, semantic markdown text from current page, stripping scripts and clutter.",
56
+ func=self.engine.extract_clean_text,
57
+ properties={
58
+ "session_id": {"type": "string", "description": "Session identifier (default: 'default')."}
59
+ },
60
+ required=[]
61
+ )
62
+
63
+ # 3. extract_accessibility_tree
64
+ self._register_tool(
65
+ name="extract_accessibility_tree",
66
+ description="Extract interactive elements (buttons, links, inputs) as an Accessibility Object Model (AOM), reducing token cost by ~95%.",
67
+ func=self.engine.extract_accessibility_tree,
68
+ properties={
69
+ "session_id": {"type": "string", "description": "Session identifier (default: 'default')."}
70
+ },
71
+ required=[]
72
+ )
73
+
74
+ # 4. click_element
75
+ self._register_tool(
76
+ name="click_element",
77
+ description="Click an interactive element by CSS selector or target ID.",
78
+ func=self.engine.click_element,
79
+ properties={
80
+ "selector": {"type": "string", "description": "CSS selector or element name to click."},
81
+ "session_id": {"type": "string", "description": "Session identifier (default: 'default')."}
82
+ },
83
+ required=["selector"]
84
+ )
85
+
86
+ # 5. fill_form_field
87
+ self._register_tool(
88
+ name="fill_form_field",
89
+ description="Enter text or value into a form input or textarea.",
90
+ func=self.engine.fill_form_field,
91
+ properties={
92
+ "selector": {"type": "string", "description": "CSS selector or input name."},
93
+ "value": {"type": "string", "description": "Text value to insert."},
94
+ "session_id": {"type": "string", "description": "Session identifier (default: 'default')."}
95
+ },
96
+ required=["selector", "value"]
97
+ )
98
+
99
+ # 6. get_page_status
100
+ self._register_tool(
101
+ name="get_page_status",
102
+ description="Inspect active session state, current URL, page title, and HTTP response code.",
103
+ func=self.engine.get_page_status,
104
+ properties={
105
+ "session_id": {"type": "string", "description": "Session identifier (default: 'default')."}
106
+ },
107
+ required=[]
108
+ )
109
+
110
+ def get_tools_definition(self) -> List[Dict[str, Any]]:
111
+ return [
112
+ {
113
+ "name": t["name"],
114
+ "description": t["description"],
115
+ "inputSchema": t["inputSchema"]
116
+ }
117
+ for t in self.tools.values()
118
+ ]
119
+
120
+ def call_tool(self, name: str, arguments: Dict[str, Any]) -> Any:
121
+ if name not in self.tools:
122
+ raise KeyError(f"Tool not found: '{name}'")
123
+ func = self.tools[name]["func"]
124
+ return func(**arguments)
125
+
126
+ def handle_request(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
127
+ req_id = request.get("id")
128
+ method = request.get("method")
129
+ params = request.get("params", {})
130
+
131
+ if req_id is None:
132
+ if method == "notifications/initialized":
133
+ logger.info("BrowserBridge client initialized.")
134
+ return None
135
+
136
+ if method == "initialize":
137
+ return {
138
+ "jsonrpc": "2.0",
139
+ "id": req_id,
140
+ "result": {
141
+ "protocolVersion": "2024-11-05",
142
+ "capabilities": {"tools": {}},
143
+ "serverInfo": {
144
+ "name": self.name,
145
+ "version": self.version
146
+ }
147
+ }
148
+ }
149
+ elif method == "ping":
150
+ return {"jsonrpc": "2.0", "id": req_id, "result": {}}
151
+ elif method == "tools/list":
152
+ return {
153
+ "jsonrpc": "2.0",
154
+ "id": req_id,
155
+ "result": {"tools": self.get_tools_definition()}
156
+ }
157
+ elif method == "tools/call":
158
+ tool_name = params.get("name")
159
+ tool_args = params.get("arguments", {})
160
+ try:
161
+ result = self.call_tool(tool_name, tool_args)
162
+ formatted_text = result if isinstance(result, str) else json.dumps(result, indent=2, ensure_ascii=False)
163
+ return {
164
+ "jsonrpc": "2.0",
165
+ "id": req_id,
166
+ "result": {
167
+ "content": [{"type": "text", "text": formatted_text}],
168
+ "isError": False
169
+ }
170
+ }
171
+ except KeyError as e:
172
+ return {
173
+ "jsonrpc": "2.0",
174
+ "id": req_id,
175
+ "error": {"code": -32601, "message": str(e)}
176
+ }
177
+ except (ValueError, TypeError) as e:
178
+ return {
179
+ "jsonrpc": "2.0",
180
+ "id": req_id,
181
+ "error": {"code": -32602, "message": str(e)}
182
+ }
183
+ except Exception as e:
184
+ logger.error(f"Error calling tool {tool_name}: {e}")
185
+ return {
186
+ "jsonrpc": "2.0",
187
+ "id": req_id,
188
+ "error": {"code": -32000, "message": str(e)}
189
+ }
190
+ else:
191
+ return {
192
+ "jsonrpc": "2.0",
193
+ "id": req_id,
194
+ "error": {"code": -32601, "message": f"Method not found: {method}"}
195
+ }
196
+
197
+ def run_stdio(self):
198
+ """Runs JSON-RPC event loop over stdio."""
199
+ logger.info(f"BrowserBridge Server v{self.version} running on stdio.")
200
+ for line in sys.stdin:
201
+ line = line.strip()
202
+ if not line:
203
+ continue
204
+ try:
205
+ req = json.loads(line)
206
+ res = self.handle_request(req)
207
+ if res is not None:
208
+ sys.stdout.write(json.dumps(res) + "\n")
209
+ sys.stdout.flush()
210
+ except json.JSONDecodeError as e:
211
+ err_resp = {
212
+ "jsonrpc": "2.0",
213
+ "id": None,
214
+ "error": {"code": -32700, "message": f"Parse error: {e}"}
215
+ }
216
+ sys.stdout.write(json.dumps(err_resp) + "\n")
217
+ sys.stdout.flush()
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: browserbridge-mcp
3
+ Version: 1.0.0
4
+ Summary: Headless Browser Session Bridge & Accessibility DOM Inspector for AI Agents
5
+ Author: Johann Schiffer
6
+ License: MIT
7
+ Project-URL: Homepage, https://johannschiffer.github.io
8
+ Project-URL: Repository, https://github.com/johannschiffer/browserbridge-mcp
9
+ Project-URL: Starter Kit, https://johanld.gumroad.com/l/genesis-starter-kit
10
+ Project-URL: Enterprise Suite, https://johanld.gumroad.com/l/genesis-enterprise-suite
11
+ Keywords: mcp,model-context-protocol,browser,headless,automation,claude,cursor,playwright
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: httpx>=0.25.0
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest>=7; extra == "test"
18
+ Dynamic: license-file
19
+
20
+ # BrowserBridge MCP (Lite Edition)
21
+
22
+ <a href="https://glama.ai/mcp/servers/@johannschiffer/browserbridge-mcp">
23
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/@johannschiffer/browserbridge-mcp/badge" alt="browserbridge-mcp MCP server" />
24
+ </a>
25
+
26
+
27
+ > **Free:** [Genesis Starter Kit](https://johanld.gumroad.com/l/genesis-starter-kit) (25 `.cursorrules`/`CLAUDE.md` rulesets + MCP configs). Try the [live config builder](https://johannschiffer.github.io).
28
+ > **Full stack:** all six servers in [Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite).
29
+
30
+
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
32
+ [![Protocol: MCP](https://img.shields.io/badge/MCP-2024--11--05-green.svg)](https://modelcontextprotocol.io/)
33
+ [![Python: >=3.10](https://img.shields.io/badge/Python->=3.10-yellow.svg)](https://python.org/)
34
+
35
+ **BrowserBridge MCP** is a lightweight headless browser session bridge and Accessibility Object Model (AOM) inspector designed for AI coding assistants (Claude Desktop, Cursor, OpenAI Codex, LangGraph).
36
+
37
+ It enables agents to navigate web applications, extract semantic clean text, and interact with web pages without drowning the LLM context window in massive HTML blobs.
38
+
39
+ ---
40
+
41
+ ## Run without cloning
42
+
43
+ ```bash
44
+ uvx --from git+https://github.com/johannschiffer/browserbridge-mcp browserbridge-mcp
45
+ ```
46
+
47
+ Claude Desktop / Cursor config entry:
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "browserbridge-mcp": {
53
+ "command": "uvx",
54
+ "args": [
55
+ "--from",
56
+ "git+https://github.com/johannschiffer/browserbridge-mcp",
57
+ "browserbridge-mcp"
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ [![CI](https://github.com/johannschiffer/browserbridge-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/johannschiffer/browserbridge-mcp/actions/workflows/ci.yml)
65
+
66
+ ## 🎯 The Problem
67
+
68
+ When autonomous AI agents interact with web pages:
69
+ 1. **Context Window Exhaustion:** Dumping 2MB of raw HTML, scripts, CSS, and SVG icons into an LLM exhausts the token budget within 3 turns.
70
+ 2. **Brittle Selectors:** Agents fail to find buttons when modern websites use dynamic, hashed class names (`class="btn_3x9a_primary"`).
71
+ 3. **Zombie Memory Leaks:** Unmanaged headless browsers leak memory on the host system when agents drop sessions without cleanup.
72
+
73
+ **BrowserBridge** solves this by converting web pages into a compact, token-efficient Accessibility Object Model (AOM) tree (~95% token reduction) and providing clean interaction primitives.
74
+
75
+ ---
76
+
77
+ ## ✨ Features (Lite Edition)
78
+
79
+ - 🌐 **Headless Navigation:** Fetch and navigate web pages with automatic redirect handling.
80
+ - 🌳 **Accessibility Tree (AOM) Extraction:** Filters noisy DOMs into structured interactive nodes (buttons, links, textboxes) with stable `@node` identifiers.
81
+ - 🧹 **Clean Text Extractor:** Strips scripts, ads, and formatting for fast summarization.
82
+ - ⚡ **Zero Browser Overhead:** Pure Python runtime compatible with Claude Desktop and Cursor.
83
+
84
+ ---
85
+
86
+ ## 🚀 Quickstart
87
+
88
+ ### 1. Installation
89
+
90
+ ```bash
91
+ git clone https://github.com/johannschiffer/browserbridge-mcp.git
92
+ cd browserbridge-mcp
93
+ pip install -e .
94
+ ```
95
+
96
+ ### 2. Configure in Claude Desktop or Cursor
97
+
98
+ Add to your `claude_desktop_config.json` or Cursor MCP settings:
99
+
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "browserbridge": {
104
+ "command": "browserbridge",
105
+ "args": ["run"]
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### 3. CLI Inspection
112
+
113
+ ```bash
114
+ # Inspect clean accessibility nodes of any website
115
+ browserbridge aom https://news.ycombinator.com
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 🛠️ MCP Tools Reference
121
+
122
+ | Tool | Parameters | Description |
123
+ |---|---|---|
124
+ | `navigate_page` | `url`, `session_id` | Navigates headless browser to destination URL |
125
+ | `extract_clean_text` | `session_id` | Extracts cleaned markdown text from page |
126
+ | `extract_accessibility_tree` | `session_id` | Extracts compact interactive elements (AOM) |
127
+ | `click_element` | `selector`, `session_id` | Simulates click on interactive element |
128
+ | `fill_form_field` | `selector`, `value`, `session_id` | Fills input or textarea field |
129
+ | `get_page_status` | `session_id` | Returns current URL, title, and response code |
130
+
131
+ ---
132
+
133
+ ## 🚀 Upgrade to BrowserBridge Pro
134
+
135
+ Need enterprise-grade auditability, durable execution receipts, and browser pool sandboxing?
136
+
137
+ The **BrowserBridge Pro Edition** adds:
138
+ - 📜 **Durable Action Receipts Engine:** Generates cryptographic SHA-256 receipts for every web action, linking pre/post DOM hashes with visual state snapshots.
139
+ - 🛡️ **Session Pool Manager con Anti-Zombie Auto-Cleanup:** Manages multiple browser contexts with strict inactivity TTLs, preventing memory leaks on your host.
140
+ - 🎯 **Visual Element Selector & Coordinate Resolver:** Resolves robust CSS/XPath selectors and handles dynamic single-page applications (SPAs).
141
+ - 🐳 **Production Docker & Compose:** Containerized headless Chromium runtime ready for cloud execution.
142
+ - 🧪 **100% Passing Pytest Suite.**
143
+
144
+ 👉 **[Get the Genesis Enterprise Suite ($19)](https://johanld.gumroad.com/l/genesis-enterprise-suite)**
145
+
146
+ ---
147
+
148
+ ## 📄 License
149
+
150
+ MIT © 2026 [Johann Schiffer](https://github.com/johannschiffer)
151
+
152
+ ## Recommended Infrastructure
153
+
154
+ Proxies that work well with BrowserBridge MCP. Some links are referral links.
155
+
156
+ - [Webshare](https://www.webshare.io/)
157
+ - [ScraperAPI](https://www.scraperapi.com/)
158
+ - [Bright Data](https://brightdata.com/)
159
+
160
+ ## Genesis MCP Ecosystem
161
+
162
+ | Module | What it does | Repo |
163
+ |---|---|---|
164
+ | **CrawlEngine MCP** | Anti-bot web scraping, clean Markdown, proxy rotation and TLS evasion | [GitHub](https://github.com/johannschiffer/crawlengine-mcp) |
165
+ | **BrowserBridge MCP** (this repo) | Deterministic headless browser automation with cryptographic action receipts | [GitHub](https://github.com/johannschiffer/browserbridge-mcp) |
166
+ | **PgSafe MCP** | Zero-trust PostgreSQL/SQLite guard, live PII masking, zero-downtime migration checks | [GitHub](https://github.com/johannschiffer/pgsafe-mcp) |
167
+ | **OmniBridge MCP** | Multi-channel notification and webhook bus with a persistent SQLite WAL queue | [GitHub](https://github.com/johannschiffer/omnibridge-mcp) |
168
+ | **SysFlow MCP** | Safe Linux administration plane: systemd, port audit, isolated execution | [GitHub](https://github.com/johannschiffer/sysflow-mcp) |
169
+ | **FastMCP Forge** | Production MCP server scaffolder: stdio, SSE and auth middleware | [GitHub](https://github.com/johannschiffer/fastmcp-forge) |
170
+
171
+ [Live config builder](https://johannschiffer.github.io) | [Starter Kit, free](https://johanld.gumroad.com/l/genesis-starter-kit) | [Enterprise Suite, $19](https://johanld.gumroad.com/l/genesis-enterprise-suite)
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ browserbridge/__init__.py
5
+ browserbridge/_genesis.py
6
+ browserbridge/cli.py
7
+ browserbridge/engine.py
8
+ browserbridge/server.py
9
+ browserbridge_mcp.egg-info/PKG-INFO
10
+ browserbridge_mcp.egg-info/SOURCES.txt
11
+ browserbridge_mcp.egg-info/dependency_links.txt
12
+ browserbridge_mcp.egg-info/entry_points.txt
13
+ browserbridge_mcp.egg-info/requires.txt
14
+ browserbridge_mcp.egg-info/top_level.txt
15
+ tests/test_genesis_entrypoints.py
16
+ tests/test_lite_engine.py
17
+ tests/test_lite_server.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ browserbridge = browserbridge.cli:main
3
+ browserbridge-mcp = browserbridge.cli:main
@@ -0,0 +1,4 @@
1
+ httpx>=0.25.0
2
+
3
+ [test]
4
+ pytest>=7
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "browserbridge-mcp"
7
+ version = "1.0.0"
8
+ description = "Headless Browser Session Bridge & Accessibility DOM Inspector for AI Agents"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Johann Schiffer"}
14
+ ]
15
+ keywords = ["mcp", "model-context-protocol", "browser", "headless", "automation", "claude", "cursor", "playwright"]
16
+ dependencies = [
17
+ "httpx>=0.25.0"
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ test = ["pytest>=7"]
22
+
23
+ [project.scripts]
24
+ browserbridge-mcp = "browserbridge.cli:main"
25
+ browserbridge = "browserbridge.cli:main"
26
+
27
+ [tool.setuptools.packages.find]
28
+ include = ["browserbridge*"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://johannschiffer.github.io"
32
+ Repository = "https://github.com/johannschiffer/browserbridge-mcp"
33
+ "Starter Kit" = "https://johanld.gumroad.com/l/genesis-starter-kit"
34
+ "Enterprise Suite" = "https://johanld.gumroad.com/l/genesis-enterprise-suite"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+ import importlib
2
+ import io
3
+ import os
4
+ import sys
5
+
6
+
7
+ def test_script_target_is_callable():
8
+ mod = importlib.import_module('browserbridge.cli')
9
+ assert callable(getattr(mod, 'main'))
10
+
11
+
12
+ def test_upgrade_notice_goes_to_stderr(capsys, monkeypatch):
13
+ monkeypatch.delenv('GENESIS_NO_NOTICE', raising=False)
14
+ from browserbridge._genesis import print_upgrade_notice
15
+ print_upgrade_notice()
16
+ out = capsys.readouterr()
17
+ assert out.out == ''
18
+ assert 'gumroad.com' in out.err
19
+
20
+
21
+ def test_upgrade_notice_can_be_silenced(capsys, monkeypatch):
22
+ monkeypatch.setenv('GENESIS_NO_NOTICE', '1')
23
+ from browserbridge._genesis import print_upgrade_notice
24
+ print_upgrade_notice()
25
+ assert capsys.readouterr().err == ''
@@ -0,0 +1,30 @@
1
+ import pytest
2
+ from browserbridge.engine import BrowserSession, BrowserEngine
3
+
4
+ def test_lite_session():
5
+ session = BrowserSession(session_id="test_lite")
6
+ assert session.session_id == "test_lite"
7
+ assert session.status_code == 200
8
+
9
+ def test_lite_extract_clean_text():
10
+ session = BrowserSession()
11
+ session.html = "<html><body><h1>Title</h1><p>Paragraph with <b>bold</b> text.</p></body></html>"
12
+ text = session.extract_clean_text()
13
+ assert "Title" in text
14
+ assert "Paragraph with bold text." in text
15
+
16
+ def test_lite_aom_extraction():
17
+ session = BrowserSession()
18
+ session.html = '<html><body><button>Click Me</button><a href="/docs">Docs</a><input name="user" /></body></html>'
19
+ aom = session.extract_accessibility_tree()
20
+ assert len(aom) == 3
21
+ assert aom[0]["role"] == "button"
22
+ assert aom[1]["role"] == "link"
23
+ assert aom[2]["role"] == "textbox"
24
+
25
+ def test_lite_engine():
26
+ engine = BrowserEngine()
27
+ sess = engine.get_or_create_session("s1")
28
+ assert sess.session_id == "s1"
29
+ status = engine.get_page_status("s1")
30
+ assert status["session_id"] == "s1"
@@ -0,0 +1,16 @@
1
+ import pytest
2
+ import json
3
+ from browserbridge.server import BrowserBridgeServer
4
+
5
+ def test_lite_server():
6
+ server = BrowserBridgeServer()
7
+ assert server.name == "browserbridge-mcp"
8
+ assert len(server.tools) == 6
9
+
10
+ req = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}
11
+ res = server.handle_request(req)
12
+ assert res["result"]["serverInfo"]["name"] == "browserbridge-mcp"
13
+
14
+ tools_req = {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
15
+ tools_res = server.handle_request(tools_req)
16
+ assert len(tools_res["result"]["tools"]) == 6