Skip to main content
Glama
README.md
# 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

A3.8/5.0

Scored across 10 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues