browsing
Use when you need direct browser control - teaches Chrome DevTools Protocol for controlling existing browser sessions, multi-tab management, form automation, and content extraction via use_browser MCP tool
By obra · 401 installs
npx skills add obra/superpowers-chrome --skill browsing
Source repository · Upstream listing
Browsing with Chrome Direct
Overview
Control Chrome via DevTools Protocol using the use browser MCP tool. Single unified interface with auto starting Chrome.
Announce: "I'm using the browsing skill to control Chrome."
When to Use
Use this when:
Controlling authenticated sessions
Managing multiple tabs in running browser
Playwright MCP unavailable or excessive
Use Playwright MCP when:
Need fresh browser instances
Generating screenshots/PDFs
Prefer higher level abstractions
Auto Capture
Every DOM action (navigate, click, type, select, eval, keyboard press, hover, drag drop, double click, right click, file upload) automatically saves:
{prefix}.png — viewport screenshot
{prefix}.md — page content as structured markdown
{prefix}.html — full rendered DOM
{prefix} console.txt — browser console messages
Files are saved to the session directory with sequential prefixes (001 navigate, 002 click, etc.). You must check these before using extract or screenshot actions.
The use browser Tool
Single MCP tool with action based interface. Chrome auto starts on first use.
Parameters:
action (required): Operation to perform
selector (optional): CSS or XPath selector for element operations
payload (optional): Action specific data (string or object)
timeout (optional): Timeout in ms for await operations (default: 5000)
Active tab : Every action operates on the current activeTab . Use switch tab to change it.
Actions Reference
Navigation
navigate : Navigate to URL
payload : URL string
Example: {action: "navigate", payload: "https://example.com"}
await element : Wait for element to appear
selector : CSS selector
timeout : Max wait time in ms
Example: {action: "await element", selector: ".loaded", timeout: 10000}
await text : Wait for text to appear
payload : Text to wait for
Example: {action: "await text", payload: "Welcome"}
Interaction
click : Click element
selector : CSS selector
Example: {action: "click", selector: "button.submit"}
type : Text input
selector : Optional — clicks to focus first
payload : Text to type ( \t =Tab, \n =Enter)
Example: {action: "type", selector: " email", payload: "user@example.com"}
double click : Double click element (fires dblclick event)
selector : CSS selector
Example: {action: "double click", selector: ".item"}
right click : Right click element (fires contextmenu event)
selector : CSS selector
Example: {action: "right click", selector: ".row"}
select : Select dropdown option
selector : CSS selector
payload : Option value(s)
Example: {action: "select", selector: "select[name=state]", payload: "CA"}
keyboard press : Press special keys (Tab, Enter, Escape, Arrow keys, F1 F12)
payload : Key name (string) or {"key": "Tab", "modifiers": {"shift": true, "ctrl": false, "alt": false, "meta": false}}
Example: {action: "keyboard press", payload: "Tab"}
Example with modifiers: {action: "keyboard press", payload: {"key": "Tab", "modifiers": {"shift": true}}}
Mouse Actions (CDP Level)
These use CDP Input.dispatchMouseEvent, bypassing synthetic event restrictions.
hover : Move mouse over element (CSS :hover, tooltips, menus)
selector : CSS selector
Example: {action: "hover", selector: ".menu trigger"}
drag drop : Drag element to target (native drag and drop via CDP)
selector : Source element
payload : Target selector or JSON coordinates {"x":N,"y":N}
Example: {action: "drag drop", selector: ".card", payload: ".column 2"}
mouse move : Move mouse to coordinates
payload : JSON {"x":N,"y":N} (optional: steps , fromX , fromY for smooth movement)
Example: {action: "mouse move", payload: "{\"x\":100,\"y\":200}"}
scroll : Scroll via mouse wheel events
payload : Direction (up/down/left/right) or JSON {"deltaX":N,"deltaY":N}
selector : Optional — scroll within element
Example: {action: "scroll", payload: "down"}
File Upload
file upload : Set files on input[type=file] elements (can't be done via JavaScript)
selector : File input element
payload : File path or JSON {"files":["/path/a.pdf","/path/b.jpg"]}
Example: {action: "file upload", selector: " upload", payload: "/tmp/doc.pdf"}
Extraction
extract : Get page content
payload : Format ('markdown' 'text' 'html')
selector : Optional limit to element
Example: {action: "extract", payload: "markdown"}
Example: {action: "extract", payload: "text", selector: "h1"}
attr : Get element attribute
selector : CSS selector
payload : Attribute name
Example: {action: "attr", selector: "a.download", payload: "href"}
eval : Execute JavaScript
payload : JavaScript code
Example: {action: "eval", payload: "document.title"}
Export
screenshot : Capture screenshot of a specific element
payload : Filename
selector : Optional screenshot specific element
Viewport screenshots are auto captured after every DOM action. Use this only when you need a specific element.
Example: {action: "screenshot", payload: "/tmp/chart.png", selector: ".chart"}
Tab Management
list tabs : List all open tabs
Example: {action: "list tabs"}
new tab : Create new tab
Example: {action: "new tab"}
close tab : Close the active tab
Example: {action: "close tab"}
switch tab : Switch the active tab (sticky — stays until changed)
payload : Tab index (number), URL substring, or title substring
Example: {action: "switch tab", payload: 1} (by index)
Example: {action: "switch tab", payload: "example.com"} (by URL substring)
Example: {action: "switch tab", payload: "GitHub"} (by title substring)
Browser Mode Control
show browser : Make browser window visible (headed mode)
Example: {action: "show browser"}
⚠️ WARNING : Restarts Chrome, reloads pages via GET, loses POST state
hide browser : Switch to headless mode (invisible browser)
Example: {action: "hide browser"}
⚠️ WARNING : Restarts Chrome, reloads pages via GET, loses POST state
browser mode : Check current browser mode, port, and profile
Example: {action: "browser mode"}
Returns: {"headless": true false, "mode": "headless" "headed", "running": true false, "port": 9222, "profile": "name", "profileDir": "/path"}
Profile Management
set profile : Change Chrome profile (must kill Chrome first)
Example: {action: "set profile", "payload": "browser user"}
⚠️ WARNING : Chrome must be stopped first
Side effect : marks the profile as explicit, opting out of auto disambiguation (see below)
get profile : Get current profile name and directory
Example: {action: "get profile"}
Returns: {"profile": "name", "profileDir": "/path"}
Default behavior : Chrome starts in headless mode with "superpowers chrome" profile on a dynamically allocated port (range 9222 12111). Override the port with CHROME WS PORT ; override the profile with CHROME WS PROFILE .
Auto disambiguation across parallel MCPs :
When two MCP servers start on the same host with the default profile, the first claims superpowers chrome (port 9222) and later ones silently fall through to superpowers chrome 2 (port 9223), superpowers chrome 3 , etc. Each MCP drives its own Chrome with its own profile dir; they don't fight over activeTab . The bridge tracks ownership via a lock file at ~/.cache/superpowers/browser profiles/<profile .mcp.lock ; stale locks (dead PIDs) are reclaimed automatically.
To opt out of disambiguation — e.g., to intentionally share Chrome between a long lived chrome ws CLI session and your MCP — set the profile name explicitly:
Env var: CHROME WS PROFILE=my profile
Or: {action: "set profile", payload: "my profile"} at runtime
An explicit profile name still acquires the lock, but on conflict the bridge shares rather than disambiguates — the second process reconnects to the first's Chrome (the original reconnect on restart behavior).
Chrome Lifecycle (Recovery)
kill chrome : Kill the Chrome process this MCP is driving
Example: {action: "kill chrome"}
Releases the meta.json; next page action auto restarts Chrome
restart chrome : kill chrome + immediate spawn
Example: {action: "restart chrome"}
Auto restart banner : when the bridge has to spawn a fresh Chrome (because the previous one died or was killed externally — e.g., kill 9 <pid from the shell), the first response after the restart prepends:
Treat this as a signal that your prior URL / tab state is gone — re navigate before assuming anything about the current page.
Console Logging
Capture browser console output for the active tab. Buffer is keyed by the page session's sessionId , so it survives close tab / new tab ordering quirks. Levels: log , info , warn , error .
enable console logging : Start capturing
Example: {action: "enable console logging"}
get console messages : Read captured messages
All: {action: "get console messages"}
Since timestamp (epoch ms): {action: "get console messages", payload: {since: 1716000000000}}
Returns: array of {timestamp, level, text} entries
clear console messages : Reset the buffer
Example: {action: "clear console messages"}
Dialog Handling
Native dialogs (JS alert/confirm/prompt, beforeunload, HTTP basic auth, permission prompts, device choosers) pause the page. While a dialog is open, page targeted actions ( extract , click , eval , etc.) return a refusal whose text contains Page is behind a dialog and lists the available dialog:: selectors.
When a dialog fires during a navigate (typical for HTTP basic auth), navigate itself throws with the dialog grammar in the message — you don't have to issue a separate page targeted call to discover the dialog.
Handle dialogs by clicking/typing a dialog:: selector:
{action: "click", selector: "dialog::accept"} — accept JS alert/confirm/prompt, beforeunload, permission grant
{action: "click", selector: "dialog::dismiss"} — dismiss / cancel / deny
{action: "type", selector: "dialog::prompt", payload: "text"} then accept — respond to JS prompt
{action: "type", selector: "dialog::username", payload: "alice"} + {action: "type", selector: "dialog::password", payload: "secret"} + {action: "click", selector: "dialog::accept"} — HTTP basic auth
{action: "click", selector: "dialog::device[id=\"<deviceId \"]"} — pick a WebUSB/Bluetooth/Serial/HID device
Critical caveats when toggling modes :
1. Chrome must restart Cannot switch headless/headed mode on running Chrome
2. Pages reload via GET All open tabs are reopened with GET requests
3. POST state is lost Form submissions, POST results, and POST based navigation will be lost
4. Session state is lost Any client side state (JavaScript variables, etc.) is cleared
5. Cookies/auth may persist Uses same user data directory, so logged in sessions may survive
When to use headed mode :
Debugging visual rendering issues
Demonstrating browser behavior to user
Testing features that only work with visible browser
Debugging issues that don't reproduce in headless mode
When to stay in headless mode (default):
All other cases faster, cleaner, less intrusive
Screenshots work perfectly in headless mode
Most automation works identically in both modes
Profile management :
Profiles store persistent browser data (cookies, localStorage, extensions, auth sessions).
Profile locations :
macOS: ~/Library/Caches/superpowers/browser profiles/{name}/
Linux: ~/.cache/superpowers/browser profiles/{name}/
Windows: %LOCALAPPDATA%/superpowers/browser profiles/{name}/
When to use separate profiles :
Default profile ("superpowers chrome") : General a