Metadata-Version: 2.4
Name: browserious
Version: 0.1.2
Summary: MCP server for Browserious — browser-as-a-service for AI agents
License-Expression: MIT
Requires-Python: >=3.11
Requires-Dist: fastmcp>=2.0
Requires-Dist: httpx>=0.27
Requires-Dist: playwright>=1.40
Description-Content-Type: text/markdown

# Browserious - Browser API Wrapper

Self-hosted, API-controlled browser-as-a-service for automation, testing, and web scraping.

[![Tests](https://github.com/muzhig/browserious/actions/workflows/tests.yml/badge.svg)](https://github.com/muzhig/browserious/actions/workflows/tests.yml)
[![Python](https://img.shields.io/badge/python-3.11+-blue.svg)](https://python.org)
[![Docker](https://img.shields.io/badge/docker-required-blue.svg)](https://docker.com)

## Overview

Browserious provides a REST API + WebSocket interface to control Chromium browsers running in Docker containers. Each browser session runs in an isolated container with:

- **Persistent Profiles** - Maintain cookies, localStorage, and session state
- **Proxy Support** - Configure HTTP/HTTPS/SOCKS5 proxies per session
- **Extensions** - Load custom Chrome extensions
- **Request Interception** - Block, redirect, or mock network requests
- **Remote Viewing** - Watch browser sessions via VNC
- **CDP Access** - Direct Chrome DevTools Protocol control

**Primary Use Cases:**
- Browser automation and E2E testing
- Web scraping and data extraction
- Remote browser service

## Quick Start

### Prerequisites

- Docker and Docker Compose
- Python 3.11+ (for development)

### Installation

```bash
# Clone repository
git clone https://github.com/muzhig/browserious.git
cd browserious

# Generate API key
export API_KEYS=$(openssl rand -base64 32)

# Start service
docker-compose up -d

# Verify
curl -H "X-API-Key: $API_KEYS" http://localhost:8100/sessions
```

### Create Your First Session

```bash
# Create browser session
SESSION_ID=$(curl -X POST http://localhost:8100/sessions \
  -H "X-API-Key: $API_KEYS" \
  -H "Content-Type: application/json" \
  -d '{"timeout": 600}' \
  | jq -r '.session_id')

# Navigate to a page
curl -X POST http://localhost:8100/sessions/$SESSION_ID/pages/page-0/goto \
  -H "X-API-Key: $API_KEYS" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'

# Take screenshot
curl -H "X-API-Key: $API_KEYS" \
  http://localhost:8100/sessions/$SESSION_ID/pages/page-0/screenshot \
  > screenshot.png

# Watch in browser (VNC)
# Open the web_vnc_url from the session response, e.g.:
# http://localhost:8100/vnc.html?session=$SESSION_ID

# Cleanup
curl -X DELETE http://localhost:8100/sessions/$SESSION_ID \
  -H "X-API-Key: $API_KEYS"
```

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│  API Container (browserious-api)                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │  FastAPI Application :8100                        │  │
│  │  - REST API + WebSocket                           │  │
│  │  - VNC WebSocket proxy /sessions/{id}/vnc         │  │
│  │  - Web VNC viewer /vnc.html?session={id}          │  │
│  │  - Docker API (creates/manages browser nodes)     │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
           │
           │ Docker Network: browserious
           ▼
┌─────────────────────────────────────────────────────────┐
│  Browser Node Containers (one per session)              │
│                                                         │
│  ┌─────────────────┐    ┌─────────────────┐            │
│  │ session-abc123  │    │ session-xyz789  │   ...      │
│  │                 │    │                 │            │
│  │  Xvfb :99       │    │  Xvfb :99       │            │
│  │  x11vnc :5900   │    │  x11vnc :5900   │            │
│  │  Chromium       │    │  Chromium       │            │
│  │  CDP :9222      │    │  CDP :9222      │            │
│  └─────────────────┘    └─────────────────┘            │
│                                                         │
│  (No port conflicts - each node uses same internal     │
│   ports, addressed by container hostname/IP)           │
└─────────────────────────────────────────────────────────┘
```

## Features

### ✅ Session Management
- Create isolated browser sessions
- Configure proxy, viewport, user agent
- Persistent profiles with cookie/storage management
- Automatic timeout and cleanup

### ✅ Page Operations
- Navigate, click, type, fill forms
- Multiple selector strategies (CSS, XPath, Playwright, JS)
- Wait for elements (visibility, loading states)
- Execute JavaScript in page context
- Screenshots (full page or viewport)
- HTML content extraction

### ✅ Request Interception
- Block requests (ads, trackers, images)
- Redirect URLs (mock APIs, override media)
- Override responses (status, headers, body)
- Inspect and capture requests

### ✅ Extensions
- Upload custom Chrome extensions
- Load extensions per session
- Support for .zip and .crx formats

### ✅ Remote Access
- VNC server with web client (noVNC)
- Per-session password protection
- Real-time browser viewing

### ✅ CDP WebSocket
- Direct Chrome DevTools Protocol access
- Compatible with Puppeteer/Playwright
- Full browser control for advanced use cases

## Documentation

| Document | Description |
|----------|-------------|
| [PRD](docs/PRD.md) | Product Requirements Document - features, use cases, MVP scope |
| [ADR](docs/ADR.md) | Architecture Decision Records - key technical decisions and rationale |
| [API](docs/API.md) | Complete API Reference - endpoints, examples, error codes |
| [EXAMPLES](docs/EXAMPLES.md) | Code examples - form interaction, content extraction, visual debugging |
| [SECURITY](docs/SECURITY.md) | Security Analysis - threat model, hardening, best practices |
| [Project Brief](docs/project-brief.md) | Original project specification |

## MCP Server for Claude Code

Browserious includes an MCP (Model Context Protocol) server for integration with Claude Code and other AI assistants.

### Setup

Install:

```bash
pip install browserious
# or run without installing:
uvx browserious
```

Add to your Claude Code, Cursor, or Claude Desktop config:

```json
{
  "mcpServers": {
    "browserious": {
      "command": "uvx",
      "args": ["browserious"],
      "env": {
        "BROWSERIOUS_API_URL": "https://api.browserious.com",
        "BROWSERIOUS_API_KEY": "<your-api-token>"
      }
    }
  }
}
```

The MCP server runs locally on your machine. It uses the API for session lifecycle (create/delete) and connects directly to browser containers for all operations — no proxy overhead.

### Available Tools

The MCP server exposes 20 tools:

| Tool | Description |
|------|-------------|
| `create_session` | Create browser session, returns session_id and URLs |
| `delete_session` | Close and cleanup session |
| `list_sessions` | List all active sessions |
| `create_page` | Open new tab, optionally navigate to URL |
| `page_goto` | Navigate to URL |
| `page_click` | Click element (CSS, XPath, text, role, JS selectors) |
| `page_type` | Type text with keyboard events |
| `page_fill` | Fill input instantly |
| `page_eval` | Execute JavaScript, return result |
| `page_wait_for_selector` | Wait for element state |
| `page_content` | Get full HTML |
| `page_screenshot` | Capture page, save to file, return path |
| `display_screenshot` | Capture X11 display with cursor |
| `play_input` | Hardware-level mouse/keyboard via X11 |
| `get_cursor_position` | Get current cursor screen coordinates |

### Screenshot Handling

Screenshots are saved to files and the path is returned, allowing multimodal AI to view them:

```python
# MCP tool returns: {"file_path": "/tmp/screenshot_abc123.png", "size_bytes": 45678}
# AI can then read the image file directly
```

### Coordinate System

For `play_input`, convert viewport coordinates to screen coordinates:

```
screen_x = viewport_x + screenX
screen_y = viewport_y + screenY + chromeHeight
```

Get these values from `window_info` in the session response or via `page_eval`:

```javascript
({
    screenX: window.screenX,
    screenY: window.screenY,
    chromeHeight: window.outerHeight - window.innerHeight
})
```

## API Examples

### Web Scraping with Proxy

```python
import requests

API_KEY = "your-api-key"
BASE_URL = "http://localhost:8100"
headers = {"X-API-Key": API_KEY}

# Create session with proxy
session = requests.post(
    f"{BASE_URL}/sessions",
    headers=headers,
    json={
        "profile_id": "scraper-001",
        "proxy": {"server": "http://proxy.example.com:8080"},
        "timeout": 3600
    }
).json()
session_id = session["session_id"]

# Block images to speed up scraping
requests.post(
    f"{BASE_URL}/sessions/{session_id}/routes",
    headers=headers,
    json={
        "pattern": "**/*.{png,jpg,jpeg,gif}",
        "action": {"type": "block"}
    }
)

# Navigate and extract data
requests.post(
    f"{BASE_URL}/sessions/{session_id}/pages/page-0/goto",
    headers=headers,
    json={"url": "https://example.com"}
)

data = requests.post(
    f"{BASE_URL}/sessions/{session_id}/pages/page-0/eval",
    headers=headers,
    json={"script": "return document.querySelectorAll('.item').length"}
).json()

print(f"Found {data['result']} items")

# Cleanup
requests.delete(f"{BASE_URL}/sessions/{session_id}", headers=headers)
```

### E2E Testing with Mocked API

```python
# Create test session
session = requests.post(
    f"{BASE_URL}/sessions",
    headers=headers,
    json={"profile_id": "test-user"}
).json()
session_id = session["session_id"]

# Mock API response
requests.post(
    f"{BASE_URL}/sessions/{session_id}/routes",
    headers=headers,
    json={
        "pattern": "**/api/user",
        "action": {
            "type": "override",
            "status": 200,
            "body": '{"id": 123, "name": "Test User"}'
        }
    }
)

# Run test
requests.post(
    f"{BASE_URL}/sessions/{session_id}/pages/page-0/goto",
    headers=headers,
    json={"url": "https://app.example.com"}
)

# Interact with page
requests.post(
    f"{BASE_URL}/sessions/{session_id}/pages/page-0/click",
    headers=headers,
    json={"selector": {"strategy": "css", "value": "#login-button"}}
)

# Capture screenshot on failure
screenshot = requests.get(
    f"{BASE_URL}/sessions/{session_id}/pages/page-0/screenshot",
    headers=headers,
    params={"full_page": True}
).content

with open("test-failure.png", "wb") as f:
    f.write(screenshot)
```

### Using Puppeteer via CDP

```javascript
const puppeteer = require('puppeteer-core');

// Connect to existing session via CDP
const browser = await puppeteer.connect({
  browserWSEndpoint: 'ws://localhost:8100/sessions/{session_id}/cdp'
});

const page = await browser.newPage();
await page.goto('https://example.com');

const title = await page.title();
console.log('Page title:', title);

await page.screenshot({path: 'screenshot.png'});
```

## Configuration

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `API_KEYS` | - | Comma-separated API keys (required) |
| `PROFILES_DIR` | `/data/profiles` | Profile storage directory |
| `EXTENSIONS_DIR` | `/data/extensions` | Extensions storage directory |
| `MAX_SESSIONS` | 10 | Maximum concurrent sessions |
| `DEFAULT_TIMEOUT` | 3600 | Default session timeout (seconds) |
| `RESOLUTION` | `1920x1080x24` | Virtual display resolution |

### Docker Compose

```yaml
services:
  api:
    build: .
    container_name: browserious-api
    ports:
      - "8100:8100"   # API + VNC viewer
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./profiles:/data/profiles
      - ./extensions:/data/extensions
    environment:
      - API_KEYS=${API_KEYS}
      - API_HOST=localhost
      - API_PORT=8100
      - PROFILES_DIR=/data/profiles
      - EXTENSIONS_DIR=/data/extensions
      - HOST_PROFILES_DIR=${HOST_PROFILES_DIR}
      - HOST_EXTENSIONS_DIR=${HOST_EXTENSIONS_DIR}
      - DOCKER_NETWORK=browserious
      - DOCKER_IMAGE=browserious-node:latest
      - MAX_SESSIONS=10
      - DEFAULT_TIMEOUT=3600
    networks:
      - browserious
    restart: unless-stopped

networks:
  browserious:
    name: browserious
    driver: bridge
```

**Note:** The API container requires access to Docker socket to spawn browser node containers. Set `HOST_PROFILES_DIR` and `HOST_EXTENSIONS_DIR` to the absolute paths on your host machine for volume mounts to work correctly in browser nodes.

## Development

### Setup

```bash
# Clone repository
git clone https://github.com/muzhig/browserious.git
cd browserious

# Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt

# Install Playwright browsers
playwright install chromium

# Run tests
pytest

# Run locally (without Docker)
export API_KEYS=test-key
export DISPLAY=:99
Xvfb :99 -screen 0 1920x1080x24 &
python -m uvicorn src.main:app --reload
```

### Rebuilding After Code Changes

Different parts of the codebase require different rebuild steps:

| Changed | Rebuild Command | Notes |
|---------|-----------------|-------|
| `src/*.py` | `docker-compose build api` | Python API code |
| `node/api/*.go` | `docker build -t browserious-node:latest ./node` | Go node API (input simulation) |
| `node/Dockerfile` | `docker build -t browserious-node:latest ./node` | Node container config |
| `node/entrypoint.sh` | `docker build -t browserious-node:latest ./node` | Node startup script |
| `Dockerfile` | `docker-compose build api` | API container config |
| `docker-compose.yml` | `docker-compose up -d` | Just restart, no rebuild |

**Quick reference:**

```bash
# After changing Python code (src/)
docker-compose build api && docker-compose up -d

# After changing Go code (node/api/)
docker build -t browserious-node:latest ./node

# After changing both
docker build -t browserious-node:latest ./node && docker-compose build api && docker-compose up -d

# Full rebuild from scratch
docker build -t browserious-node:latest ./node && docker-compose build --no-cache && docker-compose up -d
```

**Note:** Existing browser sessions use the node image that was current when they were created. New sessions will use the updated image.

### Project Structure

```
browserious/
├── docs/               # Documentation
│   ├── PRD.md
│   ├── ADR.md
│   ├── API.md
│   └── SECURITY.md
├── src/                # Source code
│   ├── main.py
│   ├── browser_manager.py
│   ├── page_operations.py
│   ├── route_manager.py
│   └── ...
├── tests/              # Test suite
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── README.md
```

## Deployment

### Production Checklist

- [ ] Generate strong API keys (`openssl rand -base64 32`)
- [ ] Enable HTTPS (nginx/Caddy reverse proxy)
- [ ] Configure firewall (restrict API/VNC to trusted IPs)
- [ ] Set resource limits (CPU, memory)
- [ ] Enable profile encryption (LUKS or application-level)
- [ ] Set up monitoring and alerting
- [ ] Configure backups
- [ ] Review [Security Documentation](docs/SECURITY.md)

### Horizontal Scaling

Run multiple instances with external load balancer:

```bash
# Instance 1
docker-compose up -d

# Instance 2 (different ports)
docker-compose -f docker-compose.instance2.yml up -d

# Load balancer (nginx)
upstream browserapi {
    server instance1:8000;
    server instance2:8000;
}
```

See [ADR-002](docs/ADR.md#adr-002-manual-orchestration-vs-built-in-scaling) for scaling strategy.

## Security

Browserious provides multiple security layers:
- API key authentication
- Docker container isolation
- Chromium sandbox
- Per-session VNC passwords
- Resource limits

**Important:** This service exposes significant attack surface. Review [SECURITY.md](docs/SECURITY.md) before production deployment.

### Quick Security Wins

1. **HTTPS Only**: Use TLS for API (nginx reverse proxy)
2. **Network Isolation**: Firewall rules to restrict access
3. **Strong API Keys**: Generate with `openssl rand -base64 32`
4. **Resource Limits**: Prevent DoS via docker-compose limits
5. **Profile Encryption**: Encrypt `/data/profiles` volume

## Troubleshooting

### Common Issues

**Session creation fails**
- Check Docker is running: `docker ps`
- Check logs: `docker-compose logs`
- Verify shm_size: `docker inspect | grep ShmSize`

**VNC not showing browser**
- Ensure you're using the web VNC URL: `http://localhost:8100/vnc.html?session={session_id}`
- Check that the session exists: `curl http://localhost:8100/sessions/{session_id}`
- Verify the browser node container is running: `docker ps | grep session-`

**Browser crashes**
- Increase shm_size in docker-compose.yml (recommended: 2g)
- Check memory limits
- Review container logs

**Profile corruption**
- Avoid concurrent access to same profile_id
- Use unique profile IDs: `user-{id}-{session-num}`
- See [ADR-003](docs/ADR.md#adr-003-shared-profile-concurrent-access)

## Roadmap

### MVP (Current)
- [x] Session management
- [x] Page operations (all selector strategies)
- [x] Request interception
- [x] Profile management
- [x] Extension support
- [x] VNC access
- [x] CDP WebSocket

### Post-MVP
- [ ] Metrics and monitoring (`/metrics`, `/health`)
- [ ] Session event webhooks
- [ ] Redis-based session registry (multi-instance)
- [ ] Session recording (Playwright trace)
- [ ] Firefox support
- [ ] Mobile device emulation

## Contributing

Contributions welcome! Please:

1. Fork the repository
2. Create feature branch (`git checkout -b feature/amazing-feature`)
3. Make changes following [code style guidelines](CONTRIBUTING.md#code-style-guidelines)
4. Run tests: `pytest tests/ -v`
5. Commit changes (`git commit -m 'Add amazing feature'`)
6. Push to branch (`git push origin feature/amazing-feature`)
7. Open Pull Request

### Quick Setup for Contributors

```bash
git clone https://github.com/muzhig/browserious.git
cd browserious
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
playwright install chromium
pytest tests/ -v
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.

## Acknowledgments

- [Playwright](https://playwright.dev/) - Browser automation framework
- [FastAPI](https://fastapi.tiangolo.com/) - Modern Python web framework
- [noVNC](https://github.com/novnc/noVNC) - HTML5 VNC client
- [x11vnc](https://github.com/LibVNC/x11vnc) - VNC server

## Support

- **Issues**: [GitHub Issues](https://github.com/muzhig/browserious/issues)
- **Documentation**: [docs/](docs/)
- **Security**: Report vulnerabilities to security@example.com

---

**Built for automation, testing, and scraping workflows.**
