Use when automating browsers, scraping web data, executing SQL-like queries against web pages, managing proxy caching or request interception, or building any browser automation workflow with BrowserX MCP tools
From browserxnpx claudepluginhub layerdynamics/lore --plugin browserxThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
BrowserX is a composable browser toolkit exposed via MCP. It provides three tool categories: Query Engine (SQL-like declarative queries), Browser Tools (imperative session-based automation), and Proxy Tools (caching and request interception).
Core principle: Use browserx_query for simple one-shot operations. Use browser_* tools for multi-step workflows needing session persistence. Use proxy_* tools for network-level control.
Additional documentation:
mcp-server/docs/AGENT_GUIDE.md - Comprehensive agent guide with error recoverymcp-server/docs/WORKFLOWS.md - Step-by-step workflow examples with anti-patternsmcp__browserx__*)Need to do something with a web page?
├── Simple, one-shot extraction? → browserx_query (SQL-like)
├── Multi-step workflow (login, fill form, navigate, screenshot)? → browser_* tools
├── Block/modify network requests? → proxy_add_interceptor
├── Cache responses? → proxy_cache_set/get
└── Long-running query? → browserx_query_async + browserx_query_status
| Tool | Purpose |
|---|---|
browserx_query | Execute SQL-like query (sync) |
browserx_query_async | Execute query async, returns queryId |
browserx_query_status | Check async query progress |
browserx_query_cancel | Cancel running async query |
browserx_query_explain | Dry-run: get execution plan without executing |
| Tool | Purpose |
|---|---|
browser_navigate | Navigate to URL; creates session if no sessionId |
browser_click | Click element (CSS or XPath selector) |
browser_type | Type into input (with optional clear, delay) |
browser_screenshot | Capture page/element screenshot (PNG/JPEG) |
browser_pdf | Generate PDF (A4/Letter/Legal/A3) |
browser_evaluate | Execute arbitrary JavaScript in page |
browser_query_dom | Extract structured data from DOM elements |
browser_wait | Wait for condition (time/selector/function) |
browser_close_session | Release browser session resources |
browser_list_sessions | List all active sessions |
| Tool | Purpose |
|---|---|
proxy_cache_get | Retrieve cached value by key |
proxy_cache_set | Store value with optional TTL (milliseconds) |
proxy_cache_clear | Clear cache entries by regex pattern |
proxy_add_interceptor | Add request interceptor (allow/block/modify) |
proxy_remove_interceptor | Remove interceptor by ID |
This is the most common source of errors. The syntax is specific and must be exact.
-- Extract data from a page
SELECT title, description FROM "https://example.com"
-- Navigate with proxy/browser configuration
NAVIGATE TO "https://api.example.com"
WITH {
proxy: { cache: true, headers: {"Authorization": "Bearer token"} },
browser: { viewport: {width: 1920, height: 1080} }
}
CAPTURE response.body, dom.title
-- Insert values into form fields
INSERT "user@example.com" INTO "#email"
INSERT "password" INTO "#password"
-- Click elements
CLICK "#submit"
-- Conditional execution
IF EXISTS("#login-form") THEN
INSERT "user@example.com" INTO "#email"
CLICK "#submit"
ELSE
SELECT "Already logged in"
-- Loop through URLs
FOR EACH url IN ["https://example1.com", "https://example2.com"]
SELECT title, description FROM url
-- Configure engine settings
SET timeout = 30000
SET proxy.cache = true
-- Display state
SHOW cache
SHOW cookies
SHOW headers
| Type | Operators |
|---|---|
| Comparison | =, !=, >, >=, <, <= |
| Logical | AND, OR, NOT |
| Arithmetic | +, -, *, /, % |
| String | || (concat), LIKE, MATCHES |
| Collection | IN, CONTAINS |
DOM:
TEXT(selector) - Extract text contentHTML(selector) - Extract inner HTMLATTR(selector, name) - Extract attribute valueCOUNT(selector) - Count matching elementsEXISTS(selector) - Check if element existsString:
UPPER(text), LOWER(text), TRIM(text)SUBSTRING(text, start, length)REPLACE(text, pattern, replacement)SPLIT(text, delimiter)Network:
HEADER(request, name) - Get header valueSTATUS(response) - Get status codeBODY(response) - Get response bodyCACHED(url) - Check if URL is cachedUtility:
PARSE_JSON(text) - Parse JSON stringPARSE_HTML(text) - Parse HTML stringWAIT(duration) - Wait for durationSCREENSHOT() - Capture screenshotPDF() - Generate PDFbrowserx_query supports: JSON (default), TABLE, CSV, HTML, XML, YAML
Sessions are backed by the Runtime's BrowserPool — a unified resource pool that manages browser instance lifecycle. When you create a session, the SessionManager acquires a browser instance from the pool. When you close it, the instance is released back for reuse.
1. CREATE: browser_navigate(url) → returns sessionId
(acquires browser instance from BrowserPool)
2. USE: Pass sessionId to ALL subsequent browser_* calls
3. CLEANUP: browser_close_session(sessionId) when done
(releases instance back to pool for reuse)
Critical rules:
browser_navigate WITHOUT sessionId creates a NEW sessionbrowser_navigate WITH sessionId reuses the existing sessionbrowser_list_sessions to check active sessions"Cannot create session: all browser pool slots are in use. Close an existing session first."system_dashboard to check pool stats, health status, and session metrics-- Via browserx_query (one call)
SELECT title, price FROM "https://store.com/product/123"
1. browser_navigate(url, waitUntil: "networkidle") → sessionId
2. browser_wait(sessionId, type: "selector", selector: ".products-loaded")
3. browser_query_dom(sessionId, selector: ".product", extract: [
{name: "title", getText: true},
{name: "price", getText: true},
{name: "link", attribute: "href"},
{name: "sku", attribute: "data-sku"}
])
4. browser_close_session(sessionId)
1. browser_navigate("https://example.com/login") → sessionId
2. browser_type(sessionId, "#email", "user@test.com", clear: true)
3. browser_type(sessionId, "#password", "pass123", clear: true)
4. browser_click(sessionId, "button[type=submit]")
5. browser_wait(sessionId, type: "selector", selector: ".dashboard")
6. browser_screenshot(sessionId, fullPage: true)
7. browser_close_session(sessionId)
1. proxy_add_interceptor(action: "block", urlPattern: ".*tracking\\.example\\.com.*")
2. proxy_add_interceptor(action: "modify", urlPattern: ".*api\\.example\\.com.*",
modifications: {headers: {"Cache-Control": "max-age=300"}})
3. proxy_cache_set(key: "api-response", value: <data>, ttl: 300000) // 5 min
4. proxy_cache_get(key: "api-response") // retrieve later
1. browserx_query_explain(query) → execution plan, cost estimate
2. browserx_query(query) → actual execution
1. browserx_query_async(query, timeout: 60000) → queryId
2. browserx_query_status(queryId) → progress %
3. browserx_query_cancel(queryId) → if needed
browser_wait with selector - Wait for specific DOM element. Most reliable.
browser_wait(sessionId, type: "selector", selector: ".loaded", timeout: 10000)
browser_wait with function - Wait for JavaScript condition. For compound conditions.
browser_wait(sessionId, type: "function",
condition: "document.readyState === 'complete' && window.location.pathname === '/dashboard'",
timeout: 10000)
browser_wait with time - Fixed delay. Last resort only.
browser_wait(sessionId, type: "time", duration: 3000)
Never use time waits when a selector or function wait would work. Time waits are fragile and slow.
# stdio transport (for Claude Desktop)
deno task mcp:start
# HTTP transport (for custom integrations)
deno task mcp:start:http
| Variable | Default | Description |
|---|---|---|
MCP_TRANSPORT | stdio | Transport: stdio or http |
MCP_PORT | 3000 | HTTP port |
MCP_PERMISSIONS | AUTOMATION | Level: READONLY, AUTOMATION, FULL |
MCP_MAX_SESSIONS | 10 | Max concurrent browser sessions |
{
"mcpServers": {
"browserx": {
"command": "deno",
"args": ["task", "mcp:start"],
"cwd": "/path/to/BrowserX"
}
}
}
Page Resources (page://{sessionId}/*):
page://{sessionId}/content - Full page HTMLpage://{sessionId}/screenshot - Current viewport screenshotpage://{sessionId}/title - Document titlepage://{sessionId}/url - Current URLMetrics Resources:
metrics://query-engine - Query execution statsmetrics://browser-pool - Session pool healthmetrics://runtime - Runtime performancemetrics://cache - Cache statisticsTip: Use resources for passive state retrieval; use tools for actions.
Symptoms: Tool returns timeout error, operation did not complete Recovery:
timeout parameterwaitUntil: "domcontentloaded" instead of "load"browser_query_dom firstSymptoms: Cannot create session: all browser pool slots are in use
Recovery:
browser_close_sessionbrowser_list_sessions for forgotten sessionssystem_dashboard to see pool stats (total/idle/in-use)Symptoms: Session not found or Invalid session ID
Recovery:
browser_navigate (no sessionId)browser_list_sessions for active sessionsSymptoms: Element not found, No element matches selector
Recovery:
browser_query_dom(sessionId, yourSelector)browser_wait(sessionId, type: "selector", selector: yourSelector)Symptoms: Navigation failed, Net error
Recovery:
waitUntil options| Mistake | Fix |
|---|---|
Wrong query syntax: SET 'selector' TO 'value' | Use INSERT "value" INTO "selector" |
Wrong query syntax: INSERT CLICK ON 'selector' | Use CLICK "selector" |
| Forgetting sessionId on subsequent calls | Always pass sessionId from browser_navigate response |
| Not waiting after navigation/click | Use browser_wait with selector or function, not time |
| Using time-based waits | Use selector or function waits instead |
| Not closing sessions | Always browser_close_session when done |
| Guessing query syntax | Use browserx_query_explain to validate first |
| Cache TTL in seconds | TTL is in milliseconds (5 min = 300000) |
| Missing WITH clause for NAVIGATE | Use NAVIGATE TO url WITH {proxy: {...}, browser: {...}} |
| Missing CAPTURE clause | Use NAVIGATE TO url CAPTURE response.body, dom.title |
| Not checking proxy controller availability | Proxy tools return error if proxy not enabled in config |
Query Engine (SQL-like) → Declarative interface for AI/humans
↓
MCP Server → Tool API (stdio/HTTP transport)
├── SessionManager → Manages browser sessions (acquire/release from pool)
└── ServiceInitializer → Lazy init: Runtime starts on first browser tool call
↓
Runtime → Orchestrates browser + proxy + query
├── BrowserPool → Manages browser instance lifecycle (acquire/release)
├── HealthChecker → Component health monitoring (session-manager, pool)
├── MetricsCollector → Session and pool metrics
└── EventCoordinator → session_created/closed/expired events
↓
Proxy Engine → Traffic routing, middleware, caching
↓
Browser Engine → HTML/CSS parsing, JS execution, rendering
Session flow: browser_navigate → SessionManager.createSession() → BrowserPool.acquire() → BrowserInstance → session wraps instance
Shutdown order: SessionManager (releases all pool instances) → Runtime (stops BrowserPool)
Query pipeline: Query String → Lexer → Parser → Semantic Analyzer → Optimizer → Planner → Executor → Formatter