ffmcp
Provides tools for controlling Firefox browser, managing tabs, pages, cookies, history, bookmarks, and executing arbitrary WebExtension API calls, enabling full browser automation.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ffmcpopen a new tab to example.com"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ffmcp
Расширения для Firefox и Chrome + MCP-сервер, дающие агентам полный доступ к твоим настоящим браузерам. Транспорт целиком локальный: stdio и Unix-сокет, ни одного HTTP-запроса и ни одного открытого порта.
Как устроено
┌─ Unix-сокет ~/.ffmcp/firefox.sock ─▶ ffmcp-host.js ─▶ расширение Firefox
MCP-агент ──stdio JSON-RPC──▶ ffmcp-mcp.js
└─ Unix-сокет ~/.ffmcp/chrome.sock ──▶ ffmcp-host.js ─▶ расширение ChromeУ каждого браузера свой native-хост и свой сокет (права 0600). Хост поднимается самим
браузером при старте расширения и работает как брокер: к нему подключаются MCP-серверы.
Поэтому несколько агентов одновременно работают с одним браузером, а внешне не открыт
ни один сетевой порт.
Один MCP-сервер обслуживает оба браузера сразу — какой именно использовать, агент решает
вызовом browser_use либо аргументом browser у любого инструмента.
Ответы больше ~700 КБ (скриншоты, дампы DOM) автоматически режутся на чанки и собираются обратно в хосте — ограничение native messaging на размер сообщения обходится прозрачно.
Related MCP server: mcp-browser-kit
Установка
cd ~/Projects/ffmcp
./install.shСкрипт кладёт манифесты native-хоста в каталоги Firefox и всех найденных браузеров на
Chromium и создаёт лаунчеры с абсолютным путём к node (браузер из Finder не видит
/opt/homebrew/bin).
Затем ставим расширения — нужно хотя бы одно.
Firefox:
about:debugging#/runtime/this-firefoxЗагрузить временное дополнение… → выбрать
extension/manifest.json
Временное дополнение живёт до перезапуска Firefox. Чтобы поставить навсегда, нужен Firefox Developer Edition / Nightly / ESR с
xpinstall.signatures.required = falseвabout:config— тогда собери.xpi(npm run build-xpi) и установи черезabout:addons→ Установить дополнение из файла. В обычном релизном Firefox неподписанные расширения навсегда поставить нельзя — это ограничение Mozilla.
Chrome:
chrome://extensions→ включить Режим разработчикаЗагрузить распакованное расширение → выбрать каталог
extension-chromeID должен получиться
mifolcjccjkcdfdoebmkgklimogoemng— он зашит полемkeyв манифесте, и именно его разрешает манифест native-хостаЖелательно: в карточке расширения включить Разрешить пользовательские скрипты
Последний пункт нужен для browser_eval на сайтах со строгим CSP — подробности ниже.
Распакованное расширение переживает перезапуск Chrome, но требует включённого режима
разработчика.
Проверка обоих браузеров сразу:
./bin/ffmcp.js doctor
./bin/ffmcp.js browsersПодключение к агентам
Claude Code:
claude mcp add browser -- node ~/Projects/ffmcp/bin/ffmcp-mcp.jsЛюбой другой MCP-клиент (mcp.json, Cursor, Zed):
{
"mcpServers": {
"browser": {
"command": "node",
"args": ["/абсолютный/путь/к/ffmcp/bin/ffmcp-mcp.js"]
}
}
}Если хочется жёстко привязать сервер к одному браузеру — добавь --browser firefox
или --browser chrome (то же самое делает переменная FFMCP_BROWSER). Тогда
browser_use не понадобится, а вызовы в другой браузер уходить не будут.
Выбор браузера
browser_list # кто доступен и кто выбран сейчас
browser_use {"browser": "chrome"} # закрепить Chrome для всех следующих вызовов
browser_use {} # показать текущий выбор
browser_use {"release": true} # вернуться к автоопределению
browser_tabs {"browser": "firefox"} # разовый вызов мимо закрепленияБез закрепления браузер выбирается сам: единственный запущенный, иначе последний
удачно использованный, иначе Firefox. Аргумент browser у конкретного вызова всегда
сильнее закрепления.
Инструменты
39 инструментов. Основные:
Группа | Инструменты |
Браузер |
|
Рабочее окно |
|
Вкладки и окна |
|
Страница |
|
Отладка |
|
Профиль |
|
Всё остальное |
|
Старые имена вида firefox_tabs продолжают работать (и заодно задают браузер), но в
списке инструментов их больше нет.
browser_api — это и есть «полный доступ». Он вызывает любой метод WebExtension API
напрямую, так что агент не ограничен готовым списком:
{ "path": "tabs.query", "args": [{ "audible": true }] }
{ "path": "browsingData.removeCache", "args": [{}] }
{ "path": "proxy.settings.set", "args": [{ "value": { "proxyType": "none" } }] }browser_api_describe показывает, что вообще доступно ({"path": "cookies"} → список
методов). Наборы API у Firefox и Chrome разные, так что спрашивать стоит у того браузера,
в котором собираешься работать.
Рабочее окно
По умолчанию операции без явного tabId идут в активную вкладку текущего окна — то есть
туда, где сейчас работаешь ты. Чтобы агент не мешал, закрепи за ним окно:
browser_new_window {"url": "https://example.com"} # своё окно, сразу закреплено
browser_use_window {"windowId": 3} # закрепить существующее
browser_use_window {} # показать текущее закрепление
browser_use_window {"release": true} # снятьПосле закрепления в это окно уходят все операции без tabId, туда же открываются новые
вкладки, а browser_tabs показывает только его вкладки (allWindows: true — все).
В browser_windows закреплённое окно помечено target: true, а в попапе расширения видно
строкой «Рабочее окно». Закрепление живёт отдельно в каждом браузере и снимается само,
если окно закрыть. Из CLI — ffmcp use-window <id> и ffmcp new-window [url].
Работа со страницей
browser_snapshot возвращает интерактивные элементы с короткими uid — это дешевле
скриншота и точнее селекторов:
{ "uid": "e12", "tag": "input", "type": "email", "text": "Email address", "rect": {...} }Далее browser_fill {uid: "e12", value: "..."} и browser_click {uid: "e15"}.
browser_fill выставляет значение через нативный сеттер прототипа и шлёт input/change,
поэтому корректно работает с React, Vue и Svelte.
browser_eval выполняет код в контексте страницы — это тело async-функции, доступны
await и return:
return [...document.querySelectorAll("h2")].map(h => h.innerText)Логи консоли и необработанные исключения собираются автоматически на каждой загруженной
странице (буфер на 2000 записей), сетевые запросы — через webRequest. Читать через
browser_console и browser_network. Вывод console.* из самого browser_eval тоже
попадает в буфер.
Чем Chrome отличается от Firefox
Расширение для Chrome — Manifest V3, и часть вещей там устроена иначе. Набор операций одинаковый, отличается поведение:
Firefox | Chrome | |
Фон | постоянная фоновая страница | service worker, который браузер усыпляет |
Произвольный JS |
| мир страницы + |
Скриншот всей страницы | одним вызовом | прокрутка и сшивка кусков (медленнее, до 25 экранов) |
Скриншот неактивной вкладки | как есть | вкладку приходится на миг активировать |
Контейнеры |
| аналога нет, вызов честно падает |
Что из этого важно на практике:
browser_evalи CSP. Код выполняется в мире страницы, а там действует её Content-Security-Policy. На сайтах со строгимscript-src(например GitHub)evalзапрещён. Обход — включить в карточке расширения «Разрешить пользовательские скрипты»: тогда ffmcp выполнит код в отдельном мире, где CSP страницы не действует. Без этого на таких сайтах придёт понятная ошибка с этой же подсказкой. Инструментыbrowser_click,browser_fill,browser_snapshotи остальные структурные операции CSP не касается вообще — они внедряются готовыми функциями, а не строками.Буферы логов и сон воркера. Service worker MV3 живёт от события до события. Расширение держит его будильником и переподключается автоматически, но если Chrome всё же усыпит воркер, накопленные
browser_consoleиbrowser_networkобнулятся. Закрепление рабочего окна переживает это — оно лежит вstorage.session.Служебные страницы.
chrome://*, Chrome Web Store и PDF расширениям недоступны — там любая операция со страницей вернёт ошибку. В Firefox то же самое сabout:*иaddons.mozilla.org.
CLI
Тот же мост доступен вручную, без агента:
./bin/ffmcp.js doctor # диагностика обоих браузеров
./bin/ffmcp.js browsers # кто сейчас на связи
./bin/ffmcp.js status # версия браузера, число вкладок
./bin/ffmcp.js tabs # список вкладок
./bin/ffmcp.js open https://example.com
./bin/ffmcp.js snapshot 12 # элементы вкладки 12
./bin/ffmcp.js eval 'return document.title'
./bin/ffmcp.js api tabs.query '[{"pinned":true}]'
./bin/ffmcp.js call page.text '{"tabId":12}'Браузер выбирается флагом --browser=chrome (или -b chrome), переменной
FFMCP_BROWSER, иначе автоматически:
./bin/ffmcp.js -b chrome tabs
FFMCP_BROWSER=chrome ./bin/ffmcp.js statusТесты
./tests/run.shЖивой браузер не нужен — браузеры и расширения заменяются заглушками. Проверяются: сборка JSON-RPC и все схемы инструментов, проход вызова MCP → сокет → хост → расширение, выбор браузера и маршрутизация между двумя хостами, сборка чанкованных ответов, очистка сокета, отсутствие синхронных циклов при загрузке фона (отдельно для фоновой страницы Firefox и для service worker Chrome) и, отдельно, устойчивость обоих перехватчиков консоли к зацикливанию.
Последний тест не декоративный. В первой версии обмен со страницей шёл через
window.postMessage, а его видит и сама страница: если её код логирует полученные
сообщения (частый паттерн у виджетов и аналитики), возникала петля
console.log → postMessage → обработчик страницы → console.log. Каждая итерация уходила
в фон отдельным runtime.sendMessage, забивая главный поток родительского процесса
браузера, и он переставал реагировать на ввод. Сейчас обмен идёт приватным
CustomEvent, стоят защита от реентрантности, лимит 100 сообщений в секунду со страницы,
батчинг раз в 250 мс и общий потолок 500 записей в секунду в фоне.
Безопасность
Это по построению очень мощный доступ: агент видит твои cookies, историю и авторизованные сессии и может действовать от твоего имени на любом сайте.
Подтверждение подключения
Каждое новое подключение MCP-клиента к сокету пользователь подтверждает вручную. Пока
не подтверждено — все вызовы (кроме health-check ping и __status) висят, а хост
показывает нотификацию браузера. Клик по ней открывает popup расширения, где для запроса
предлагается выбор области:
это подключение — доступ только для текущего живого соединения; переподключился клиент — спросят снова;
на эту сессию — до перезапуска браузера (запоминается токен клиента в памяти хоста);
навсегда — токен клиента сохраняется в
~/.ffmcp/allowed-<браузер>.json(0600) и переживает перезапуск.
Одобрение действует в пределах одного браузера: разрешив агенту Firefox, ты не разрешил ему заодно и Chrome — там он спросит отдельно.
Клиент идентифицируется стабильным токеном из ~/.ffmcp/client-token (0600); MCP-сервер
и CLI используют один и тот же токен, поэтому одобрение «навсегда» покрывает оба. Если
пользователь не реагирует за FFMCP_APPROVAL_TIMEOUT_MS (по умолчанию 120000 мс), доступ
отклоняется (fail-closed). Полностью выключить гейт — FFMCP_APPROVAL=0 (для headless/CI).
Ограничение платформы: кнопок в нотификациях Firefox нет, поэтому подтверждать нужно именно в popup (нотификация только сигналит и открывает его).
Сокеты лежат в
~/.ffmcpс правами0600— доступны только твоему пользователю, никакой сети, ни локальной, ни внешней.Буферы
browser_consoleиbrowser_networkпишутся постоянно, со всех вкладок, и в них оседает то, что страницы логируют сами. На практике это бывают почта, идентификаторы аккаунта, тариф и прочие персональные данные — например,claude.aiпечатает в консоль полный набор трейтов аналитики. Любой подключённый агент прочитает это одним вызовом. Чистить буфер:browser_console {"clear": true}.Манифест хоста ограничивает подключение конкретным расширением:
allowed_extensionsсffmcp@localу Firefox,allowed_originsсchrome-extension://mifolcjccjkcdfdoebmkgklimogoemng/у Chrome.Чтобы временно всё отключить — выгрузи расширение (
about:debugging/chrome://extensions) или закрой браузер: сокет исчезает вместе с хостом.Логи хостов:
~/.ffmcp/firefox.logи~/.ffmcp/chrome.log.
Диагностика
Симптом | Причина |
| браузер не запущен либо расширение не загружено; |
| хост поднялся, но расширение отвалилось — жми «Переподключить» в попапе |
Значок расширения показывает | native-хост не запускается; смотри |
В Chrome ID расширения не | загружен каталог без поля |
| включи «Разрешить пользовательские скрипты» в карточке расширения Chrome |
| это внутренняя страница ( |
Вызовы висят, значок показывает | ждёт твоего подтверждения — открой popup ffmcp и выбери область доступа |
| никто не нажал подтверждение за отведённое время; повтори вызов и подтверди в popup |
Агент переспрашивает доступ каждый раз | одобрено «на подключение»; выбери «на сессию» или «навсегда» |
В Chrome пропали логи консоли | service worker уснул и буферы обнулились — логи копятся заново с этого момента |
This server cannot be deployed
Maintenance
Related MCP Connectors
Live browser debugging for AI assistants — DOM, console, network via MCP.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceAn MCP server paired with a Firefox extension that enables LLM clients to control the user's browser, supporting tab management, history search, and content reading.8322MIT
- AlicenseNot gradedqualityBmaintenanceAn MCP Server that enables AI assistants to interact with your local browsers.1,45154MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server that exposes Firefox browser automation capabilities through the Model Context Protocol, enabling AI assistants to control web browsers for navigation, interaction, and data collection tasks.4BSD 3-Clause
- AlicenseAqualityDmaintenanceA MCP server that gives AI assistants full browser control, enabling navigation, clicking, form filling, and screenshots via any MCP client.8MIT