Skip to main content
Glama
README.md
# browser-control-mcp

Локальный MCP-сервер с открытым исходным кодом для безопасного управления существующей Chrome-сессией через Playwright и Chrome DevTools Protocol.

Codex вызывает MCP tools, сервер управляет вкладками в Chrome, а действия с повышенным риском требуют отдельного подтверждения. Автоматизация hh.ru остаётся специализированным модулем проекта. Проект не является официальным продуктом Google, Chromium или hh.ru.

## Что делает MVP

- Подключается к существующей локальной Chrome-сессии через CDP.
- Открывает, читает и закрывает вкладки, нажимает элементы и заполняет поля.
- Сохраняет авторизацию внутри Chrome и не возвращает cookies или browser storage MCP-клиенту.
- Классифицирует риск действий и требует одноразовое подтверждение для опасных операций.
- Блокирует платёжные действия, покупки и денежные переводы.
- Предоставляет специализированные сценарии автоматизации hh.ru.
- Открывает ссылку вакансии в Chrome с текущей пользовательской сессией.
- Проверяет, что пользователь авторизован на hh.ru.
- Парсит вакансию.
- Помогает подготовить сопроводительное письмо.
- Находит вопросы работодателя.
- Заполняет ответы и письмо только после подтверждения пользователя.
- Не отправляет отклик без явного подтверждения.

## Архитектура

```mermaid
flowchart LR
    User["Пользователь"] --> Codex["Codex"]
    Codex --> MCP["Local MCP server"]
    MCP --> HH["hh.ru flows"]
    MCP --> BrowserControl["Universal browser-control"]
    HH --> Playwright["Playwright connectOverCDP"]
    BrowserControl --> Playwright
    Playwright --> Chrome["Существующая Chrome-сессия пользователя"]
```

## Стек

| Компонент            | Выбор                                 |
| -------------------- | ------------------------------------- |
| Язык                 | TypeScript                            |
| Runtime              | Node.js 20+                           |
| MCP                  | `@modelcontextprotocol/sdk`           |
| Управление браузером | Playwright + Chrome DevTools Protocol |
| Валидация            | Zod                                   |
| Логирование          | pino                                  |
| Тесты                | Vitest                                |

## Локальный запуск

```bash
npm install
cp .env.example .env
npm run typecheck
npm test
```

Для MVP Chrome нужно запустить с локальным CDP-портом. Это даёт Playwright возможность подключиться к уже существующему профилю Chrome, где есть ваша hh.ru-сессия.

### macOS

```bash
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-address=127.0.0.1 \
  --remote-debugging-port=9222 \
  --user-data-dir="$HOME/Library/Application Support/Google/Chrome"
```

### Linux

```bash
google-chrome \
  --remote-debugging-address=127.0.0.1 \
  --remote-debugging-port=9222 \
  --user-data-dir="$HOME/.config/google-chrome"
```

Если команда `google-chrome` недоступна, проверьте имя бинарника:

```bash
which google-chrome chromium chromium-browser
```

### Windows PowerShell

```powershell
& "C:\Program Files\Google\Chrome\Application\chrome.exe" `
  --remote-debugging-address=127.0.0.1 `
  --remote-debugging-port=9222 `
  --user-data-dir="$env:LOCALAPPDATA\Google\Chrome\User Data"
```

Если Chrome установлен в 32-bit путь:

```powershell
& "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" `
  --remote-debugging-address=127.0.0.1 `
  --remote-debugging-port=9222 `
  --user-data-dir="$env:LOCALAPPDATA\Google\Chrome\User Data"
```

Важно: CDP-доступ к Chrome очень мощный. Не открывайте debug-port на внешний интерфейс. Для MVP используйте только `127.0.0.1`.

## Переменные окружения

```env
NODE_ENV=development
LOG_LEVEL=info
CDP_URL=http://127.0.0.1:9222
ALLOWED_DOMAINS=hh.ru,ramenskoe.hh.ru
DRY_RUN=true
REQUIRE_CONFIRMATION=true
BROWSER_ALLOWED_DOMAINS=
BROWSER_DENIED_DOMAINS=
BROWSER_ALLOW_PRIVATE_NETWORK=false
APPLICATION_STORE_PATH=storage/applications.json
```

## Модель безопасности

MVP намеренно ограничивает автоматизацию:

- без хранения логинов, паролей, cookies и токенов;
- без автоматического входа в аккаунт;
- по умолчанию включён dry-run;
- без отправки отклика без явного подтверждения пользователя;
- остановка на неизвестных вопросах или недостающих данных кандидата.
- universal browser-control не возвращает cookies, localStorage, sessionStorage,
  hidden inputs или значения полей;
- платёжные действия, покупки и денежные переводы полностью заблокированы в MVP;
- sensitive/irreversible действия выполняются только по одноразовому токену;
- private network и опасные URL-схемы по умолчанию заблокированы.

## Реализованные MCP tools

| Tool                                     | Ответственность                                                                  |
| ---------------------------------------- | -------------------------------------------------------------------------------- |
| `health_check`                           | Вернуть конфигурацию локального MCP-агента                                       |
| `inspect_vacancy`                        | Открыть allowlisted hh.ru URL, проверить сессию, проверить страницу и распарсить |
| `prepare_required_cover_letter_response` | Проверить обязательность письма и создать одноразовый токен подтверждения        |
| `submit_required_cover_letter_response`  | Заполнить одобренное письмо и выполнить один submit, если выключен dry-run       |
| `prepare_attach_cover_letter`            | Проверить возможность добавить письмо к уже отправленному отклику                |
| `submit_attach_cover_letter`             | Добавить одобренное письмо к существующему отклику по одноразовому токену        |
| `prepare_response_with_cover_letter`     | Подготовить отклик с необязательным письмом и создать одноразовый токен          |
| `submit_response_with_cover_letter`      | Откликнуться и сразу отправить письмо в той же вкладке                           |
| `browser_list_pages`                     | Получить открытые Chrome-вкладки и их CDP `pageId`                               |
| `browser_open_page`                      | Открыть разрешённый публичный HTTP(S) URL                                        |
| `browser_get_page`                       | Получить ограниченный semantic snapshot без значений полей                       |
| `browser_click`                          | Нажать элемент либо подготовить confirmation token                               |
| `browser_fill`                           | Заменить значение поля без логирования текста                                    |
| `browser_type`                           | Дописать текст в поле без логирования текста                                     |
| `browser_close_page`                     | Закрыть вкладку; изменённая вкладка требует подтверждения                        |
| `browser_wait_for`                       | Дождаться элемента, текста или URL                                               |
| `browser_screenshot`                     | Вернуть PNG в памяти без сохранения на диск                                      |
| `browser_execute_confirmed_action`       | Один раз выполнить точное действие по confirmation token                         |

`inspect_vacancy` пишет локальные диагностические логи в stderr. Это важно для MCP stdio: stdout должен оставаться чистым JSON-RPC транспортом.

Flow обязательного письма всегда состоит из двух вызовов. Токен действует 10 минут,
привязан к вакансии и точному тексту письма и используется только один раз.
При `DRY_RUN=true` второй tool открывает и заполняет форму, но не нажимает финальную
кнопку. MCP-сервер не сохраняет письмо на диск и не пишет письмо или токен в логи.

Для отклика, который уже доставлен без письма, используется отдельная пара
`prepare_attach_cover_letter` -> `submit_attach_cover_letter`. Токены двух flow
имеют разное назначение и не могут использоваться взаимозаменяемо. Если письмо
уже приложено, tool возвращает идемпотентный статус `already_attached`.

Для вакансий с необязательным письмом используется пара
`prepare_response_with_cover_letter` -> `submit_response_with_cover_letter`.
После одного клика «Откликнуться» tool классифицирует результат в той же вкладке:

- вопросы работодателя возвращаются как `EmployerQuestion[]` без заполнения;
- открытая форма письма заполняется подтверждённым текстом и отправляется один раз;
- после доставки резюме кнопка приложения письма ожидается до трёх секунд;
- если бот работодателя успел убрать кнопку, возвращается частичный успех
  `resume_submitted_letter_unavailable`.

`executionMode: "full"` выполняет полный сценарий. `executionMode: "start_only"`
разрешает только первый разрушительный шаг и не отправляет письмо. При `DRY_RUN=true`
оба режима безопасны: основная кнопка «Откликнуться» не нажимается. Если письмо
обязательно, prepare-tool возвращает статус `required_cover_letter` и токен для
существующего `submit_required_cover_letter_response`.

## Универсальное управление браузером

Модуль `src/browser-control` работает поверх той же Chrome CDP-сессии, но не зависит
от hh.ru. Cookies остаются внутри пользовательского `BrowserContext`: MCP использует
авторизованные вкладки, не читая и не передавая cookies наружу.

`pageId` — Chrome CDP `targetId`. Пока вкладка и Chrome-процесс живы, MCP может после
собственного перезапуска заново перечислить targets и продолжить работу с тем же ID.
После закрытия вкладки ID становится stale. После перезапуска Chrome автоматического
восстановления по URL/title нет, потому что одинаковых вкладок может быть несколько.

Риски определяет сервер:

| Риск           | Поведение                                                           |
| -------------- | ------------------------------------------------------------------- |
| `read`         | Выполняется без подтверждения                                       |
| `low`          | Выполняется сразу                                                   |
| `sensitive`    | Возвращается одноразовый token с TTL 2 минуты                       |
| `irreversible` | Всегда требуется одноразовый token                                  |
| `critical`     | Payment/purchase/transfer блокируются без возможности подтверждения |

`requestedRisk` может повысить риск, но не понизить вычисленный сервером. Перед
confirmed action повторно проверяются `pageId`, URL, origin, версия документа и
fingerprint элемента. После необратимого клика автоматических retry нет.

По умолчанию разрешены публичные HTTP(S)-домены. Ограничить их можно через
`BROWSER_ALLOWED_DOMAINS`, запретить отдельные — через `BROWSER_DENIED_DOMAINS`.
Доступ к localhost/private network включается только явно:

```env
BROWSER_ALLOW_PRIVATE_NETWORK=true
```

## Планируемые MCP tools

| Tool                    | Ответственность                                |
| ----------------------- | ---------------------------------------------- |
| `parse_resume_context`  | Извлечь контекст резюме кандидата              |
| `fill_question_answers` | Заполнить ответы в видимые поля                |
| `submit_response`       | Поддержать остальные варианты отклика          |
| `screenshot`            | Вернуть текущее состояние браузера для отладки |

## Разработка

Перед изменениями читайте `PROJECT_CONTEXT.md`. Это рабочая память проекта, её нужно обновлять после важных решений.

Проект также используется как учебная TypeScript-практика. Поэтому крупные фичи лучше делать маленькими шагами: сначала объяснить интерфейсы и границы модулей, затем реализовать один слой, проверить тестами и только потом двигаться дальше.