{
  "markdown": "# gecko-mcp\n\n[![CI](https://github.com/Frumane/gecko-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Frumane/gecko-mcp/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n> An **MCP (Model Context Protocol)** server that lets AI assistants — Claude Code,\n> Claude Desktop, Cursor, and any MCP client — **read pages, take screenshots and\n> manage tabs** in [Floorp](https://floorp.app) **and other Firefox-based browsers**\n> (LibreWolf, Waterfox, Zen, Mullvad, Firefox…), using your real, logged-in session.\n\nThink \"Claude in Chrome\", but for the whole Firefox/Gecko family.\n\n![Claude driving Floorp via gecko-mcp — opening the GitHub repo and the npm page from a natural-language prompt](docs/demo.gif)\n\n**Get started in ~30 seconds:**\n\n```bash\nnpx gecko-mcp setup\n```\n\nRegisters gecko-mcp with **Claude Code, Claude Desktop, Cursor, VS Code (Copilot), Windsurf, Zed, Codex** and more — per-project or global. See [Setup](#setup) for manual config and [Requirements](#requirements) for the one-time browser step.\n\n> **Cautious about installing this?** Good — you should be. It's small (2 deps, all\n> in [`src/`](src)), the OS keyboard/mouse is **locked by default** (browser-only\n> until you opt in), releases ship with **npm provenance** (verifiable against this\n> source), and the full threat model is in [SECURITY.md](SECURITY.md). Read it\n> before you run `npx gecko-mcp`.\n\n## How it works\n\ngecko-mcp talks to the browser through one of two backends, picked automatically:\n\n- **Floorp** ships a built-in automation HTTP API. Set `floorp.mcp.enabled = true`\n  in `about:config` and gecko-mcp uses the fast `http://127.0.0.1:58261` API —\n  no extension, richest feature set.\n- **Any other Gecko browser** — launch it with **Marionette** (the automation\n  engine built into every Firefox fork) and gecko-mcp drives your live session\n  over it. Same tools, same real session.\n\n```\n  Claude Code / Desktop / Cursor\n            │  MCP (stdio)\n            ▼\n      gecko-mcp ──► Floorp :58261 (built-in API)        ─┐\n   (this project) ──► Marionette :2828 (any Gecko fork)  ─┴─► your real tabs\n```\n\n## Requirements\n\n- A **Firefox-based browser** installed and running, with automation enabled:\n  - **Floorp:** set **`floorp.mcp.enabled`** to `true` in `about:config`, restart Floorp.\n  - **Other forks (LibreWolf / Waterfox / Zen / Mullvad / Firefox):** launch the\n    browser with **`-marionette`** (see [Browser support](#browser-support)).\n- **Node.js** ≥ 18.\n\n## Setup\n\n### Quick start — the setup wizard\n\n```bash\nnpx gecko-mcp setup\n```\n\nAn interactive wizard registers gecko-mcp with the AI coding tool(s) of your\nchoice — **Claude Code, Cursor, Windsurf, VS Code (Copilot), Gemini CLI, Codex,\nZed, Cline** (and a copy-paste snippet for **Kimi Code, Antigravity**, or any\nother MCP client) — and lets you install it **for the current project** or\n**globally (all repos)**. It merges into existing config (and backs it up first).\n\nNon-interactive / scriptable:\n\n```bash\nnpx gecko-mcp setup --list                          # show supported tools\nnpx gecko-mcp setup --tool claude-code,cursor --scope global\nnpx gecko-mcp setup --tool codex --scope global --print   # dry run\n```\n\n### Manual\n\nAny MCP client works with this server block (no clone/build needed — `npx`\nfetches it):\n\n```json\n{\n  \"mcpServers\": {\n    \"gecko\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"gecko-mcp\"]\n    }\n  }\n}\n```\n\nOr with Claude Code's CLI: `claude mcp add gecko -s user -- npx -y gecko-mcp`.\n\n> **One-time Floorp step:** set `floorp.mcp.enabled = true` in `about:config` and\n> restart Floorp so its automation API is available.\n\n## Browser support\n\ngecko-mcp picks its backend automatically: if Floorp's `:58261` API is reachable\nit uses that; otherwise it connects to **Marionette**, the automation engine built\ninto every Gecko browser. To use a non-Floorp browser, launch it once with\nMarionette enabled:\n\n| Browser | Launch with Marionette |\n|---|---|\n| **Floorp** | *(no flag — just set `floorp.mcp.enabled=true`; uses the native API)* |\n| **Firefox** | `firefox -marionette` |\n| **LibreWolf** | `librewolf -marionette` |\n| **Waterfox** | `waterfox -marionette` |\n| **Zen** | `zen -marionette` |\n| **Mullvad** | `mullvad-browser -marionette` |\n\nMarionette listens on TCP **2828** by default. To use another port, set the\n`marionette.port` pref in the profile (e.g. via `user.js`) and start gecko-mcp\nwith a matching `MARIONETTE_PORT`. Force a backend with `GECKO_MCP_BACKEND=marionette`.\n\n> **Note:** Marionette must be enabled *at launch* to attach to your live session.\n> On the Marionette backend, Floorp-only extras (`snapshot` fingerprints,\n> `list_workspaces`/`switch_workspace`, accessibility tree) return a clear\n> \"not supported\" message — use `find` / `read_page` instead. Everything else\n> (tabs, navigation, click, type, forms, screenshots, cookies, real OS input…) works.\n\n## Tools\n\n**Tabs & reading**\n\n| Tool | What it does |\n|------|--------------|\n| `list_tabs` | List all open tabs (title, URL, browserId, active, pinned). |\n| `open_tab` | Open a new tab at a URL; **returns the new tab's `browserId`** so you can target it. |\n| `get_active_tab` | Return the active tab's title, URL and browserId. |\n| `navigate_tab` | Navigate an existing tab to a URL. |\n| `close_tab` | Close a tab. |\n| `read_page` | Read a tab's content as clean Markdown (or HTML / accessibility tree). Output is capped (default 25 KB) to protect the context. |\n| `find` | **Fast element locator** — search a page server-side by visible text and/or tag; returns a compact list of ready-to-use CSS `selector`s (~1 KB) instead of the whole HTML. Use it to find a button/link/field, then act on the selector. |\n| `snapshot` | Structured page map: Markdown with inline `fp:` refs + an element selector map — locate elements without grepping HTML, then act via a `ref`. |\n| `screenshot` | Capture a screenshot of a tab (viewport or full page). |\n| `launch_floorp` | Ensure Floorp is running — launches it if the API isn't reachable (Windows). |\n| `launch` | Start any Firefox-based browser (Firefox, LibreWolf, Zen…) with Marionette enabled so gecko-mcp can drive it. |\n\n**Interaction**\n\n| Tool | What it does |\n|------|--------------|\n| `click` | Click an element by CSS selector **or a `ref` from `snapshot`**; auto-scrolls it into view first. |\n| `type_text` | Type into an input/textarea — or a rich/contenteditable editor (Slate, ProseMirror…) — by CSS selector. |\n| `fill_form` | Fill multiple fields at once. |\n| `press_key` | Press a keyboard key (Enter, Tab, …). |\n| `wait_for_element` | Wait for an element to attach / become visible / etc. |\n| `get_value` | **Sensitive.** Read the current value of an input/textarea/select (can read password fields). |\n\nMost tools target the **active tab** by default; pass a `browserId` (from\n`list_tabs`) to target a specific tab.\n\n### OS keyboard & mouse — **locked by default** 🔒\n\nThe tools below can affect things *outside* the browser, so they are **disabled\nuntil you turn them on**. With nothing set, gecko-mcp does browser automation only.\nUnlock them per-session by just asking (\"**enable OS input**\", which calls the\n`enable_os_input` tool), or persistently with `GECKO_MCP_ENABLE_OS_INPUT=1`. Lock\nagain with `disable_os_input`. While locked, these tools refuse with a clear message.\n\nThe **`evaluate`** tool (run arbitrary page JavaScript) is locked the same way —\nunlock with `enable_evaluate` or `GECKO_MCP_ENABLE_EVALUATE=1`.\n\n| Tool | What it does |\n|------|--------------|\n| `enable_os_input` / `disable_os_input` | Unlock / re-lock the OS keyboard & mouse tools for this session. |\n| `enable_evaluate` / `disable_evaluate` | Unlock / re-lock the `evaluate` (run page JS) tool for this session. |\n| `evaluate` | **Locked.** Run JavaScript in the page and return its value (`return …`). |\n\n**Real OS keyboard (Windows)** — for React/rich editors and bot-guarded submits\nthat ignore synthetic input:\n\n| Tool | What it does |\n|------|--------------|\n| `real_type` | Type into the focused element via **genuine OS key events** (`isTrusted`). |\n| `real_key` | Press a real key/combo, e.g. `\"Enter\"`, `\"ctrl+a\"`. |\n| `real_clear` | Real Ctrl+A + Delete — reliably clears a rich/contenteditable field. |\n\nThese produce input a page can't distinguish from a human's, so they drive\nReact/Slate editors and submit composers that synthetic clicks/typing can't.\nWorkflow: `click` the field to focus it → `real_clear` / `real_type` / `real_key \"Enter\"`.\n\n> **Safety guard:** OS keystrokes go to the foreground window, so before sending\n> anything these tools bring Floorp to the foreground and **verify** it — if Floorp\n> isn't running or can't be focused, they **abort without typing a single key**, so\n> input can never leak into another app.\n\n**Real OS mouse (Windows)** — genuine `isTrusted` clicks at screen coordinates:\n\n| Tool | What it does |\n|------|--------------|\n| `window_bounds` | Floorp's window rectangle in screen pixels (to compute targets). |\n| `move_cursor` | Move the real OS cursor to a screen pixel inside Floorp. |\n| `real_click` | Real OS click (left/right, single/double) at a screen pixel inside Floorp. |\n\n> **Double guard:** the click is sent only when Floorp is verified foreground **and**\n> the point lies **inside Floorp's window rect** — a stray coordinate is refused, so\n> a click can never land in another app/window. Coordinates are screen pixels\n> (note display scaling/DPI when mapping from a screenshot).\n\n**More interaction & queries**\n\n| Tool | What it does |\n|------|--------------|\n| `hover` / `double_click` / `right_click` | Mouse gestures on an element (selector or `ref`). |\n| `select_option` | Choose an option in a `<select>`. |\n| `set_checked` | Check/uncheck a checkbox or radio. |\n| `submit_form` | Submit a form. |\n| `upload_file` | **Sensitive.** Set a file `<input>` by absolute path — restrict with `GECKO_MCP_ALLOW_UPLOAD_DIRS`. |\n| `get_attribute` | Read an element attribute (href, value, …). |\n| `get_article` | Readability-extracted main article as Markdown. |\n| `get_cookies` | **Sensitive.** Cookies visible to the page — values redacted unless `includeValues: true`. |\n| `wait_for_network_idle` | Wait for network activity to settle. |\n| `list_workspaces` / `switch_workspace` | Floorp workspaces (where supported). |\n\n## Security\n\nUnderstand the threat model before enabling this. Two risks dominate:\n\n1. **Floorp's automation API has no authentication by default.** While\n   `floorp.mcp.enabled` is on, **any local process** can drive your logged-in\n   browser via `127.0.0.1:58261` — not just this server. There is also no\n   Origin check, so hostile web pages may attempt CSRF/DNS-rebinding tricks\n   against it. Mitigations:\n   - Turn `floorp.mcp.enabled` **off** when you're not using automation.\n   - Set the `GECKO_MCP_TOKEN` environment variable — this server then sends it\n     as a `Bearer` token on every request (effective on Floorp builds that\n     enforce a token; harmless otherwise).\n2. **Prompt injection (\"lethal trifecta\").** The assistant reads untrusted page\n   content *and* can act on your authenticated sessions (click, type, submit,\n   navigate, real OS input). A malicious page could try to instruct the\n   assistant to act against you. Treat everything read from a page as untrusted;\n   don't run automation unattended on sites you don't trust.\n\nHardening built into this server:\n\n- **OS keyboard/mouse is locked by default (least privilege):** the only tools that\n  can act outside the browser refuse to run until you explicitly unlock them\n  (`enable_os_input` tool, or `GECKO_MCP_ENABLE_OS_INPUT=1`). By default gecko-mcp\n  can only automate the browser, never your wider machine.\n- **Real OS input is double-guarded:** keys/clicks are sent only after verifying\n  Floorp is the foreground window, and mouse clicks must land inside Floorp's\n  window rectangle — otherwise it aborts *without* sending anything. PowerShell\n  payloads are passed base64-encoded via process-private environment variables\n  (no shell interpolation, no temp script files on disk).\n- **URL scheme + host allowlist:** `open_tab`/`navigate_tab` accept only `http(s)`\n  (and `about:blank`) by default, and **refuse loopback/private hosts**\n  (`127.0.0.1`, `localhost`, `10/8`, `172.16/12`, `192.168/16`, `169.254/16`,\n  IPv6 ULA/link-local). This stops a prompt-injected agent from pivoting the\n  browser onto Floorp's own API or your LAN and reading the response back. Lift\n  with `GECKO_MCP_ALLOW_PRIVILEGED_URLS=1`. Optionally pin navigation to a\n  domain allowlist with `GECKO_MCP_ALLOW_DOMAINS`.\n- **Cookie values are redacted by default** in `get_cookies`; raw values require\n  an explicit `includeValues: true`.\n- **`get_value` can read secrets:** browsers let same-origin JS read password\n  fields, so this tool *can* return a typed password. It's flagged SENSITIVE —\n  use it only on fields the user asked about, never to harvest credentials.\n- **Upload allowlist:** set `GECKO_MCP_ALLOW_UPLOAD_DIRS` (`;`-separated\n  directories) to confine `upload_file`. Paths are canonicalised with realpath\n  (symlinks resolved) and checked so `..`, a symlink, a same-prefix sibling\n  directory, or a UNC path can't escape the allowed folders.\n- **`find` skips hidden elements** (inline `display:none`/`visibility:hidden`,\n  `hidden`, `type=hidden`, `aria-hidden`) so a page can't lure the agent into\n  clicking an invisible button via text search.\n- **Input bounds:** numeric/text tool parameters are range- and length-capped\n  (coordinates, timeouts, `maxChars`, `find` limit, typed text, form fields) to\n  prevent resource-exhaustion / crash inputs.\n- **Truncated API errors & validated port:** Floorp error bodies are truncated\n  before reaching the model; `GECKO_MCP_PORT` is validated as 1–65535.\n- **Tool annotations for human-in-the-loop:** every tool carries MCP hints\n  (`readOnlyHint`/`destructiveHint`/…) so your client can auto-run read-only\n  tools and confirm destructive ones (`close_tab`, `navigate_tab`, `submit_form`,\n  `upload_file`). A server can't show prompts itself — approval is the client's\n  job — so this is how gecko-mcp tells the client what's safe vs consequential.\n- **No `evaluate` tool:** arbitrary page-JS execution is deliberately not exposed.\n\nWhat is **not** defended (inherent / Floorp-side): a malicious *local* process can\nstill read or impersonate the unauthenticated loopback API (plaintext, no TLS), and\nprompt injection from a page you choose to automate can still drive legitimate\nactions on that page. Disable `floorp.mcp.enabled` when idle and don't automate\nuntrusted sites unattended.\n\n| Environment variable | Effect |\n|---|---|\n| `GECKO_MCP_TOKEN` | Sent as `Authorization: Bearer …` to the Floorp API. |\n| `GECKO_MCP_PORT` | API port (default `58261`, validated 1–65535). |\n| `GECKO_MCP_ALLOW_PRIVILEGED_URLS` | `1` allows non-http(s) URLs **and** loopback/private hosts in open/navigate. |\n| `GECKO_MCP_ALLOW_DOMAINS` | Comma-separated domain allowlist for navigation (subdomains included). Unset = any public host. |\n| `GECKO_MCP_ALLOW_UPLOAD_DIRS` | Restrict `upload_file` to these directories (`;`-separated). |\n| `FLOORP_PATH` | Full path to `floorp.exe` for `launch_floorp`. |\n| `GECKO_MCP_BACKEND` | Force the backend: `floorp` or `marionette`. Default: auto-detect. |\n| `MARIONETTE_PORT` | Marionette TCP port for non-Floorp browsers (default `2828`). |\n| `GECKO_MCP_ENABLE_OS_INPUT` | `1` unlocks the OS keyboard/mouse tools at startup (otherwise locked until the `enable_os_input` tool is called). |\n| `GECKO_MCP_ENABLE_EVALUATE` | `1` unlocks the `evaluate` (run page JS) tool at startup (otherwise locked until `enable_evaluate`). |\n| `GECKO_MCP_BROWSER_PROCESS` | Process-name regex the real OS keyboard/mouse may target (default covers the common Gecko forks). |\n\n> The legacy `FLOORP_MCP_*` variable names still work as fallbacks (from before the\n> rename), so existing configs keep working — prefer `GECKO_MCP_*` going forward.\n\n## Performance\n\n- **HTTP tool calls are cheap** — a full attach → act → detach round-trip against\n  Floorp's local API is ~5–6 ms. `find` searches the page server-side and returns\n  ~1 KB of ready-to-use selectors instead of dumping the whole HTML, and\n  `read_page` is capped (default 25 KB) so a page read can't flood the context.\n- **Real OS input uses a persistent PowerShell host.** Spawning `powershell.exe`\n  (~700 ms) and compiling the P/Invoke helper (~600 ms) used to happen on *every*\n  `real_*`/`move_cursor`/`window_bounds` call (~1.9 s each). Now one host is\n  started lazily, compiles once, and runs a read-eval loop — so the first call\n  pays ~1.6 s but every call after is **~350 ms** for a guarded key/click (~5×\n  faster) and a few ms for a window-bounds query. The foreground/bounds safety\n  guards still run on every command; the host is recycled if it hangs or dies.\n\n## Notes & limitations\n\nLearned from driving real apps (incl. Google Flow):\n\n- **Rich editors:** `type_text` handles plain inputs *and* contenteditable editors\n  (Slate, ProseMirror, Lexical) — it falls back to dispatching a real text-input\n  event when an element has no `.value`. Reliably *clearing* such editors isn't\n  solved yet (no `select-all`/`evaluate`).\n- **Submitting React composers:** many chat/prompt composers submit on a real\n  **Enter keydown**, not on a synthetic click of the send button. Prefer\n  `press_key` `\"Enter\"` over `click` for those.\n- **Trusted events:** you cannot forge `isTrusted=true` from page JavaScript — it\n  is a browser security invariant. Floorp injects input at a privileged layer, so\n  ordinary clicks/keys behave like real ones; but flows guarded by reCAPTCHA or\n  strict bot-detection may still refuse automated submission.\n- **`evaluate`:** the page-JS eval endpoint returns HTTP 404 on some Floorp builds,\n  so it is not exposed as a tool here.\n- **Multiple windows:** when more than one window is open, the \"active tab\" is\n  ambiguous (each window has its own active tab). Prefer the `browserId` returned\n  by `open_tab`, or one from `list_tabs`, and pass it explicitly to every tool.\n\n## Roadmap\n\n- [x] Tab management, page reading, screenshots\n- [x] Interaction tools: click, type, fill forms, key presses, read field values\n- [x] Real OS keyboard (Windows): `real_type` / `real_key` / `real_clear`, with a\n      foreground safety guard — drives React/Slate editors & bot-guarded submits\n- [x] `snapshot` (fingerprint refs + selector map) + `click` by `ref` + auto-scroll-into-view\n- [x] `launch_floorp` — start Floorp if not running (Windows)\n- [x] Extra tools: hover, double/right-click, select_option, set_checked, submit,\n      upload_file, get_attribute, get_article, get_cookies, wait_for_network_idle, workspaces\n- [x] Real OS mouse (Windows): `window_bounds` / `move_cursor` / `real_click`, with a\n      foreground + in-window-bounds double guard\n- [x] **Marionette backend — all Firefox-based browsers** (LibreWolf, Waterfox,\n      Zen, Mullvad, Firefox…), auto-selected when Floorp's API isn't present\n- [ ] macOS / Linux native-input backends\n- [ ] JS `evaluate` (available in newer Floorp builds; older ones return HTTP 404)\n- [ ] Optional bearer-token auth\n- [ ] `launch` helper for non-Floorp browsers (start them with `-marionette`)\n\n## Acknowledgements\n\nBuilt against the automation API exposed by Floorp. The official\n[`Floorp-Projects/floorp-mcp-server`](https://github.com/Floorp-Projects/floorp-mcp-server)\nwas a useful reference for mapping the endpoint surface. This is an independent,\nclean-room MIT-licensed implementation.\n\n## License\n\n[MIT](./LICENSE) © Frumane\n",
  "bytes": 19684,
  "sha": "45dd1d1790e785682bdf203c182c2b17d85dfdff9c58b91d0ee35b1584db7be3",
  "repo_slug": "frumane/gecko-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_frumane_gecko_mcp_d73b2657/readme"
}