Chrome DevTools Orchestrator - CLI + MCP Integration
Browser debugging and automation through two complementary approaches: CLI (bdg) for speed and token efficiency, MCP for multi-tool integration.
1. WHEN TO USE
Activation Triggers
Use when:
- User mentions "browser debugging", "Chrome DevTools", "CDP" explicitly
- User asks to inspect, test, or automate browser tasks with lightweight CLI approach
- User wants screenshots, HAR files, console logs, or network inspection via terminal
- User mentions "bdg" or "browser-debugger-cli" explicitly
- User needs quick DOM queries, cookie manipulation, or JavaScript execution in browser
- User wants terminal-based browser automation with Unix pipe composability
- User needs production-ready automation scripts for CI/CD browser testing
Automatic Triggers:
- "bdg", "browser-debugger-cli" mentioned explicitly
- "lightweight browser debugging" or "quick CDP access"
- "terminal-based browser automation"
- "screenshot without Puppeteer"
When NOT to Use
Do not use for:
- Complex UI testing suites requiring sophisticated frameworks (use Puppeteer/Playwright)
- Heavy multi-step automation workflows better suited for frameworks
- Cross-browser testing (bdg supports Chrome/Chromium/Edge only)
- Visual regression testing or complex test frameworks
- When user explicitly requests Puppeteer, Playwright, or Selenium
<!-- /ANCHOR:when-to-use --> <!-- ANCHOR:smart-routing-references -->
2. SMART ROUTING
Resource Loading Levels
| Level | When to Load | Resources | | ----------- | ------------------------ | ------------------------------- | | ALWAYS | Every skill invocation | Core CDP pattern reference | | CONDITIONAL | If intent signals match | CLI/MCP/session/troubleshooting | | ON_DEMAND | Only on explicit request | Full diagnostics set |
Smart Router Pseudocode
The authoritative routing logic for scoped loading, weighted intent scoring, and ambiguity handling.
from pathlib import Path
SKILL_ROOT = Path(__file__).resolve().parent
RESOURCE_BASES = (SKILL_ROOT / "references", SKILL_ROOT / "assets")
DEFAULT_RESOURCE = "references/cdp_patterns.md"
INTENT_SIGNALS = {
"CLI": {"weight": 4, "keywords": ["bdg", "browser-debugger-cli", "terminal", "cli"]},
"MCP": {"weight": 4, "keywords": ["mcp", "code mode", "multi-tool", "parallel sessions"]},
"INSTALL": {"weight": 4, "keywords": ["install", "setup", "not installed", "command -v bdg"]},
"TROUBLESHOOT": {"weight": 4, "keywords": ["error", "failed", "troubleshoot", "session issue"]},
"AUTOMATION": {"weight": 3, "keywords": ["ci", "pipeline", "automation", "production"]},
}
RESOURCE_MAP = {
"CLI": ["references/cdp_patterns.md", "references/session_management.md"],
"MCP": ["references/session_management.md", "references/cdp_patterns.md"],
"INSTALL": ["references/troubleshooting.md"],
"TROUBLESHOOT": ["references/troubleshooting.md"],
"AUTOMATION": ["examples/README.md"],
}
LOADING_LEVELS = {
"ALWAYS": [DEFAULT_RESOURCE],
"ON_DEMAND_KEYWORDS": ["full troubleshooting", "full session guide", "all patterns"],
"ON_DEMAND": ["references/troubleshooting.md", "references/session_management.md"],
}
def _task_text(task) -> str:
parts = [
str(getattr(task, "text", "")),
str(getattr(task, "query", "")),
" ".join(getattr(task, "keywords", []) or []),
]
return " ".join(parts).lower()
def _guard_in_skill(relative_path: str) -> str:
resolved = (SKILL_ROOT / relative_path).resolve()
resolved.relative_to(SKILL_ROOT)
if resolved.suffix.lower() != ".md":
raise ValueError(f"Only markdown resources are routable: {relative_path}")
return resolved.relative_to(SKILL_ROOT).as_posix()
def discover_markdown_resources() -> set[str]:
docs = []
for base in RESOURCE_BASES:
if base.exists():
docs.extend(p for p in base.rglob("*.md") if p.is_file())
docs.extend(p for p in (SKILL_ROOT / "examples").rglob("*.md") if (SKILL_ROOT / "examples").exists())
return {doc.relative_to(SKILL_ROOT).as_posix() for doc in docs}
def score_intents(task) -> dict[str, float]:
"""Weighted intent scoring from request text and capability signals."""
text = _task_text(task)
scores = {intent: 0.0 for intent in INTENT_SIGNALS}
for intent, cfg in INTENT_SIGNALS.items():
for keyword in cfg["keywords"]:
if keyword in text:
scores[intent] += cfg["weight"]
if getattr(task, "cli_available", False):
scores["CLI"] += 5
if getattr(task, "code_mode_configured", False):
scores["MCP"] += 4
if getattr(task, "has_error", False):
scores["TROUBLESHOOT"] += 4
return scores
def select_intents(scores: dict[str, float], ambiguity_delta: float = 1.0, max_intents: int = 2) -> list[str]:
ranked = sorted(scores.items(), key=lambda item: item[1], reverse=True)
if not ranked or ranked[0][1] <= 0:
return ["CLI"]
selected = [ranked[0][0]]
if len(ranked) > 1 and ranked[1][1] > 0 and (ranked[0][1] - ranked[1][1]) <= ambiguity_delta:
selected.append(ranked[1][0])
return selected[:max_intents]
def route_chrome_devtools_resources(task):
inventory = discover_markdown_resources()
intents = select_intents(score_intents(task), ambiguity_delta=1.0)
loaded = []
seen = set()
def load_if_available(relative_path: str) -> None:
guarded = _guard_in_skill(relative_path)
if guarded in inventory and guarded not in seen:
load(guarded)
loaded.append(guarded)
seen.add(guarded)
for relative_path in LOADING_LEVELS["ALWAYS"]:
load_if_available(relative_path)
for intent in intents:
for relative_path in RESOURCE_MAP.get(intent, []):
load_if_available(relative_path)
text = _task_text(task)
if any(keyword in text for keyword in LOADING_LEVELS["ON_DEMAND_KEYWORDS"]):
for relative_path in LOADING_LEVELS["ON_DEMAND"]:
load_if_available(relative_path)
if not loaded:
load_if_available(DEFAULT_RESOURCE)
return {"intents": intents, "resources": loaded}
<!-- /ANCHOR:smart-routing-references --> <!-- ANCHOR:how-it-works -->
3. HOW IT WORKS
Tool Comparison
| Feature | CLI (bdg) | MCP (Code Mode) | Puppeteer/Playwright |
| -------------- | ---------------------------------- | -------------------- | -------------------- |
| Setup | npm install -g browser-debugger-cli@alpha | MCP config + server | Heavy dependencies |
| Discovery | --list, --describe, --search | search_tools() | API docs required |
| Token Cost | Lowest (self-doc) | Medium (progressive) | Highest (verbose) |
| CDP Access | 300+ methods across 53 domains | MCP-exposed subset | Full but complex |
| Best For | Debugging, inspection | Multi-tool workflows | Complex UI testing |
CLI Approach (Priority) - browser-debugger-cli (bdg)
Installation & Verification
# Check if installed
command -v bdg || echo "Install: npm install -g browser-debugger-cli@alpha"
# Installation
npm install -g browser-debugger-cli@alpha
# Verify
bdg --version 2>&1
MCP Approach (Fallback) - Chrome DevTools via Code Mode
When CLI unavailable or multi-tool integration needed.
Prerequisites
- Code Mode configured in
.utcp_config.json - Chrome DevTools MCP server registered with
--isolated=true
Isolated Instances
Key Feature: MCP uses --isolated=true flag for independent browser instances.
Benefits of isolated instances:
- Each instance runs in its own browser process
- Multiple parallel browser sessions possible
- No session conflicts between instances
- Register multiple instances for parallel testing (e.g.,
chrome_devtools_1,chrome_devtools_2)
Configuration example (.utcp_config.json):
{
"manual_call_templates": [
{
"name": "chrome_devtools_1",
"call_template_type": "mcp",
"config": {
"mcpServers": {
"chrome_devtools_1": {
"transport": "stdio",
"command": "npx",
"args": ["chrome-devtools-mcp@latest", "--isolated=true"],
"env": {}
}
}
}
},
{
"name": "chrome_devtools_2",
"call_template_type": "mcp",
"config": {
"mcpServers": {
"chrome_devtools_2": {
"transport": "stdio",
"command": "npx",
"args": ["chrome-devtools-mcp@latest", "--isolated=true"],
"env": {}
}
}
}
}
]
}
Configuration Check
cat .utcp_config.json | jq '.manual_call_templates[] | select(.name | startswith("chrome_devtools"))'
Invocation Pattern
Tool naming: {manual_name}.{manual_name}_{tool_name}
Single instance example:
await call_tool_chain({
code: `
await chrome_devtools_1.chrome_devtools_1_navigate_page({
url: "https://example.com"
});
const screenshot = await chrome_devtools_1.chrome_devtools_1_take_screenshot({});
return screenshot;
`,
timeout: 30000
});
Parallel instances example (comparing two pages):
await call_tool_chain({
code: `
// Instance 1: Production site
await chrome_devtools_1.chrome_devtools_1_navigate_page({
url: "https://example.com"
});
// Instance 2: Staging site (parallel)
await chrome_devtools_2.chrome_devtools_2_navigate_page({
url: "https://staging.example.com"
});
// Capture both screenshots
const prod = await chrome_devtools_1.chrome_devtools_1_take_screenshot({});
const staging = await chrome_devtools_2.chrome_devtools_2_take_screenshot({});
return { production: prod, staging: staging };
`,
timeout: 60000
});
Available MCP Tools
| Tool | Purpose | CLI Equivalent |
| ----------------------- | ------------------ | -------------------------------------------------- |
| navigate_page | Navigate to URL | bdg <url> |
| take_screenshot | Capture screenshot | bdg dom screenshot |
| list_console_messages | Get console logs | bdg console --list |
| resize_page | Set viewport size | N/A (use cdp) |
| click | Click on element | bdg cdp Input.dispatchMouseEvent |
| fill | Fill form field | bdg dom eval "document.querySelector(...).value = ..." |
| hover | Hover over element | bdg cdp Input.dispatchMouseEvent |
| press_key | Press keyboard key | bdg cdp Input.dispatchKeyEvent |
| wait_for | Wait for condition | N/A (scripting) |
| new_page | Open new page | N/A |
| close_page | Close page | bdg stop |
| select_page | Switch to page | N/A |
Note: Tool names use underscores (e.g., take_screenshot) not camelCase.
Full invocation pattern: {manual_name}.{manual_name}_{tool_name}()
- Example:
chrome_devtools_1.chrome_devtools_1_take_screenshot({})
When to Prefer MCP
- Already using Code Mode for other tools (Webflow, Figma, etc.)
- Need to chain browser operations with other MCP tools
- Parallel browser testing - compare multiple sites/viewports simultaneously
- Complex multi-step automation in TypeScript
- Type-safe tool invocation required
MCP Limitations
- Higher token cost than CLI
- Requires Code Mode infrastructure
- Subset of CDP methods (CLI has 300+ methods across 53 domains)
- Less self-documenting than CLI's
--list,--describe
MCP Session Cleanup
Important: Always close browser instances when done to prevent resource leaks.
// Cleanup pattern for MCP sessions
await call_tool_chain({
code: `
try {
// Your browser operations
await chrome_devtools_1.chrome_devtools_1_navigate_page({
url: "https://example.com"
});
const screenshot = await chrome_devtools_1.chrome_devtools_1_take_screenshot({});
return screenshot;
} finally {
// Always close the page when done
await chrome_devtools_1.chrome_devtools_1_close_page({});
}
`,
timeout: 30000
});
// For multi-instance cleanup
await call_tool_chain({
code: `
try {
// Operations on multiple instances...
} finally {
// Close all instances
await chrome_devtools_1.chrome_devtools_1_close_page({});
await chrome_devtools_2.chrome_devtools_2_close_page({});
}
`,
timeout: 30000
});
Best Practice: Wrap browser operations in try/finally to ensure cleanup even on errors.
<!-- /ANCHOR:how-it-works --> <!-- ANCHOR:rules -->
4. RULES
✅ ALWAYS Rules
ALWAYS do these without asking:
-
ALWAYS check CLI availability first
- Run
command -v bdgbefore any operation - Prefer CLI over MCP when available
- Run
-
ALWAYS verify bdg installation before first use
command -v bdg || echo "Install: npm install -g browser-debugger-cli@alpha"
-
ALWAYS use discovery commands when exploring
- Start with
--list,--describe,--search - Document how you found the method
- Start with
-
ALWAYS verify session status before CDP commands
bdg status 2>&1 | jq '.state'
-
ALWAYS capture stderr with
2>&1- Essential for error handling
- All bdg commands should include this
-
ALWAYS stop sessions after operations
bdg stop 2>&1or use trap pattern
-
ALWAYS use jq for JSON processing
- Avoid string manipulation on JSON output
❌ NEVER Rules
NEVER do these:
-
NEVER execute CDP commands without verifying session
- Session must be
activestate first
- Session must be
-
NEVER hardcode CDP method lists
- Use self-discovery instead
- Methods change between versions
-
NEVER skip error handling
- Always use
2>&1pattern - Check exit codes in scripts
- Always use
-
NEVER leave sessions running
- Cleanup with
bdg stopor trap - Browser processes consume resources
- Cleanup with
-
NEVER assume method names
- Verify with
--describefirst - Method signatures vary
- Verify with
-
NEVER use on Windows without WSL
- PowerShell/Git Bash not supported
⚠️ ESCALATE IF
Ask user when:
-
ESCALATE IF bdg not installed on Windows
- WSL required for Windows support
- Ask if they have WSL configured
-
ESCALATE IF Chrome/Chromium not found
- May need
CHROME_PATHenvironment variable - Ask user to specify browser location
- May need
-
ESCALATE IF session fails after 3 retries
- May indicate deeper issue
- Ask about browser permissions/sandbox
-
ESCALATE IF task requires cross-browser testing
- bdg is Chrome-only
- Suggest Puppeteer/Playwright for cross-browser
-
ESCALATE IF complex UI testing needed
- May be better suited for Puppeteer/Playwright
- Ask about test framework preferences
<!-- /ANCHOR:rules --> <!-- ANCHOR:success-criteria -->
5. SUCCESS CRITERIA
Browser Debugging Completion Checklist
Workflow complete when:
- ✅ CLI vs MCP approach selected based on availability
- ✅ bdg installation verified (or MCP configured)
- ✅ Session started successfully (
activestate) - ✅ CDP operations executed (exit code 0, valid JSON)
- ✅ Required data captured (screenshot, logs, cookies, etc.)
- ✅ Session stopped and cleaned up
- ✅ Output provided to user
- ✅ Error handling implemented (stderr captured)
- ✅ Method discovery documented
Quality Targets
- Session startup: < 5 seconds
- Screenshot capture: < 2 seconds
- Console log retrieval: < 1 second
- Error rate: 0% (all errors handled gracefully)
<!-- /ANCHOR:success-criteria --> <!-- ANCHOR:integration-points -->
6. INTEGRATION POINTS
Framework Integration
This skill operates within the behavioral framework defined in AGENTS.md.
Key integrations:
- Gate 2: Skill routing via
skill_advisor.py - Tool Routing: Per AGENTS.md Section 6 decision tree
- Memory: Context preserved via Spec Kit Memory MCP
Related Skills
mcp-code-mode: Required for MCP fallback approach
- When CLI unavailable, Code Mode provides alternative
- Tool naming:
{manual_name}.{manual_name}_{tool_name}
sk-code--web: Phase 3 browser testing integration
- Use bdg for verification during implementation
- Example integration pattern:
npm run dev & sleep 5 bdg http://localhost:3000 2>&1 bdg dom screenshot verification.png 2>&1 bdg console --list 2>&1 > console.json bdg stop 2>&1
Tool Usage Guidelines
Bash: All bdg commands, session management, error handling Read: Load reference files when detailed guidance needed Grep: Filter command output, search logs Glob: Find screenshot files, locate HAR exports
External Tools
browser-debugger-cli (bdg):
- Installation:
npm install -g browser-debugger-cli@alpha - Purpose: Primary CLI for browser debugging
- Fallback: Use MCP via Code Mode if unavailable
Chrome/Chromium:
- Installation: System package manager or direct download
- Purpose: Browser runtime for CDP connection
- Fallback: Set
CHROME_PATHif not auto-detected
<!-- /ANCHOR:integration-points --> <!-- ANCHOR:quick-reference -->
7. QUICK REFERENCE
Essential CLI Commands
# Discovery
bdg cdp --list # List domains
bdg cdp --describe Page # Domain methods
bdg cdp --search screenshot # Find methods
# Session
bdg <url> # Start
bdg status # Check
bdg stop # Stop
# Helpers
bdg dom screenshot <path> # Screenshot
bdg console --list # Console
bdg network getCookies # Cookies
bdg dom query "<sel>" # DOM query
bdg dom eval "<expr>" # Execute JS
bdg network har <path> # HAR export
MCP Tools (Isolated Instances)
// Tool naming: {instance}.{instance}_{tool_name}
// Each instance runs isolated browser (--isolated=true)
// Instance 1
chrome_devtools_1.chrome_devtools_1_navigate_page({ url: "..." })
chrome_devtools_1.chrome_devtools_1_take_screenshot({})
chrome_devtools_1.chrome_devtools_1_list_console_messages({})
// Instance 2 (parallel testing)
chrome_devtools_2.chrome_devtools_2_navigate_page({ url: "..." })
chrome_devtools_2.chrome_devtools_2_take_screenshot({})
Error Handling Pattern
#!/bin/bash
trap "bdg stop 2>&1" EXIT INT TERM
command -v bdg || { echo "Install bdg first"; exit 1; }
bdg "$URL" 2>&1 || exit 1
# ... operations ...
<!-- /ANCHOR:quick-reference -->