In-depth architectural comparison of the Chromeflow and Firefox Devtools MCP MCP servers. Compare execution transports, security boundaries, tool capabilities, quality scores, and ready-to-paste client installation snippets for Claude, Cursor, Windsurf, and VS Code.
At a Glance & Executive Verdict
Chromeflow
Browser Automation · Local stdio
Quality: 64/100 (Good) | Auth: No auth required
Firefox Devtools MCP
Browser Automation · Local stdio
Quality: 65/100 (Great) | Auth: No auth required
Verdict Summary: Choose Chromeflow if you need specialized Browser Automation tools running via a local process. Choose Firefox Devtools MCP if your workspace requires Browser Automation integration with local subprocess execution. Both servers can be configured concurrently in your client's mcpServers manifest.
Which MCP Server Should You Choose?
Choose Chromeflow when:
You need dedicated capabilities in the Browser Automation domain.
You prefer local stdio subprocess transport architecture.
Your security boundary fits: No auth required (Free / Open Source).
You need dedicated capabilities in the Browser Automation domain.
You prefer local stdio subprocess transport architecture.
Your security boundary fits: No auth required (Free / Open Source).
You have access to required keys: START_URL, FIREFOX_HEADLESS, ACCEPT_INSECURE_CERTS, CONNECT_EXISTING, MARIONETTE_PORT, TOOL_PRESET, ENABLE_SCRIPT, ENABLE_PRIVILEGED_CONTEXT.
Navigate to a URL. By default reuses the active tab. Set new_tab=true to open alongside the current tab without losing it. After navigating, call get_page_text to read the page — do NOT take a screenshot.
Set background=true (only with new_tab=true) to open the new tab WITHOUT switching focus to it. Use this when the current tab has a partially-filled form whose page auto-saves on focus loss (e.g. eBay seller listings) — switching away would trigger the auto-save and corrupt the in-progress draft.
After tabs.onUpdated fires status=complete, chromeflow also runs a 6s settle check (document.readyState=complete, no visible spinner element, 250ms of mutation quiet). If a spinner is still visible at the end of the window, the response carries `stuck_spinner: true` with the matching selector — the canonical case is an SPA route that left a permanent .spinner-wrapper because the API request died. Set expect_selector to wait for a known-good element to appear before considering the page settled — the response carries `expect_selector_appeared: false` if it never showed up.
inspect_request_headers
Capture the request headers Chrome sends to a URL — useful for diagnosing server-side bot detection. Returns method, URL, and all headers. Cookie values are redacted by default to avoid leaking session tokens into the agent context; pass redact_cookies: false to see them. By default opens a background tab for the inspection so your active tab keeps its scroll position and form state — set new_tab: false to use the active tab instead.
switch_to_tab
Ready-to-Paste Client Configurations
Paste either (or both) of these JSON server blocks into your client config file (e.g. claude_desktop_config.json or ~/.cursor/mcp.json).
Chromeflow is categorized under Browser Automation and uses a local stdio subprocess. In contrast, Firefox Devtools MCP belongs to Browser Automation using local stdio subprocess. Select Chromeflow when you need capabilities focused on browser automation and Firefox Devtools MCP when you require tools for browser automation.
Switch the active tab to a different open tab. Use this after open_page(new_tab=true) to switch back to the original tab, or to jump between tabs.
Accepts: a tab number (1-based), a URL substring, or a title substring.
Pass it as either `tab` (mirrors the verb in the tool name — natural when targeting by index) or `query` (clearer when matching by URL/title substring). Both work identically.
Examples: switch_to_tab({tab: 1}) for the first tab, switch_to_tab({tab: "form"}) or switch_to_tab({query: "form"}) for a tab whose URL or title contains "form".
list_tabs
List all open tabs in the current window with their index, title, and URL. Use this before switch_to_tab if you're not sure which tab to switch to.
close_tab
Close a tab by number, URL substring, or title substring. Mirrors switch_to_tab's matcher. Defaults to closing the ACTIVE tab when no query is given. Use this to clean up the tab pile after a multi-step workflow.
close_other_tabs
Close every tab in the current window EXCEPT the active one (or any tab matching keep_query). Use at the end of a session to tidy up; do NOT use mid-flow if you may need to return to one of the closed tabs.
interactive_snapshot
Compact, accessibility-style list of the page's ACTIONABLE elements — each as [role] name — selector. Use this INSTEAD of get_page_text or take_screenshot when your goal is to ACT (click / type / select), not to read prose: it is far cheaper in tokens than dumping page text, and every line gives a ready-to-use selector for click_element / type_text. Pierces open AND closed shadow roots (Reddit faceplate-*, Radix/Stencil/Lit), which a raw accessibility tree misses. Returns the top elements by document order; pass max to widen. For reading article/body text, still use get_page_text.
take_screenshot
Capture a screenshot of the active tab. By default the image is returned to the agent inline UNLESS it exceeds ~500KB base64, in which case it's saved to a temp file and the path is returned instead (preserves the agent's context window). Set inline="always" to force inline regardless of size, or inline="never" to always write to a file. Set save_to or copy_to_clipboard to also share the image with the user. Reserved for cases where DOM lookup has already failed — use get_page_text and find_text for reading content.
Refuses fast on pages that are in fullscreen mode (captureVisibleTab hangs there). Exit fullscreen first with execute_script("document.exitFullscreen()") or pass allow_fullscreen: true if you really must try anyway.
capture_terminal
Capture a screenshot of the terminal window (Terminal, iTerm2, Warp, VS Code, Ghostty, etc.) and save it as a PNG.
Use this when you need a screenshot of terminal output — e.g. test results, build logs, or command output — to upload to a form via set_file_input.
Auto-detects the terminal app. Returns the image to Claude AND saves the PNG file.
The saved file path can be passed directly to set_file_input(hint, file_path) to upload it.
clear_overlays
Remove all highlights and callout annotations from the current page.
get_form_fields
Inventory form fields on the active page (inputs, textareas, selects, CodeMirror editors). Sorted top-to-bottom by y-position; includes fields below the fold.
Pass `query` to filter+rank by label/placeholder/aria-label/name/id (the old find_input behavior — match strength reported as aria-eq / placeholder-eq / label-text-eq / name-eq / id-eq / *-includes / fuzzy-text-walk). Pass `exact: true` to refuse fuzzy text-walk matches.
Pass `only_empty: true` to filter the inventory to required-but-empty fields. This is the "why is Submit disabled" diagnostic: it returns just the required fields that haven't been filled yet (or radios/checkboxes still unchecked) and skips everything that's already populated. Required-ness is detected via the `required` attribute, `aria-required`, or a trailing `*` in the associated label text.
type_text
Type text into the currently focused element via CDP keystrokes (produces isTrusted=true events). Use when fill_input fails because the page validates isTrusted (CodeMirror/Monaco/Ace editors, shadow DOM inputs, isTrusted-gated forms). Pass `into_selector` to focus the target before typing (shadow-piercing CSS) — combined with `clear_first: true`, this collapses the old "wait_for_click → execute_script selectAll → type_text" pattern into a single call. Pass `frame: "iframe.selector"` to type into a same-origin iframe's first editable element.
**Post-type landing verification, always on.** Whether you pass `into_selector` or type into whatever's already focused, type_text reads back the target element after typing and returns `landed:false` (success:false) if the text did NOT actually stick — never trust a bare "Typed N characters" without checking this. Two known causes: (1) a rich-text editor's own state machine reverts the keystrokes (TipTap/ProseMirror — auto-recovers via `document.execCommand('insertText', ...)`, message records "recovered via execCommand insertText"); (2) tenant-level anti-automation discards synthetic keystrokes outright with zero visible error (seen on some Workday tenants) — for a plain `<input>`/`<textarea>` this attempts a native-value-setter recovery, and if that ALSO fails, reports `landed:false` so you stop and report the wall instead of proceeding on a false premise.
+20 more tools listed on main page
Firefox Devtools MCP Tools (34)
list_pages
List open tabs (index, title, URL). Selected tab is marked.
new_page
Open new tab at URL. Returns tab index.
navigate_page
Navigate selected tab to URL.
select_page
Select active tab by index, URL, or title. Index takes precedence.
close_page
Close tab by index.
get_page_text
Get the visible text of the page (document.body.innerText). Caps at maxLength (default 20000 chars); saveTo saves the full text to a file.
take_snapshot
Capture DOM snapshot with stable UIDs. A UID stays valid across snapshots until its element is removed or the page navigates. Output caps at maxLines (default 100); scope with selector or dump the full tree with saveTo.
resolve_uid_to_selector
Resolve UID to CSS selector. Fails if the element is gone.
clear_snapshot
Clear snapshot UIDs. Usually not needed.
click_by_uid
Click element by UID. Set dblClick for double-click.