marionette-mcp
# marionette-mcp
**MCP-сервер поиска и браузерной автоматизации на настоящем Firefox.** Управляет
браузером через встроенный `Marionette`. Нужен только установленный `Firefox`.
```
Агент ──MCP──▶ marionette-mcp ──Marionette(TCP)──▶ Firefox ──▶ веб
```
> По-русски: MCP для веб-ресёрча на родном `Firefox`. Ставится из git, ключей и
> квот нет.
## Зачем
`Firefox` уже стоит у человека, `Marionette` встроен в него, а мы говорим с ним
голым TCP и обычным stdlib. Браузер — настоящий, отпечаток честный.
## Требования
- **Firefox** установлен в системе (`firefox` в `PATH`, либо задай `FIREFOX_BIN`).
- Python 3.10+.
- Больше ничего: зависимость одна — `mcp`.
## Установка (из git, PyPI нет)
```bash
python3 -m venv ~/.venvs/marionette-mcp
~/.venvs/marionette-mcp/bin/pip install "git+https://github.com/aidvizhhub/marionette-mcp"
```
Проверка живьём:
```bash
~/.venvs/marionette-mcp/bin/python -c "
from marionette_mcp.marionette import Firefox
from marionette_mcp import search
with Firefox() as ff:
results, engines, blocked, note = search.search(ff, 'zig 0.14 release notes', 5)
print('движки:', engines, '| без выдачи:', blocked, '|', note)
for r in results:
print(r['url'])
"
```
## Подключение к MCP
Конфиг opencode (`~/.config/opencode/opencode.jsonc`), серверы живут под
`mcp.servers.<name>`:
```jsonc
{
"mcp": {
"servers": {
"marionette": {
"type": "local",
"command": ["/home/<user>/.venvs/marionette-mcp/bin/marionette-mcp"],
"enabled": true
}
}
}
}
```
## Инструменты
| Тул | Что делает |
|---|---|
| `ping` | проверка связи |
| `web_search(query, max_results=10, pages=1)` | поиск по двум-трём движкам, результаты сливаются по RRF; у каждой ссылки сниппет; `pages` листает выдачу |
| `fetch_page(url, max_chars=12000, query="", format="markdown")` | текст страницы в markdown; `query` возвращает куски по теме; страж SSRF |
| `fetch_many(urls, max_chars=4000, max_pages=10)` | пачка страниц за один вызов |
| `fetch_links(url, max_links=100)` | ссылки со страницы: текст + адрес |
| `screenshot(url="", width=1280)` | PNG (в headless — страница целиком), `width` задаёт ширину окна |
| `save_cookies(path="")` | сохранить куки сессии в JSON |
| `load_cookies(path="")` | загрузить куки из JSON в сессию |
| `browser_eval(js, url="")` | выполнить свой JS на странице, вернуть результат JSON |
| `status()` | счётчики: запросы, кэш, блоки движков, блоки SSRF |
## Документация
| Файл | Про что |
|---|---|
| [docs/architecture.md](docs/architecture.md) | как устроено: слои, брокер, поиск, почему так |
| [docs/configuration.md](docs/configuration.md) | все переменные окружения по группам, диагностика |
## Настройки (окружение)
Всё через переменные окружения. Полный список с пояснениями —
[docs/configuration.md](docs/configuration.md). Коротко:
| Переменная | По умолчанию | Что делает |
|---|---|---|
| `FIREFOX_BIN` | `firefox` | путь к браузеру |
| `MARIONETTE_HEADLESS` | `1` | `0` — показать окно (headed) |
| `MARIONETTE_PROFILE` | — | постоянный профиль: куки и логины живут между запусками |
| `MARIONETTE_REQUESTS_PER_MINUTE` | `30` | потолок запросов к одному хосту |
| `MARIONETTE_MIN_INTERVAL` | `0.3` | минимальная пауза между запросами, с |
| `MARIONETTE_MAX_CHARS` | `100000` | потолок текста страницы |
| `MARIONETTE_MAX_RESULTS` | `50` | потолок числа результатов |
| `MARIONETTE_SEARCH_ENGINES` | `2` | сколько движков должны дать выдачу |
| `MARIONETTE_ENGINE_TRIES` | `4` | сколько движков максимум опросить (пустые и блоки не в счёт) |
| `MARIONETTE_RRF_K` | `60` | параметр `k` в RRF (меньше — сильнее влияет ранг) |
| `MARIONETTE_PROXY` | — | прокси браузера: `socks5://host:port` или `http://host:port` |
| `MARIONETTE_IDLE_SECONDS` | `900` | столько секунд без работы — браузер гасится (освобождает ~650 МБ) |
| `MARIONETTE_BROKER` | `1` | `1` — один Firefox на все инстансы через брокера; `0` — свой браузер |
Пути (профили, кэш, скриншоты, куки) — тоже переменные, см. docs.
## Безопасность
- **Страж SSRF.** `fetch_page` проверяет адрес до навигации и после: частные,
петлевые, link-local и служебные диапазоны (`127.0.0.0/8`, `10/8`, `192.168/16`,
`169.254.169.254`, `::1` и т. п.) не проходят. Имя хоста резолвится, проверяется
каждый полученный IP — имя вроде `127.0.0.1.nip.io` тоже блокируется.
- **Разметка недоверенного контента.** Текст страниц и выдача поиска приходят
обёрнутыми в `<untrusted-content source="...">` с пометкой, что это данные, а
не команды. Модель не должна выполнять инструкции со страниц.
- **Лимиты и устойчивость.** Запросы к одному хосту разносятся (по умолчанию не
чаще 30 в минуту, `MARIONETTE_REQUESTS_PER_MINUTE`) с минимальной паузой
`MARIONETTE_MIN_INTERVAL` (0.3 с). Ответ обрезается: `MARIONETTE_MAX_CHARS`
(100 000) и `MARIONETTE_MAX_RESULTS` (50). Нетекстовые документы (PDF и т. п.)
приходят пометкой, а не мусором. Таймауты навигации повторяются с паузой.
Ответы и страницы кэшируются на `MARIONETTE_CACHE_TTL`; капчу и ошибки в кэш
не кладём. Поиск идёт по цепочке движков DuckDuckGo lite → DuckDuckGo html →
Brave → Bing, а результаты сливаются по **RRF**
(`score = Σ weight/(k + rank)`): кто нашёлся у нескольких движков — выше,
дубли склеиваются, адреса нормализуются. Пустой или заблокированный движок
не оставляет поиск с одним источником — идём к следующему.
- **Честная граница:** проверка идёт после перехода, поэтому при редиректе
Firefox успевает выполнить запрос; мы лишь **не отдаём** ответ с внутреннего
адреса. Полностью исключить DNS-rebinding средствами браузера нельзя.
## Честно про границы
- Сейчас **без спуфинга отпечатка**: браузер настоящий, но и все его сигналы —
тоже. Против стен, которые режут автоматизацию, это не лечение.
- Блоки движков упираются в **IP и частоту**, а не в браузер. Поиск через
датацентр-IP будет ловить капчу у кого угодно.
- Пока нет кликов и форм — для этого рядом живёт `playwright`. Кэш держит страницы
и выдачу, но **капчу и ошибки не кэширует**.
- Скриншот в headless снимает страницу **целиком** по высоте: подрезать высоту
нельзя, ширину — параметром `width`. Большие страницы дают тяжёлый файл, тул про
это предупреждает.
- `MARIONETTE_PROXY` уводит трафик браузера через прокси, но **ослабляет** страж
SSRF: адреса проверяются локально, а SOCKS резолвит DNS сам. Включай осознанно.
- **Один Firefox на все инстансы.** opencode держит сервер на каждый каталог, но
браузером владеет брокер, а клиенты ходят к нему по локальному TCP. Составную
операцию клиент держит арендой, поэтому параллельные поиски не перемешивают
страницы. Без клиентов брокер гасит браузер и выходит. `MARIONETTE_BROKER=0`
возвращает прежний режим — свой браузер на инстанс.
## Разработка
```bash
pip install -e .
ruff check .
```
## Лицензия
MIT — см. [LICENSE](LICENSE).
TDQS
Scored across 10 tools
Each tool has a clearly distinct purpose: ping/status are separate diagnostics, web_search vs fetch_page vs fetch_links are differentiated by WHEN/NOT WHEN guidance, and fetch_many is explicitly a batch form of fetch_page. browser_eval, screenshot, and cookie tools are also unambiguous.
All names use snake_case, which is consistent. Some are single nouns (ping, status, screenshot) and some use noun_verb order (web_search, browser_eval), but the pattern is still readable and predictable.
10 tools is well-scoped for a Firefox-backed search and page-fetching server. Diagnostics, fetching, evaluation, screenshots, and cookie persistence each earn their place without excessive surface area.
The surface covers search, single and batch page fetching, link extraction, JS evaluation, screenshots, and cookie save/load. Minor gaps exist around explicit navigation/click/type tools and cookie clearing, but browser_eval provides a workaround for interaction.