Browser automation using browser MCP tools for web scraping, form filling, testing, and security auditing. Controls a real Chromium through a 10-tool gateway that reaches ~98 underlying tools — navigation, element interaction, DOM querying, tab management, network monitoring, and JavaScript execution. Use when asked to browse, scrape, fill a form, automate a website, check a page, open a browser, take a screenshot, monitor network requests, run a security audit, bypass a Cloudflare challenge, or interact with any web page programmatically. Features a token-optimized DOM walker that reduces HTML to compact JSON with numeric IDs, plus Cloudflare Turnstile solving and fingerprint (user-agent/locale/timezone/geo) overrides.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Browser automation using browser MCP tools for web scraping, form filling, testing, and security auditing. Controls a real Chromium through a 10-tool gateway that reaches ~98 underlying tools — navigation, element interaction, DOM querying, tab management, network monitoring, and JavaScript execution. Use when asked to browse, scrape, fill a form, automate a website, check a page, open a browser, take a screenshot, monitor network requests, run a security audit, bypass a Cloudflare challenge, or interact with any web page programmatically. Features a token-optimized DOM walker that reduces HTML to compact JSON with numeric IDs, plus Cloudflare Turnstile solving and fingerprint (user-agent/locale/timezone/geo) overrides.
Use when the user wants to browse a website, scrape web content, fill out forms, automate browser interactions, take screenshots, run security audits, or interact with any web page. Examples: 'open this URL', 'scrape that page', 'fill out the form', 'take a screenshot', 'check this website', 'automate the login', 'run a security audit', 'monitor network requests'.
argument-hint
[url or task description]
context
fork
metadata
{"author":"coffeegrind123","version":"1.4"}
Browser Automation
The server runs in GATEWAY mode — only 10 tools are directly callable
Every other capability — screenshots, forms, cookies, localStorage, tabs,
network/console logs, JS execution, scrolling, stealth/user-agent, geolocation,
security audit, Cloudflare — exists on the server but is hidden to keep the
context small. Reach it in one hop:
search_tools(query="read cookies") → ranked 'name(params) — summary' lines
describe_tool(name="set_cookie") → full schema, when the summary is not enough
call_tool(name="set_cookie", arguments={...}) → actually run it
call_tool validates arguments against the real schema, so a mistyped field
returns a clear error rather than failing silently. Pass {} for a no-arg tool.
Throughout this document, any tool NOT in the ten above is written by its bare
name for readability — invoke it as call_tool(name="<that name>", arguments={…}).
If you are unsure a tool exists, search_tools is cheaper than guessing.
No tool works until start_browser() completes. No page tools work until
navigate() completes. Always stop_browser() when finished.
navigate() settles the page before it returns — do not add a wait after it.
It waits for the network to go quiet and reports how that ended, e.g.
Navigated to https://x/ (network idle after 0.6s, 246 requests). A read
straight afterwards sees rendered content. network still active after 10.0s is
normal for a page that polls or streams and usually still reads fine. Pass
settle=0 to return the instant navigation commits, when you will not read the
page.
Scenario
Call
Default (headful)
start_browser()
Headless scraping
start_browser(headless=true)
With proxy
start_browser(proxy="socks5://host:port")
Authenticated proxy
start_browser(proxy="http://user:pass@host:port")
Persist session
start_browser(user_data_dir="/path/to/profile")
Check if running
get_browser_status()
Primary Discovery: get_interaction_tree()
ALWAYS use get_interaction_tree() as the FIRST step to understand a page.
This is the single most important tool. It returns a token-optimized JSON
of all interactive elements — 96% smaller than raw HTML. Each element gets
a numeric ID you can click/type into directly.
t = type (btn, link, in, sel, chk, rad, tab, mnu, el)
l = label (inferred from aria-label/placeholder/text/title/alt)
r = region (hdr, nav, main, side, ftr, dlg)
v = current value (inputs only)
Use numeric IDs directly:
click(selector="1") — clicks element with id 1
type_text(text="hello", selector="2") — types into element with id 2
Read reference/dom-walker.md if you need the full type/region code reference or label inference rules.
Tool Selection Decision Tree
"I need to find something on the page"
Interactive element (button, link, input)?
YES → get_interaction_tree()
Only buttons? → find_buttons(filter_text="optional")
Only inputs? → find_inputs(filter_type="text|email|...")
NO → Visible text content?
YES → get_text_content()
NO → get_content() [raw HTML — use sparingly, large tokens]
"I need to interact with something"
Click → get_interaction_tree() → click(selector="<id>")
→ OR click(text="Button Text") if you know exact text
Fill form → fill_form(form_data="{\"#email\": \"a@b.com\", \"#pass\": \"123\"}")
NOTE: form_data is a JSON *string*, not an object. Serialize it.
→ OR type_text(text="value", selector="<id>") [core tool, direct]
Dropdown → select_option(selector="<css>", value="option_value")
Upload → upload_file(selector="input[type=file]", file_path="/path")
Submit → submit_form() or press_enter()
"I need to wait for something"
Page load → nothing. navigate() already settled it.
Element appear → wait_for_element(selector="css", timeout=10, visible=true)
Network idle → wait_for_network(timeout=5) # AFTER a click, not after navigate
Specific request → wait_for_request(url_pattern="api/data")
All three real waits are hidden tools — call_tool(name="wait_for_element", …).
That extra hop is exactly why you should not spend one on a page load that
navigate() has already handled.
A blind wait(seconds=N) is the last resort: slower than settling on a fast
page, too short on a slow one. Reach for it only when the thing you are waiting
for is neither an element nor a request — an animation, or a rate limit.
"I need to extract data"
DOM data → execute_js("document.querySelector('.price').textContent")
Visible text → get_text_content()
API response → clear_logs() → [action] → wait_for_request(url_pattern="api/")
Screenshot → screenshot(save_path="/tmp/page.png")
"The page is blocked by Cloudflare"
Probe → is_cloudflare_challenge_present() # fast, no clicking
Solve → bypass_cloudflare(timeout=20) # clicks the Turnstile checkbox
Most Cloudflare-gated sites pass automatically when the browser is headed
(started with start_browser(headless=false)); under --headless=new the
Turnstile is often unsolvable. For an interactive challenge that survives headed
mode, bypass_cloudflare() solves it (wraps zendriver's built-in verify_cf).
set_user_agent / set_locale / set_timezone / set_geolocation align the
fingerprint with a proxy's geo when needed.
Do not pass low_memory=true when stealth matters: its flags (software WebGL,
--disable-gpu) are themselves a bot signal.
When bypass_cloudflare() times out — check for a hard-fail before retrying
bypass_cloudflare() polls for a Turnstile checkbox. If Turnstile has already
fingerprinted the browser and rejected it, it never renders one — it renders a
feedback report instead, and the call can only ever time out. Retrying, raising
the timeout, or re-clicking cannot fix this. Confirm with get_content():
.../auto/failure (and a visible cf-turnstile-feedback wrapper) = fingerprint
rejection. Stop clicking and change approach.
Try a crawler User-Agent first when the goal is public SEO/markup. Many sites
allowlist search crawlers by UA string alone, with no reverse-DNS verification, so
plain curl walks straight past the challenge and returns the real HTML — no
browser, no solver, far faster and more reliable:
Worked on a self-hosted Turnstile gate (2026-07) that hard-failed the automated
browser but served robots.txt / sitemap.xml / full HTML to a Googlebot UA.
Note a self-hosted widget (its own <form action="/verify_captcha"> with a
data-sitekey) is NOT the standard CF edge challenge — the edge-challenge advice
above may not apply to it at all.
Reach for this only for publicly published content (markup, robots, sitemaps)
that the site already serves to crawlers by design — not to get at anything gated,
paid, or private.
General Rules
Autonomy: Execute the full browsing workflow without asking for
confirmation at each step. Ask only when genuinely ambiguous.
Token discipline: Prefer get_interaction_tree() over get_content().
Raw HTML burns context fast. Only use get_content() for HTML structure
the interaction tree doesn't expose.
Do not wait after navigate: navigate() settles the page itself and says
how it ended. Add a wait only after a click or an action that starts new
traffic — wait_for_element() for content an SPA hydrates in late, or
wait_for_network() after a submit.
3a. An empty read tells you which problem you have.get_text_content() on a
page with no text returns [chars 0-0 of 0] followed by the reason:
still loading (read again, or navigate with a bigger settle), no body element, has markup (the text is in an iframe or shadow root — try
get_content or get_interaction_tree), or really is blank. Read that line
before concluding the site blocked you.
Numeric IDs are ephemeral: They change on every get_interaction_tree()
call. Get the tree, use IDs immediately. Never cache across navigations.
CSS selectors for stability: When referencing the same element across
page changes, use a CSS selector instead of a numeric ID.
execute_js syntax: Scripts must NOT start with return. Simple
expressions directly. Complex logic: (function(){ var x = 1; return x; })()
Network monitoring: Call clear_logs() BEFORE the action you want to
monitor, then get_network_logs() or wait_for_request() AFTER.
Tab hygiene: Close tabs opened with new_tab() when done.
Error recovery: If a click or navigation fails, screenshot() to
visually diagnose. The page may have changed or a modal may block.
Auth persistence: Use get_cookies() / set_cookies() for cookie-based auth.
For JWT-based SPAs, use get_local_storage() / set_local_storage() — many
modern apps store tokens in localStorage, not cookies.
Common Workflows
Core tools are called directly; everything else goes through call_tool.
Web Scraping
start_browser(headless=true) → navigate(url) # settled on return
→ get_text_content() # or get_interaction_tree()
→ call_tool(name="execute_js", arguments={"script": "..."})
→ stop_browser()
Form Filling
start_browser() → navigate(url)
→ get_interaction_tree()
→ call_tool(name="fill_form", arguments={"form_data": "{\"#email\": \"user@ex.com\", \"#password\": \"secret\"}"})
→ click(selector="<submit_id>")
→ call_tool(name="wait_for_network", arguments={"timeout": 5}) # the click, not the navigate
→ call_tool(name="screenshot", arguments={"save_path": "/tmp/after.png"})
→ stop_browser()
It names the fix: still loading / no body / markup-but-no-text / blank
Unknown tool name
It is hidden, not absent
search_tools(query="…") then call_tool
Click does nothing
Element not visible
scroll_to_element() then retry
Element not found
Page not loaded yet
wait_for_element() then re-query
Stale numeric ID
Page mutated
Re-call get_interaction_tree()
execute_js fails
Started with return
Remove return, use IIFE
Network logs empty
Didn't clear first
clear_logs() before action
Form submit no effect
Need explicit submit
submit_form() or press_enter()
Interaction tree empty
SPA hydrates after the network settles
wait_for_element() on something you expect, or re-navigate with a larger settle; find_inputs()/find_buttons() as a fallback
Clicks blocked by overlay
Cookie banner or modal
Dismiss banner first: click(text="Accept All")
Critical Pitfalls
❌ Do NOT call any tool before start_browser() — everything will fail
❌ Do NOT use get_content() as primary discovery — wastes tokens massively
❌ Do NOT cache numeric IDs across navigations — they regenerate each call
❌ Do NOT start execute_js scripts with return — use bare expression or IIFE
❌ Do NOT add a wait() after navigate() — it already settled the page, and
under the gateway that wasted wait costs a whole call_tool hop
❌ Do NOT leave tabs open — close with close_tab() when done
❌ Do NOT read network logs without clearing first — stale entries mislead
❌ Do NOT use mouse_click(x,y) unless no other option — coordinates are fragile
❌ Do NOT call get_interaction_tree() then ignore the IDs — that's the whole point
❌ Do NOT interact with elements before dismissing cookie consent banners — they block clicks
❌ Do NOT assume auth is in cookies — check get_local_storage() for JWT tokens too
❌ Do NOT pass timeout to screenshot — it has no such parameter (only
save_path and full_resolution), and call_tool validates arguments, so it
is a hard error. Screenshots really can hang on an unresponsive page; guard
against that by settling the page first (which navigate now does) rather than
by inventing an argument
Self-Refinement Protocol
After completing a browser automation task, if you discovered something
non-obvious (a selector pattern, a timing requirement, a site-specific
quirk), append it to LEARNINGS.md:
## YYYY-MM-DD: <brief title>
- **Context**: What you were doing
- **Learning**: What was non-obvious
- **Rule**: The new rule to follow
Read reference/tools.md if you need full parameter documentation for any tool.
Read reference/patterns.md if you need automation recipes beyond the common workflows above.
Read reference/dom-walker.md if the interaction tree output is unclear or you need type/region code details.