This is a Chrome DevTools Protocol gateway that gives AI agents direct browser control through eight MCP tools: navigate, screenshot, viewport manipulation, element interaction, and JavaScript evaluation. It sits between your agent and one or more browser providers (cloud CDP services, Playwright servers, local Chrome), routing connections with automatic failover and load balancing across five strategies. The MCP mode auto-detects Chrome and requires zero config, but you can point it at a gateway.yml for multi-provider setups with health checks and concurrency limits. If a provider dies mid-session, your agent's next action routes to a healthy one. Also includes a REST API for screenshots and content extraction if you want stateless scraping instead of persistent sessions.
OpenRouter for browsers.
One endpoint that routes across every browser provider you use: automatic failover, persistent profiles, session replay, REST API, MCP server, dashboard.
Works unchanged with Puppeteer, Playwright, Stagehand, browser-use, and any MCP client.
Website · Quick start · MCP · Profiles · Replays · REST API · Dashboard
One endpoint. Multiple providers. Automatic failover when one is saturated or goes down.
Your app connects to ws://gateway:9500/v1/connect. The gateway picks the best available provider based on health, capacity, and your routing strategy. Providers can be cloud CDP services, Docker containers, or local Chrome instances.
A web dashboard ships with every install. Open http://localhost:9500/web after starting the gateway.
Overview. Active sessions, queue depth, provider health, connection endpoint, and a copy-paste quickstart for Puppeteer, Playwright, Stagehand, browser-use, and raw CDP.
REST API. Run screenshot, content extraction, and structured scraping endpoints from a form-driven UI, with profile selection and parameter reference inline.
Playground. Drive any provider live from the browser. Pick a provider and profile, type into the canvas as if it were a local browser, and watch the remote session in real time.
maxConcurrent on every backendPOST /v1/screenshot returns any URL as PNG or JPEG, full-page or scoped to a selectorPOST /v1/content returns markdown, plain text, HTML, or a cleaned articlePOST /v1/scrape extracts structured data via CSS selectors or full-page formatslocalStorage, sessionStorage, and IndexedDB are captured on disconnect and replayed on the next connect with the same id?profile=acme to the WebSocket URL, the rest is automatic.bgp blobs are portable between gateway installsSee the Profiles docs for the full guide, security model, REST endpoints, and limitations.
Page.startScreencastSee the Replays docs for the storage layout, REST endpoints, and tuning knobs.
gateway.yml in-browser with syntax highlighting and validationnpm install -g browser-gateway
Create gateway.yml:
version: 1
providers:
primary:
url: wss://provider.example.com?token=${PROVIDER_TOKEN}
limits:
maxConcurrent: 5
priority: 1
fallback:
url: ws://my-playwright-server:4000
limits:
maxConcurrent: 10
priority: 2
browser-gateway serve
Connect from your app:
// For CDP providers
const browser = await chromium.connectOverCDP('ws://localhost:9500/v1/connect');
// For Playwright run-server providers
const browser = await chromium.connect('ws://localhost:9500/v1/connect');
Or use the REST API — no WebSocket management needed:
# Screenshot
curl -X POST http://localhost:9500/v1/screenshot \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}' --output screenshot.png
# Extract content as markdown
curl -X POST http://localhost:9500/v1/content \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "formats": ["markdown"]}'
Dashboard at http://localhost:9500/web.
Add to your Claude Code or Cursor config:
{
"mcpServers": {
"browser-gateway": {
"command": "npx",
"args": ["browser-gateway", "mcp"]
}
}
}
No config files needed. The agent gets navigate, snapshot, screenshot, click, type, and evaluate tools through the gateway's routing layer.
See the MCP docs for all options.
Set BG_TOKEN to require a token (or put it in a .env file):
BG_TOKEN=my-secret-token browser-gateway serve
?token= query paramAuthorization: Bearer <token> header/health) is always public# Proxy server
browser-gateway serve # Start the gateway + dashboard
browser-gateway serve --port 8080 # Custom port
browser-gateway serve --config path.yml # Custom config
# MCP server for AI agents
browser-gateway mcp # Auto-detect Chrome, zero config
browser-gateway mcp --headless # Headless mode (for CI/Docker)
browser-gateway mcp --cdp-endpoint ws:// # Connect to existing browser
browser-gateway mcp --config gateway.yml # Multi-provider with failover
# Utilities
browser-gateway check # Test provider connectivity
browser-gateway version # Print version
browser-gateway help # Show help
| Endpoint | Method | Description |
|---|---|---|
/v1/connect | WebSocket | Connect to a browser (the core feature) |
/v1/screenshot | POST | Take a screenshot of any URL (docs) |
/v1/content | POST | Extract page content as markdown, text, or HTML (docs) |
/v1/scrape | POST | Extract data via CSS selectors or full-page formats (docs) |
/v1/status | GET | Gateway health + provider status + pool status |
/v1/sessions | GET | Active sessions |
/v1/providers | GET/POST | List or add providers |
/v1/providers/:id | PUT/DELETE | Update or remove a provider |
/v1/providers/:id/test | POST | Test provider connectivity |
/v1/config | GET/PUT | Read or save config |
/v1/config/validate | POST | Validate YAML without saving |
/mcp | POST | MCP Streamable HTTP endpoint |
/json/version | GET | CDP discovery (for browser-use, Playwright, Stagehand) |
/health | GET | Health check |
Recommended: Docker Compose. The bundled docker-compose.yml mounts a named volume for state and a read-only gateway.yml from the host.
# Drop your gateway.yml next to docker-compose.yml, then:
docker compose up -d
Plain docker run:
docker run -d \
-p 9500:9500 \
-v bg_data:/data \
-v ./gateway.yml:/app/gateway.yml:ro \
-e PROVIDER_TOKEN=xxx \
ghcr.io/browser-gateway/server:latest
Everything the gateway writes to disk lives under a single directory, BG_DATA_DIR (defaults to /data inside the image). Mount that as a named volume or a bind mount and all state survives container restarts and image upgrades. Today it contains:
profiles/ — encrypted profile blobs (when profiles are enabled)Future versions may add more subdirectories under the same root (cooldown state, session snapshots, captures). Mounting BG_DATA_DIR as one volume keeps every subsystem persistent without follow-up config changes.
State lives in the volume, code lives in the image. Pull the new image, recreate the container — no data lost:
docker compose pull
docker compose up -d
The container reads the same BG_DATA_DIR and the same gateway.yml. Profile blobs are versioned and the gateway reads older formats transparently.
| Tag | Updated on |
|---|---|
:0.3.0 (and every subsequent version) | published manually after a release |
:latest | always points at the newest version |
Images are multi-arch (linux/amd64, linux/arm64), signed with Sigstore build provenance, and ship an SBOM. Verify with the GitHub CLI:
gh attestation verify oci://ghcr.io/browser-gateway/server:0.3.0 \
--repo browser-gateway/browser-gateway
Sessions without profile / recording / observability (the default) take the byte-pipe fast lane:
ws://gateway:9500/v1/connect101 Switching Protocolsclient <-> gateway <-> providerSessions with profile inject, session recording, live view, or observability (e.g. ?profile=X, ?session_record=true, /v1/live) run through a CDP-aware pipeline instead — one WebSocket per session, N plugins observing the wire. Same routing + failover, byte-perfect passthrough at rest, plugins only fire when their feature is requested. Architecture + plugin-authoring guide: docs/PIPELINE.md.
browserserve is the stack's own self-hosted browser server: one container that hands out isolated Chrome sessions over CDP. Add it like any other provider:
providers:
browserserve:
url: ws://your-host:9222
cloud-provider:
url: <websocket-url-with-auth>
priority: 2
Because the gateway controls that runtime, a browserserve provider is auto-detected and unlocks two things no external provider gets:
maxConcurrent. browserserve measures its host (memory, thread, and CPU limits) and advertises a safe ceiling, which the gateway adopts. The dashboard shows it as (auto).A common shape: browserserve as the primary provider on your own hardware, with a cloud provider at a lower priority for failover.
browser-gateway is compatible with existing browser tools. Just pass the gateway URL — it auto-resolves via /json/version.
AI Agent Frameworks:
# browser-use (Python) — HTTP URL auto-resolves
BrowserSession(cdp_url="http://localhost:9500")
// Stagehand (TypeScript)
new Stagehand({ env: "LOCAL", localBrowserLaunchOptions: { cdpUrl: "http://localhost:9500" } })
Playwright MCP (all 70 Playwright tools through gateway routing):
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--cdp-endpoint", "http://localhost:9500"]
}
}
}
Puppeteer / Playwright:
// Playwright — HTTP or WebSocket
const browser = await chromium.connectOverCDP("http://localhost:9500");
// Puppeteer — WebSocket
const browser = await puppeteer.connect({ browserWSEndpoint: "ws://localhost:9500/v1/connect" });
Full docs live at docs.browsergateway.com.
Contributions welcome. See CONTRIBUTING.md for guidelines.
MIT - see LICENSE.
Questions, security reports, or partnership inquiries: hello@browsergateway.com.
Maintained by Monostellar Labs.
BG_TOKENsecretAuthentication token for gateway access (optional, only needed with remote providers)
therealtimex/browser-use
jae-jae/fetcher-mcp
merajmehrabi/puppeteer-mcp-server
com.thenextgennexus/playwright-mcp-server
saik0s/mcp-browser-use