Toolkit для управляемой браузерной автоматизации на Playwright. Проект нужен, чтобы агент или скрипт могли работать с обычным Chromium как с устойчивой сессией: открыть сайт, пройти UI, нажать кнопку скачивания, прочитать страницу, сделать скриншот, проверить логин и переиспользовать cookies между шагами.
Главная идея: не писать каждый раз сырой Playwright-код, а использовать набор готовых операций с нормальным поиском элементов, persistent profile, download handling и MCP-интерфейсом для Codex.
- Запускает persistent Chromium-сессии с отдельным профилем и папкой скачиваний.
- Переиспользует активную сессию через
get_or_start_session. - Навигирует, кликает, вводит текст, нажимает клавиши и скроллит страницу.
- Ищет элементы по видимому тексту, CSS, XPath, ARIA/role и RegExp.
- Работает с модалками и portal-heavy UI через
scopeиscopeSelector. - Делает introspection страницы: видимые элементы, поля, кнопки/ссылки, диалоги, активный scope, описание конкретного target.
- Безопасно обрабатывает downloads через browser download event.
- Читает содержимое страницы как markdown/readable/raw.
- Делает скриншоты.
- Делает прямые HTTP-запросы через cookies активной browser-сессии.
- Проверяет cookies и примерный signed-in статус.
- Публикуется как stdio MCP server для Codex и других MCP clients.
npm install
npx playwright install chromiumnode_modules/ не хранится в git. Если папки нет после чистого clone, это нормально.
import {
get_or_start_session,
navigate,
click,
type,
get_content,
screenshot,
close_session
} from "./src/browser-tools.mjs";
const session = await get_or_start_session({
headless: false,
userDataDir: "%USERPROFILE%/.rootlord/browser/sessions/default",
downloadDir: "%USERPROFILE%/.rootlord/browser/downloads/default"
});
await navigate("https://example.com", { sessionId: session.sessionId });
await click("More information", { sessionId: session.sessionId });
const page = await get_content({
sessionId: session.sessionId,
format: "markdown"
});
const image = await screenshot({
sessionId: session.sessionId,
full_page: true
});
await close_session({ sessionId: session.sessionId });В примерах ниже показаны JS-вызовы. При работе через MCP вызывай одноимённые tools с теми же аргументами, а для RegExp используй targetPattern и targetFlags.
Запуск MCP server:
npm run mcpТо же самое напрямую:
node ./src/browser-mcp-server.mjsПример подключения в %USERPROFILE%\.codex\config.toml:
[mcp_servers.browser_toolkit]
command = "node"
args = ["C:/path/to/rootlord/browser/src/browser-mcp-server.mjs"]После изменения MCP config перезапусти Codex, иначе новые native tools могут не появиться. Пустой resources/list для этого сервера нормален: server публикует tools, а не resources.
- Создай или переиспользуй сессию:
const session = await get_or_start_session({
headless: false,
userDataDir: "%USERPROFILE%/.rootlord/browser/sessions/work",
downloadDir: "%USERPROFILE%/.rootlord/browser/downloads/work"
});- Открой страницу:
await navigate("https://www.investing.com/", {
sessionId: session.sessionId
});- Если UI непонятен, сначала посмотри, что на странице:
await list_buttons_links({ sessionId: session.sessionId, limit: 30 });
await list_inputs({ sessionId: session.sessionId, includeHidden: true });
await get_dialogs({ sessionId: session.sessionId });
await describe_target("Download", { sessionId: session.sessionId });- Выполни действие:
await click("Download", {
sessionId: session.sessionId,
expectDownload: true
});- Закрой сессию в конце задачи:
await close_session({ sessionId: session.sessionId });start_session(options) всегда запускает сессию, если sessionId ещё не занят. get_or_start_session(options) сначала ищет активную сессию и только потом запускает новую. Для агентских flow обычно лучше get_or_start_session.
Основные опции:
headless:false, если нужно видеть браузер;trueдля фонового режима.sessionId: своё имя/ID сессии.userDataDir: папка persistent профиля Chromium. По умолчанию вне проекта:%USERPROFILE%/.rootlord/browser/sessions/<sessionId>.downloadDir: папка для скачанных файлов. По умолчанию вне проекта:%USERPROFILE%/.rootlord/browser/downloads/<sessionId>.reuseExistingилиreuse_existing: переиспользовать активную сессию.stealth: по умолчанию включён; помогает на сайтах с anti-bot проверками.proxy: строка или объект Playwright proxy.locale,timezoneId,userAgent,viewport: параметры browser context.timeoutMs: базовый timeout. Слишком большие значения toolkit ограничивает по типу операции.
Посмотреть активные сессии:
await list_sessions();Закрыть одну сессию:
await close_session({ sessionId: "work" });Закрыть активную сессию:
await close_session();click, type, exists, wait_for, describe_target понимают разные target-форматы:
- Видимый текст:
"Download","Sign in". - RegExp в JS API:
/download/i. - RegExp в MCP:
targetPattern: "download", targetFlags: "i". - CSS selector:
"css=input[name='email']"или обычный CSS, если строка похожа на selector. - XPath:
"xpath=//button[contains(., 'Download')]". - Индекс при нескольких совпадениях:
indexилиmatchIndex. - Скрытые поля:
allowHidden: true.
Пример для hidden date inputs:
await type("2025-04-21", "input[type='date']", {
sessionId: session.sessionId,
allowHidden: true,
index: 0
});
await type("2026-04-21", "input[type='date']", {
sessionId: session.sessionId,
allowHidden: true,
index: 1
});Если на странице есть modal, drawer, dropdown portal или overlay, сначала проверь активный scope:
await get_active_scope({ sessionId: session.sessionId });
await get_dialogs({ sessionId: session.sessionId });Действие внутри активной модалки:
await click("Sign in with Email", {
sessionId: session.sessionId,
scope: "active_dialog"
});Действие внутри конкретного контейнера:
await type("user@example.com", "css=input[name='email']", {
sessionId: session.sessionId,
scopeSelector: "css=[data-floating-ui-portal]"
});Для кнопок вроде Download, Export, CSV, Excel, PDF используй click, а не page.evaluate(() => element.click()). Toolkit заранее подписывается на browser download event и сохраняет файл.
const result = await click("Download", {
sessionId: session.sessionId,
expectDownload: true,
downloadDir: "%USERPROFILE%/.rootlord/browser/downloads/manual"
});
console.log(result.download);Полезные опции:
expectDownload: явно ждать download event.downloadTimeoutMs: timeout ожидания скачивания.downloadDir: папка сохранения.downloadPathилиsaveDownloadAs: полный путь или имя файла.copyDownloadTo: дополнительная копия в папку или полный путь.
Если нужно скачать URL без открытия страницы:
await download_url({
url: "https://example.com/file.csv",
saveDir: "%USERPROFILE%/.rootlord/browser/downloads/api",
useSessionCookies: true,
sessionId: session.sessionId
});useSessionCookies: true позволяет использовать cookies активной browser-сессии для приватных файлов/API.
get_content читает текущую страницу:
await get_content({
sessionId: session.sessionId,
format: "markdown"
});Форматы:
markdown: HTML очищается через Turndown.readable: сначала извлекается основная статья через Mozilla Readability, потом markdown.raw: возвращается исходное тело.
fetch_url делает HTTP-запрос через request context:
await fetch_url({
url: "https://example.com/api/data",
method: "GET",
useSessionCookies: true,
sessionId: session.sessionId,
extract: "markdown"
});Для JSON toolkit пытается заполнить json. Для бинарных ответов возвращает base64.
Обычный wait:
await wait_for({
sessionId: session.sessionId,
waitTextIncludes: "Done",
waitTimeoutMs: 10000
});Клик с последующим ожиданием:
await click_and_wait_for("Learn more", {
sessionId: session.sessionId,
waitUrlIncludes: "iana.org"
});Ввод с последующим ожиданием:
await type_and_wait_for("hello", "css=input[name='q']", {
sessionId: session.sessionId,
waitTextIncludes: "Search"
});Для длинных сценариев лучше run_steps: меньше MCP round-trips, один session context, понятный список действий.
await run_steps({
steps: [
{
action: "get_or_start_session",
headless: false,
userDataDir: "%USERPROFILE%/.rootlord/browser/sessions/investing",
downloadDir: "%USERPROFILE%/.rootlord/browser/downloads/investing"
},
{
action: "navigate",
url: "https://www.investing.com/indices/mcx-historical-data"
},
{
action: "click",
target: "Download",
expectDownload: true
}
]
});list_sessions: список активных сессий.start_session: запуск persistent Chromium.get_or_start_session: переиспользовать активную сессию или создать новую.navigate: открыть URL.click: кликнуть target, включая download buttons.type: ввести текст в target или активное поле.press_key: нажать клавишу.scroll: прокрутить страницу.exists: дешёвая проверка наличия target.click_and_wait_for: клик и ожидание результата.type_and_wait_for: ввод и ожидание результата.run_steps: выполнить несколько действий в одном MCP call.get_content: получить содержимое страницы.screenshot: сделать PNG screenshot.wait_for: дождаться target/URL/title/text.list_visible_elements: список видимых элементов.list_inputs: список input/select/textarea, при необходимости hidden.list_buttons_links: список кнопок и ссылок.get_dialogs: список dialog/modal-like элементов.get_active_scope: описание активного элемента/scope.describe_target: диагностика конкретного target.fetch_url: HTTP request, опционально с cookies сессии.download_url: скачать URL в файл.get_cookies: получить cookies текущего context.check_signed_in: грубая проверка, есть ли признаки авторизованной сессии.close_session: закрыть сессию.
Не коммить:
.cache/node_modules/.env,.env.**.pem,*.key,*.p12,*.pfx- скачанные файлы с приватными данными
Browser profile содержит cookies, localStorage, sessionStorage, IndexedDB и следы посещённых сайтов. Это может быть эквивалентно утечке логина.
По умолчанию toolkit хранит такие данные вне проекта:
%USERPROFILE%/.rootlord/browser/sessions/...%USERPROFILE%/.rootlord/browser/downloads/...
Если userDataDir, downloadDir, savePath, saveDir или copyDownloadTo указывают внутрь папки проекта, toolkit выбросит ошибку и не будет туда писать browser state/download data.
Папку состояния можно переопределить переменной окружения ROOTLORD_BROWSER_STATE_DIR, но она тоже должна указывать вне проекта.
get_cookies возвращает чувствительные значения. Не вставляй его полный вывод в issue, README, prompt, логи или коммиты.
Если нужно полностью сбросить локальные браузерные сессии, закрой browser/session и удали %USERPROFILE%/.rootlord/browser/sessions. Если в такой папке были реальные логины и она попала наружу, разлогинь активные сессии на сайтах и перевыпусти токены.
Установить зависимости:
npm installЗапустить MCP server:
npm run mcpПроверить, что в git не попали профили и зависимости:
git ls-files | rg "^(.cache|node_modules)/"Команда не должна ничего вывести.