# chromeflow [Health: Active]

**Category:** 📂 Browser Automation  
**Repository:** https://github.com/NeoDrew/chromeflow  
**GitHub Stars:** 2  
**npm Downloads (last month):** 1170  
**Views:** 3  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/chromeflow

## Description
Lets Claude Code or Codex CLI drive your real Chrome — sessions intact, 26 browser tools.

## Tools
Capabilities this server exposes over MCP:

- **open_page** — 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** — 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.
- **set_file_input** — Upload a file to a file input — works even when the input is hidden behind a custom drag-and-drop zone. Returns success=true only after an observable commit: file count goes up, verify_selector appears, OR the input is reset AND the filename shows up somewhere on the page. An input reset with NO filename ever appearing anywhere returns success=false with a "silent rejection" message (some drag-and-drop widgets read then discard a file on tenant-level rejection with zero visible error) — do not trust that case as landed even though the input accepted the file momentarily. If there is no input[type=file] anywhere on the page (light or shadow DOM, checked for ALL hints, not just yours), the widget likely opens the browser-native file picker via window.showOpenFilePicker() instead of a classic file input — there is no DOM element for chromeflow to target in that case, so stop retrying with different hints and report it back. See CLAUDE.md for batch-upload guidance.

Two ways to supply the file:
- file_path (CDP mode): an absolute path on the machine running this server. Reaches both open AND closed shadow roots.
- file_content + file_name (inline-content mode): base64 file bytes plus a filename, materialized into the input directly. Use this when the server has no local disk access (e.g. a remote endpoint that can't see your filesystem). Caveat: inline-content mode reaches OPEN shadow roots only — if the input lives in a closed shadow root, use file_path instead.

Provide file_path OR file_content, not both.
- **execute_script** — Execute JavaScript in a tab's MAIN world (the page's own context, not the extension's isolated world). Use for reading framework state or DOM properties not visible in text — prefer get_page_text for visible content. Top-level `return` and `await` are supported.

**Object returns are auto-stringified** — return an object/array and the response carries its JSON. No need to wrap return values in JSON.stringify yourself.

**Shadow-piercing helpers are pre-injected** into every script:
- `$deep(selector, root?)` — querySelector that walks open shadow roots
- `$deepAll(selector, root?)` — querySelectorAll equivalent, returns an array
- `shadowDocument` — the open shadow root with the most interactive elements (buttons, inputs, links), or `document` if none. Useful when an SPA mounts ALL of its UI inside a single root shadow host (annotation-style dashboards that wrap the whole app in one web component): replace every `document.querySelector*` call with `shadowDocument.querySelector*` and the same code now reaches the SPA's content. On pages with multiple shadow roots (e.g. one for CSS theme vars, one for content), this picks the content root automatically.
- `shadowDocuments` — array of ALL open shadow roots on the page (in DOM order). Use when you need to search across multiple shadow roots or when the automatic pick is wrong.

The helpers pierce OPEN shadow roots only — MAIN world can't reach closed roots. For closed roots, use find_text / get_page_text / click_element / fill_input which pierce both kinds via chrome.dom.openOrClosedShadowRoot.

MAIN-world means the page's Content-Security-Policy applies: `fetch()` against authenticated APIs is often blocked by the page's connect-src directive. When that happens, switch to fetch_url — it runs in the extension's privileged context (full host_permissions, automatic cookie jar, no page CSP).

CSP-strict pages that disallow eval (Stripe, GitHub) silently fall through to a CDP eval path. Page alerts (alert/confirm/prompt) fired since the last script appear as PAGE ALERT in the result.

Pass `tab_query` to run the script against a specific tab without focus-switching (helpful for self-rescheduling loops where the active tab may have drifted while AFK). Accepts the same syntax as switch_to_tab: numeric index, URL substring, or title substring.

When the page navigates mid-script, the response carries `navigated: true` and result="[navigated]" instead of a thrown error — verify post-navigation state with get_page_text or wait_for. When host permission was lost (idle tab eviction), the handler reloads the tab once and retries; the response includes `reauthorized: true` so callers can see what happened.
- **highlight_region** — Show the user where to look with an instructional callout. Pass exactly one of:
- text — search the page for this visible text, highlight the match
- selector — CSS selector, highlight the matched element
- x/y/width/height — pixel rectangle (use only when DOM lookup failed)

Returns whether the element was found. Set valueToType only when the user must personally type a sensitive value (password, payment data) — otherwise Claude should auto-fill after the click.
- **fill_input** — Fill a form input by visible label / placeholder / aria-label (`textHint`) OR by direct CSS selector (`selector`). Pass exactly one.

`textHint` mode: fuzzy-rank against label/placeholder/aria-label/name/id. Response includes the matched element's identifying attributes and match-strength (aria-eq, placeholder-eq, label-text-eq, name-eq, id-eq, *-includes, fuzzy-text-walk). Verify the match — fuzzy-text-walk is the lowest-confidence kind. Pass `exact: true` to refuse fuzzy and *-includes matches.

`selector` mode (replaces the old react_set_input): targets the input directly and routes through the React-aware native value-setter so React's onChange picks up the change. Handles same-origin iframe inputs via `frame`.

Works on React-controlled inputs, contenteditable (Stripe, Notion), and CodeMirror 6 editors. Use `nth` (1-based) when multiple inputs share the same label.

**Workday auto-escalation** (textHint mode): fill_input always tries the native-setter fill first and reads the value back. On fields marked with Workday's `data-automation-id` convention where that read-back genuinely fails, it transparently re-enters the value via trusted keystrokes instead, same mechanism as type_text — the response message says "Escalated to trusted keystrokes" when this fires. This is a per-tenant behavior, not a per-platform one: on some anti-bot-hardened tenants it's the OPPOSITE (native setter lands, trusted keystrokes get dropped) — which is exactly why escalation only fires after a verified failure, never on marker-presence alone.
- **get_page_text** — Get the visible text content of the current page without taking a screenshot.
Use this instead of take_screenshot whenever you need to read what's on the page — errors, build status, form labels, confirmation messages, etc.
Returns up to 10,000 characters per call (~3k tokens). If the response ends with "... (N more characters)", call again with startIndex to read the next chunk.
Use the selector parameter to scope extraction to a specific section and avoid pulling unnecessary content.
Never use take_screenshot just to read page content — paginate with startIndex instead.
- **get_page_html** — Get the raw HTML of the current page or a scoped element. Use when you need to parse structure (tables, attribute values, nested data) and `get_page_text` strips too much, or when you're extracting structured data from a page Claude can't easily reason about from text alone.

Pierces open AND closed shadow roots for the `selector` lookup (Radix portals, Stencil/Lit web components). `<script>`, `<style>`, `<noscript>` are stripped before returning.

Default `max_chars` is 50,000. If the page is bigger, the response carries `truncated: true` and `total_chars` so you can decide whether to scope further with `selector`.

When the goal is "is X on this page?" or "find clickable Y", use `find_text` instead — it returns a focused match list rather than a wall of HTML.
- **get_console_logs** — Read the browser console output (log, warn, error, info) captured since the page loaded.
Returns the last 200 messages with their level and timestamp.
Use this to check for JavaScript errors, debug React issues, or verify that an action produced the expected console output.
Pass level="error" to see only errors, or omit to see all levels.
- **write_to_env** — Write a key=value pair to a .env file. Use this after capturing an API key or ID from the page.
- **read_attachment** — Fetch a URL via Chrome's privileged context (uses cookie jar, bypasses page CSP) and return parsed text. Supports docx (in-extension ZIP+XML extraction), txt/md/csv/json (UTF-8), html/xml (tag-stripped). For PDF, the response is a structured error pointing at download_file + local pdftotext. Truncates to max_chars (default 20000); reports total_chars + truncated for pagination.
- **download_file** — Download a file from a URL to the user's local disk using Chrome's authenticated download flow.

Uses the user's existing Chrome session, so this works on authenticated URLs (Canvas attachments, Stripe document downloads, GitHub release tarballs behind SSO) without any auth setup on chromeflow's side. Returns the absolute path where the file landed, plus MIME type and byte size.

Use this when you need the BYTES of a file (binary parsing, large content, anything you'll process with another tool). For "I just need the text content of this attachment" use read_attachment instead — it downloads + parses in one call.

The file is saved to the user's default downloads directory (usually ~/Downloads). Pass filename to suggest a name; Chrome will add a numeric suffix if a file with that name already exists.
- **fetch_url** — Make an HTTP request to a URL from the extension's privileged context, bypassing the page's Content-Security-Policy.

This is the "privileged context for network access" companion to execute_script. The mental model:
- **execute_script (page context):** DOM access, page CSP applies, fetch() blocked by connect-src.
- **fetch_url (privileged context):** no DOM, full extension host_permissions (<all_urls>), Chrome's cookie jar included automatically, page CSP does not apply.

Use this when:
- You need bytes from an authenticated URL the user is already signed into (Canvas attachments, Stripe document downloads, internal API JSON).
- fetch() inside execute_script returns "Failed to fetch" or hits a Content-Security-Policy connect-src error.
- You want a clean response object (status, headers, body) instead of having to wire up your own request handling in page-context JS.

Returns: { status, status_text, headers, content_type, body_text or body_base64 (when binary), truncated, total_bytes }.
Cookies and Origin headers are set by Chrome — pass any extra request headers via the headers param.
Set binary=true for non-text responses (PDFs, images, zips) — the body is returned base64-encoded.
- **click_element** — Click an interactive element by its visible text/aria-label (textHint) OR by direct CSS selector (selector). Pass exactly one.

`textHint` mode: fuzzy-rank against visible text, aria-label, button content. Ranks visible candidates ahead of hidden.

`selector` mode: pierces open AND closed shadow roots via queryAllDeep. Use when the target has no visible text (icon buttons, custom-element placeholders like Reddit's collapsed comment composer, drop-zone overlays). Skips the textHint matcher entirely. `nth` still picks the Nth match.

Optionally pass an until_* clause to verify the click took effect:
- until_selector — CSS selector that should appear after the click
- until_url_contains — substring that should appear in the URL (requires an actual URL change if the substring was already in the pre-click URL)
- until_text_contains — substring that should appear in page text
- until_url_changes — ANY URL change (use for submits where the destination URL is unknown ahead of time)
- expect_submit — broad anti-bot detector for form submissions (toast, alert, modal, URL change, form removal). See note below.

Returns {success, message, before_url, after_url, navigated}. `navigated` is true when the post-click URL differs from the pre-click URL — surfaces silent redirects without a second list_tabs call. Refuses to click 0×0 elements and now ranks visible candidates above hidden when text/aria match; when forced to refuse a hidden element it surfaces the next visible candidate in the error message.

Scope matching with `within_selector` or `near_text` restricts where matches are searched — useful for long forms with repeated labels per section (e.g. one "Approve" radio per row). `within_selector` is a CSS selector; `near_text` finds the nearest container whose heading starts with the given text.

Shadow DOM (open AND closed) is pierced by default via chrome.dom.openOrClosedShadowRoot — Reddit faceplate-* / r-post-form-submit-button / web-component-heavy SPAs no longer need manual deepFind recipes.

ANTI-BOT SUBMIT CEILING — synthetic clicks on social/auth platforms (Reddit, X / Twitter, mcp.so) are silently rejected by isTrusted-aware form validators and CSRF/reCAPTCHA gates. Pass `expect_submit: true` to detect this case (returns success=false with "submit silently rejected" when no signal fires within 4s). For confirmed anti-bot sites, do NOT retry — pre-fill the form and retry once with `try_fiber: true`; if it's still rejected, most sessions run unattended, so report the rejection rather than reaching for highlight_region + wait_for_click. Only use that pairing when a human is actually present for this session.
- **click_at_coordinates** — Dispatch a real CDP mouse click at viewport (x, y). The only way to interact with cross-origin iframes — `click_element` refuses cross-origin frames because `find_text` can't enter them, but a CDP-level mouse event resolves at the renderer process and reaches the iframe's content the way an OS-level click does.

Coordinates are viewport CSS pixels, NOT screen coordinates. `list_frames` reports each iframe at `(x, y, width, height)` in this same space, so to click 50px in / 80px down inside an iframe: `click_at_coordinates(frame.x + 50, frame.y + 80)`.

Runs the same humanlike sequence as `click_element` (bezier approach path, settle-hover micro-tremor, press, release, post-click micro-move) so behavioural fingerprinters can't distinguish the call from any other chromeflow click. Skips the activity probe — cross-origin iframe activity isn't observable from the parent.

Refuses obviously-bad coordinates (negative, > 10000). Use this only when DOM matching has failed and you have a known target position from `list_frames` or a screenshot.
- **save_flow** — Trust the hard-won interaction steps chromeflow buffered for the current site, immediately, as a named flow. chromeflow auto-buffers only NOTABLE resolutions (a click that needed a fallback, a verified submit, a field that needed real keystrokes), and AUTOSAVES them as a provisional flow when you leave the site — so memory works even if you never call this. Provisional flows are not recalled until they have been independently re-observed, or until you vouch for them here. Calling save_flow promotes the buffered steps to TRUSTED right away (an explicit "I confirm this worked"), so they are recalled next session instead of waiting to earn it.

Call this when a response shows `flow_capturable` and you are confident the task genuinely succeeded. Stored locally only (~/.chromeflow/flows.json), selectors/signals only — never typed text. Guidance, not autopilot: recalled steps are still verified on replay.
- **wait_for_click** — Wait for the user to click (or interact with) the currently highlighted element, then return.
Use this after highlighting a step so the flow advances automatically without the user returning to the chat.
After this resolves, highlight the next step immediately.
If the click causes page navigation, this resolves when the new page finishes loading.

Pass `redispatch: true` to turn the user's gesture into a CDP-dispatched isTrusted=true click. When the user clicks the highlighted area, chromeflow captures the coordinates and re-dispatches a full humanlike CDP click (bezier approach, settle hover, pointer events) at those exact coordinates. This produces an isTrusted=true event that passes anti-bot checks. Use for buttons that reject all synthetic clicks (shadow DOM buttons checking isTrusted, annotation dashboard "Collect Traces" buttons) where highlight_region + wait_for_click normally works but only the user's real gesture fires the action. With redispatch, the user still clicks, but chromeflow re-fires via CDP so subsequent automation (activity probe, state verification) works normally.
- **wait_for** — Wait for one of: a CSS selector to appear, a text substring (or any of an array of substrings) to appear, or an existing element's subtree to mutate. Pass exactly one of `selector`, `text`, or `change_in`. Pierces open AND closed shadow roots (text `scope_selector` pierces too). Pass `shadow_root: true` when waiting for the host's shadowRoot to attach (post-SPA-navigation hydration). `scope_selector` limits text-mode search; `regex: true` interprets text as a case-insensitive regex; `frame: "iframe.selector"` waits inside a same-origin iframe (text mode).

Text mode accepts an array — `text: ["New session", "Error", "Stop"]` resolves on the first match and the response carries `matched_query` so you know which entry fired. Useful for "wait for success OR failure" without a polling loop.

On timeout, the response carries `last_text` — the trailing 240 chars of the scope's content — so you can see the page state when the wait gave up. If the deploy panel shows "Starting up... 47%" and never reaches "Live", you'll see "Starting up... 47%" in last_text and know to extend the timeout instead of debugging a phantom failure.

Pass `since: "now"` in text mode to skip the initial check and only resolve on text appearing in a NEW DOM mutation — defeats the "stale instruction panels still in DOM" false-positive. When the wait DOES match on the initial check faster than 50ms, the response carries `initial_match_warning` suggesting since:"now" so you don't accidentally short-circuit on stale state.
- **scroll_to_element** — Scroll an element into view by CSS selector or label/text match.
Use this instead of guessing scroll amounts when you know which field or section you need to reach.
Examples: scroll_to_element("#submit-btn"), scroll_to_element("Billing address"), scroll_to_element(".cm-editor")
- **find_text** — Search the active page for text and return actionable matches (text, surrounding context, best-effort CSS selector, clickable flag). Use this instead of get_page_text when checking "is X on the page?" or locating a clickable target. Pierces open AND closed shadow roots. Pass `frame: "iframe.selector"` for same-origin iframe search.

When visible_only=true (the default) filters out all matches AND there were hidden matches, the response surfaces the hidden count so you can re-run with visible_only=false instead of guessing "is this on the page or not?"

Scope helpers: `in_dialog: true` restricts the search to the topmost open dialog; `dialog_query: "Select"` restricts it to a dialog whose heading or aria-label matches. Mirrors click_element's dialog scoping so the same flag works across discovery and action.
- **list_frames** — List every top-level iframe/frame on the active page, with its origin, whether its contentDocument is accessible (same-origin), and its on-screen position. Also reports shadow-host inventory so you can spot pages whose visible content is rendered inside closed shadow roots (Radix portals, Stencil/Lit, custom web components).

Use this BEFORE calling find_text({frame: "..."}) or other frame-targeted tools — it shows you which frames exist and which are reachable. Knowing a frame is cross-origin up front means you can route to read_attachment (for the frame's src URL) or take_screenshot instead of getting a "frame not accessible" error from another tool.

Also use this as a quick diagnostic when execute_script returns an empty document on a page you can clearly see — non-zero `shadow_hosts` (especially closed roots) means switch to find_text / get_page_text / click_element / fill_input, which pierce shadow DOM via the extension's privileged API.

Per-frame fields:
- selector: CSS selector you can pass to other tools' `frame` parameter
- src: the iframe's src attribute (may be empty for about:blank frames)
- origin: parsed origin (e.g. "https://canvadoc.instructure.com") — empty when src is data:/javascript:/empty
- accessible: true if contentDocument is reachable (same-origin), false otherwise
- title: the iframe's title attribute, often the most human-readable identifier
- x, y, width, height: bounding-box position in viewport CSS pixels

Per-shadow-host fields:
- selector: short CSS hint for the host element (tag, id, or .class)
- open: true if the shadow root is exposed via `el.shadowRoot` (most web components), false if it is closed (Radix portals, Stencil/Lit defaults) — closed roots are invisible to execute_script but pierced by chromeflow's other tools.
- depth: nesting depth (0 = top-level host attached directly to the document)

Note: this returns top-level frames only. Nested cross-origin frame trees are not enumerated. Shadow hosts are capped at 25 to keep the response compact.
- **fill_form** — Fill multiple form fields in a single call by targeting each field by its label text.
Use this instead of calling fill_input repeatedly — it fills all fields in one round trip and returns a per-field success report.
Ideal for forms with many textareas or inputs where each fill would otherwise require a separate tool call.
fields is an array of {label, value} pairs. label should match the field's visible label, placeholder, or aria-label.

Each per-field result includes the matched element description (e.g. `<input name="title" id="..." placeholder="...">`) so Claude can spot when fill_form picked the wrong field.

Pass `exact: true` for forms with short generic labels (like "Rate" or "Amount") that may collide with similarly-labeled neighbours — fields without an exact aria-label/placeholder/name/id/label-text match will return success=false instead of silently filling the wrong field.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "chromeflow": {
    "command": "npx",
    "args": ["-y","chromeflow"]
  }
}
```

## Documentation

## What chromeflow MCP server does

The chromeflow MCP server connects an AI coding agent to a real, already-running Chrome session. It is intended for browser work where a fresh automation profile would be logged out or unable to complete an authentication step. The agent can interact with pages while the user retains control of credentials, two-factor authentication, payment details, and other sensitive decisions.

This places chromeflow in the 📂 Browser Automation category, but its operating model differs from tools that launch an isolated browser. Existing cookies, logged-in sessions, and installed Chrome extensions remain available. The project describes support for workflows involving services such as Stripe, AWS, Supabase, Canvas, and GitHub, without requiring the agent to re-authenticate each time.

## How it works

The system has two parts: an MCP server that exposes browser actions to the coding agent, and a Chrome extension that executes those actions in the active tab. The extension can highlight page areas, perform interactions, and return page information to the agent. When a task reaches an action that should remain human-controlled, the flow can pause and ask the user to continue in Chrome.

Page interaction is based on DOM queries and browser control rather than a screenshot-only workflow. The repository documents handling for React inputs, contenteditable editors, dialogs, shadow DOM cases, and sites that require trusted-looking input events. On several social platforms, the documented workflow stops before submission: the agent prepares the content, highlights the region, and waits for the user to click Submit.

## Setup and configuration

For Claude Code, add the project marketplace with `/plugin marketplace add https://gitlab.com/NeoDrew/chromeflow.git`, then install it with `/plugin install chromeflow`. Install the Chromeflow extension from the Chrome Web Store and restart Claude Code. The plugin registers the MCP server and includes its usage skill.

For Codex CLI, add the marketplace with `codex plugin marketplace add https://gitlab.com/NeoDrew/chromeflow.git`, run `/plugins install chromeflow` inside Codex, install the same Chrome extension, and restart Codex. The extension is shared by both supported agent environments. The README describes a plugin-based installation and does not specify environment variables or per-project configuration.

## Tools and capabilities

Documented browser operations include opening pages, clicking elements, filling forms, setting file inputs, reading page text, and writing captured values to `.env`. The project description refers to a larger tool set for browser control, while the setup documentation specifically names these operations.

The server can support workflows such as retrieving API keys from a logged-in dashboard and saving them to a project environment file. It can also assist with composing text in web applications, including editors based on Lexical, Quill, TipTap, or ProseMirror, where the listed validation covers particular interactions.

## Limitations and notes

The user must have Chrome and the extension installed, and the relevant session must be available in that browser. Sensitive steps are intentionally not fully automated. Password entry, 2FA, payment actions, and final submission on some anti-bot-protected sites may require a manual action.

Validated behavior is platform- and interaction-specific, not a guarantee that every page or control will work. The repository lists tested cases for Reddit, X/Twitter, LinkedIn, Facebook, Instagram, Radix dialogs, closed shadow DOM, and TipTap/ProseMirror. These validations describe particular actions such as typing or opening a composer, rather than unrestricted automation of each platform.

_Full upstream README: https://allmcps.com/mcp/chromeflow/readme_

