About my-agent-browser
my-agent-browser is a skill that guides AI agents through browser automation using the chrome-devtools-mcp server. It covers setup, page interaction, scraping, diagnostics, and recovery, including workflows for large pages.
Key features
- 01Browser navigation and interaction
- 02Accessibility snapshots and screenshots
- 03Forms, dialogs, and file uploads
- 04Network, console, and Lighthouse inspection
- 05Guidance for large pages and recovery
Use cases
- 01Automating sign-in and other browser flows
- 02Extracting content from structured pages
- 03Inspecting requests and browser errors
- 04Handling large or dynamic pages
MCP support
The skill uses the chrome-devtools-mcp MCP server and documents its installation and registration with an agent or IDE. It describes the server's browser tools and their use.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ briqt/ my-agent-browser --skill my-agent-browserAfter installing, type/my-agent-browserin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
config.example.jsonconfig.schema.jsonreferences/advanced-tools.mdanti-detection.mddependency-upgrades.mdjavascript-execution.mdmulti-tab-workflow.mdnetwork-debugging.mdscraping-patterns.mdsetup.mdtroubleshooting.mdreferences/domain/academic-paper-download.mdscripts/docs.test.jsmcp-session.jsmcp-session.test.jsmigrate-config.jsstart-mcp.jsstart-mcp.test.jsstealth-e2e.jsDetails
From the project READMEmy-agent-browser
Browser automation for AI agents via chrome-devtools-mcp MCP server.
Release 1.2.0, tested with chrome-devtools-mcp 1.11.0. Node 24 LTS is recommended. Check the installed wrapper without launching Chrome:
node <skill-dir>/scripts/start-mcp.js --doctor --json
The report identifies the wrapper, Node, Chrome, MCP binary and config paths. If the wrapper and installed skill differ, reinstall this skill before troubleshooting the browser.
Setup Check
<skill-dir> refers to the directory containing this SKILL.md (typically ~/.agents/skills/my-agent-browser/ or ~/.claude/skills/my-agent-browser/ depending on your agent).
If browser MCP tools (navigate_page, take_snapshot, click, fill) are not available in your tool list:
-
Install:
npm install -g [email protected] -
Create config:
mkdir -p ~/.config/agent-skills/my-agent-browser cp <skill-dir>/config.example.json ~/.config/agent-skills/my-agent-browser/config.json -
Register the MCP server in your agent/IDE. The MCP server entry is:
- Name:
browser - Command:
node - Args:
["<skill-dir>/scripts/start-mcp.js"](resolve<skill-dir>to the actual absolute path)
How to register depends on your environment — consult your agent's
/help, official docs, or settings UI to find where MCP servers are configured. - Name:
-
Restart the agent session
Core Workflow
list_pagesornew_page { url }— get a pageId (required on every page-scoped tool)navigate_page { pageId, url }— go to a page (skip if you just usednew_page)take_snapshot { pageId }— read the page structure with uid refsclick { pageId, uid }/fill { pageId, uid, value }/press_key { pageId, key }— interacttake_snapshot { pageId }again — uids change after every page mutation
Reading Snapshots
take_snapshot returns an indented accessibility tree:
uid=1_0 RootWebArea "Sign in" url="https://example.com/login"
uid=1_5 heading "Sign in" level="1"
uid=1_7 textbox "Email" focusable required
uid=1_9 textbox "Password" required
uid=1_12 button "Sign in"
uid=1_14 link "Forgot password?" url="..."
Each uid=X_Y is the identifier you pass to click, fill, hover, etc.
Available Tools
Navigation
navigate_page { pageId, url }— Go to URLnew_page { url }— Open new tab (returns its pageId)list_pages— List all tabs with pageIdsselect_page { pageId }— Focus a tab (does not replace passing pageId on later calls)close_page { pageId }— Close tab
Reading
take_snapshot { pageId }— Accessibility tree with uid refstake_screenshot { pageId }— Capture page image
Interaction
click { pageId, uid }— Click elementfill { pageId, uid, value }— Clear field and type valuefill_form { pageId, elements: [{uid, value}] }— Fill multiple fieldstype_text { pageId, text }— Type at current focus (no clear, no target)press_key { pageId, key }— Press key (Enter, Tab, Escape, ArrowDown, etc.)hover { pageId, uid }— Hover over elementdrag { pageId, from_uid, to_uid }— Drag between elementsupload_file { pageId, uid, filePath }— Upload file to inputhandle_dialog { pageId, action }— Accept/dismiss dialog
Utility
evaluate_script { pageId, function }— Execute JavaScriptwait_for { pageId, text[] }— Wait for text to appearresize_page { pageId, width, height }— Change viewport
Advanced Tools
These tool groups are available by default — no flags needed. See references/advanced-tools.md for detailed workflows.
- Performance:
performance_start_trace/performance_stop_trace/performance_analyze_insight— traces, Core Web Vitals - Network:
list_network_requests/get_network_request— inspect requests/responses - Lighthouse:
lighthouse_audit— audits (navigation/snapshot, desktop/mobile) - Console:
list_console_messages/get_console_message— browser console - Emulation:
emulate— throttle network/CPU, geolocation, color scheme
mcp.flags in config is a passthrough to chrome-devtools-mcp. Use it to shrink the tool set (--no-categoryNetwork / --no-categoryPerformance / --no-categoryEmulation, or --slim for 3 tools) or to add opt-in groups (--memoryDebugging / --experimentalMemory for heap-snapshot analysis, --categoryExtensions, --experimentalVision for coordinate click_at). Authoritative flag list: npx [email protected] --help.
Key Rules
- Always pass
pageId— chrome-devtools-mcp requires it on page-scoped tools (click,fill,take_snapshot,navigate_page,evaluate_script,wait_for, …). Get it fromlist_pagesornew_page.select_pageonly focuses the tab; later calls still needpageId. - UIDs are ephemeral — After any navigation or interaction that changes the page, previous UIDs are invalid. Always
take_snapshot { pageId }again before the next interaction. - Use
fillfor inputs — It targets a specific element and clears first.type_texttypes at whatever is focused, which is fragile. - One action, then re-read — Don't batch multiple actions without re-snapshotting. The first action may invalidate subsequent UIDs.
- Heavy pages: use file-based snapshots — See below.
Heavy Pages (Critical)
Pages with many DOM nodes (rich-text editors, large tables, chat histories, admin dashboards) will crash or hang if you use includeSnapshot: true or wait_for on them.
Symptoms: browser unresponsive, "target closed", repeated timeouts after injecting content.
Solution:
- Use
includeSnapshot: false(or omit) forclick,fill,hoveron heavy pages - Save snapshot to file:
take_snapshot { pageId, filePath: "/tmp/snap.txt" } - Read only what you need:
tail -100 /tmp/snap.txt(dialogs/modals are at the end) - Close unrelated tabs — each holds its DOM in memory
When to expect this: WYSIWYG editors after injecting content, pages with 200+ repeating elements, infinite scroll pages after several scrolls. Switch to file-based workflow proactively before the crash, not after.
Scraping Patterns
Simple: snapshot is enough
Navigate → wait_for { pageId, text } → take_snapshot { pageId } → read text/links from the accessibility tree directly. No JS needed for most structured pages.
Paginated: prefer URL-based
Loop navigate_page { pageId, url: "...?page=N" } instead of clicking Next buttons. More reliable, avoids stale UIDs, easy to resume if interrupted.
Dynamic/lazy-loaded content
press_key { pageId, key: "End" } to trigger lazy load → wait_for { pageId, text } known content → take_snapshot { pageId }.
Complex extraction: evaluate_script
When the a11y tree doesn't capture table row/column relationships or deeply nested data, extract with JS:
evaluate_script { pageId, function: "() => JSON.stringify([...document.querySelectorAll('tr')].map(r => [...r.cells].map(c => c.textContent.trim())))" }
Login-gated content
Option A: persistent profile — log in once with headless: false, then reuse userDataDir.
Option B: automated — fill credentials via fill + click + wait_for.
Option C: connect to existing session — set browserUrl in config.
Multi-Tab Patterns
new_page { url }opens a tab and returns its pageId — use that pageId on subsequent calls, don't guess- Pass the target tab's pageId on every call.
select_pageis only for focusing/bringing to front - "Open in new tab, extract, close, return" pattern avoids losing your place on listing pages
- Each tab is independent — snapshots, UIDs, and page state don't cross tabs
JavaScript Execution Tips
evaluate_scriptruns in the browser page context (hasdocument,window, page libraries)- Return values must be JSON-serializable — use
JSON.stringify()for objects/arrays - Can return Promises (useful for polling/waiting patterns)
- DOM changes persist — after modifying the page, retake snapshot for fresh UIDs
- Common uses: scroll, extract structured data, remove overlays, trigger lazy load, read computed styles
Error Recovery
- Element not found after click → page changed,
take_snapshot { pageId }again, find new UID wait_fortimeout → page didn't load expected content,take_snapshot { pageId }to see actual state- MCP stale state → the wrapper replaces and initializes its MCP child. In-flight calls fail explicitly and are never replayed. After recovery,
list_pagesand take a fresh snapshot; verify the result before retrying an action that changes state. - Chrome crashed / "target closed" → in managed lazy mode Chrome is relaunched;
list_pagesthen navigate again with the new pageId - Anti-bot detection → check the WebGL renderer and your exit IP first, not Chrome flags. See references/anti-detection.md
Example: Login Flow
1. list_pages → pageId (or new_page { url: "https://app.example.com/login" })
2. navigate_page { pageId, url: "https://app.example.com/login" } — skip if new_page already loaded it
3. take_snapshot { pageId }
→ uid=1_7 textbox "Email", uid=1_9 textbox "Password", uid=1_12 button "Sign in"
4. fill_form { pageId, elements: [{uid: "1_7", value: "[email protected]"}, {uid: "1_9", value: "secret123"}] }
5. take_snapshot { pageId } → find the current Sign in button, e.g. uid=2_12
6. click { pageId, uid: "2_12" }
7. wait_for { pageId, text: ["Dashboard"] }
8. take_snapshot { pageId } → now on dashboard, new uids
Troubleshooting Quick Reference
| Problem | Cause | Fix |
|---|---|---|
| Click/fill does nothing | Stale UIDs | take_snapshot { pageId } again, use fresh UIDs |
Required at pageId | pageId omitted | list_pages / new_page, then pass that pageId on every page-scoped call |
| Element not in snapshot | Below fold / iframe / async | Scroll first, or wait_for { pageId }, or evaluate_script { pageId } to check |
wait_for timeout | Text never appeared | take_snapshot { pageId } to see actual state |
| Chrome not starting | Not installed or port in use | Check which google-chrome, check port conflict |
| Snapshot empty/minimal | JS-rendered content not ready | wait_for before snapshot |
| Memory overflow / crash | Heavy DOM | File-based snapshots (see above) |
| Bot detection | Usually WebGL renderer or exit IP, rarely flags | See references/anti-detection.md |
For detailed troubleshooting: references/troubleshooting.md For network debugging workflows: references/network-debugging.md For advanced scraping patterns: references/scraping-patterns.md
Domain-Specific Workflows
- Academic paper download (CNKI, Google Scholar, MDPI): references/domain/academic-paper-download.md
Navigation and script recovery
- Refresh or navigate with
navigate_page { pageId, type: "reload" }/{ pageId, url }. A script that callslocation.reload()and then awaits work can lose its execution context; perform the navigation and subsequent inspection as separate calls. evaluate_script.functionis a JavaScript function, e.g."() => document.title". Do not pass a bare expression or TypeScript syntax such as(window as any).No page foundmeans rediscover IDs withlist_pages; do not repeatedly retry the old ID.- For HTTP extraction, read
statusand the response text before parsing JSON. Empty responses and HTML error pages need separate handling; a JSON parse error alone does not establish that a requested change failed.
When upgrading browser dependencies, read dependency-upgrades.md for the tested version, runtime requirements and changed file/tool behavior.