Раннер как MCP-сервер — дайте своему ИИ-агенту браузер
Любой агент, говорящий по MCP — Claude Code, Cursor, ваш собственный агент на
SDK — может управлять раннером Mockarty напрямую: открывать страницы, читать их,
кликать, вводить текст, делать скриншоты и управлять подключённым
Android-телефоном.
Браузерным инструментам не нужен сервер Mockarty, аккаунт или лицензионный
ключ. Скачайте раннер, запустите в режиме MCP, подключите агента — этой
поверхностью можно пользоваться где угодно, как альтернативой полноценному
стеку браузерной автоматизации.
Управление устройствами и остальные механики тестирования — часть платформы:
они появляются только после рукопожатия раннера с сервером Mockarty
(COORDINATOR_URL + API_TOKEN, на сборке с лицензионным ключом Mockarty).
Неспаренный раннер их вообще не показывает — вы всегда видите ровно те
инструменты, которые сработают.
Зачем это агенту
Полноценный браузер стоит 50–150 МБ на инстанс и медленно стартует. Раннер
использует лёгкий headless-движок: сотни параллельных сессий помещаются на
скромной машине, а страницы возвращаются текстом или markdown — именно тем,
что модель реально читает, без лишнего круга со скриншотами.
Запуск
# stdio — транспорт, который локальные MCP-клиенты запускают сами
mockarty-runner mcp
# или по сети (нужен токен)
MOCKARTY_RUNNER_MCP_TOKEN=ваш-секрет mockarty-runner mcp --http 127.0.0.1:9800
Подключите MCP-клиент. Для Claude Code — в .mcp.json:
{
"mcpServers": {
"mockarty-runner": {
"command": "mockarty-runner",
"args": ["mcp"]
}
}
}
Через HTTP:
{
"mcpServers": {
"mockarty-runner": {
"type": "http",
"url": "http://127.0.0.1:9800/mcp",
"headers": { "X-API-Key": "ваш-секрет" }
}
}
}
Инструменты
Сначала вызовите runner_info — он расскажет, что умеет эта машина (браузерный
движок, подключённые устройства, платформа), чтобы агент не гадал.
| Инструмент | Что делает |
|---|---|
runner_info |
Возможности раннера — вызывать первым |
| Смотреть | |
browser_snapshot |
Элементы управления как ref=N role "имя" — действуйте по ref, без выдумывания CSS. Огромная страница обрезается и сообщает об этом; browser_find всё равно дотянется до контрола за кэпом |
browser_read |
Содержимое страницы: text, markdown, outline, links. Очень большая страница обрезается (200 КБ / 500 ссылок) и ответ об этом сообщает — уточните селектором или browser_find за остальным |
browser_find |
Поиск элементов по видимому имени → refs |
browser_screenshot |
PNG страницы (когда важны пиксели). По умолчанию возвращает ссылку на скачивание (output="link"), чтобы большая картинка не топила контекст; output="base64" — только чтобы отдать пиксели vision-шагу |
browser_pdf |
Сохранить страницу в PDF → ссылка на скачивание (или output="base64"). Проверка печатной вёрстки, архивация. Только движок Chromium — на раннере с движком light не показывается |
| Навигация | |
browser_open / browser_goto |
Открыть сессию / перейти. browser_open принимает storageState (из browser_storage_state), чтобы стартовать уже залогиненным |
browser_back / browser_forward / browser_reload |
История |
browser_wait_for |
Ждать появления/исчезновения текста, селектора или паузу |
| Взаимодействие | |
browser_act |
click / fill / press — по ref или CSS-селектору |
browser_fill_form |
Заполнить всю форму за один вызов (+ опциональный submit-селектор или submitRef-ref из snapshot) — логин/регистрация одним тулом |
browser_hover |
Наведение (меню, подсказки) |
browser_select |
Выбор пункта(ов) в <select> |
browser_check |
Поставить/снять галочку у чекбокса или радио |
browser_upload |
Прикрепить файлы к file-инпуту |
browser_drag |
Перетащить элемент на другой — каждый конец по селектору (from/to) или ref из snapshot (fromRef/toRef) |
browser_key |
Клавиша странице (Escape, Enter, Control+A) |
browser_resize |
Сменить вьюпорт (адаптивность) |
| Вкладки и диагностика | |
browser_tabs |
list / new / select / close |
browser_capture |
Начать запись консоли и сети |
browser_console |
Записанный вывод консоли (ошибки JS) |
browser_network |
Записанные запросы (метод + URL) |
| Сессия | |
browser_eval |
Выполнить JS-выражение (результат обрезается ~200 КБ — возвращайте небольшое значение, не outerHTML) |
browser_sessions |
Список открытых сессий |
browser_storage_state |
Выгрузить cookies + localStorage (войти один раз и переиспользовать) |
browser_close |
Закрыть сессию |
| Проверки | |
browser_case |
Прогнать целый ТЕСТ-КЕЙС одним вызовом — JSON-массив шагов goto/back/forward/reload/act/select/check/key/hover/drag/resize/upload/fill_form/wait/assert → отчёт PASS/FAIL; останавливается на первом упавшем шаге. Тестирование браузерных кейсов сверх пошагового драйва |
browser_assert |
PASS/FAIL: текст (вхождение или regex), видимость, значение, количество, url, title, атрибут, checked, enabled |
browser_dialog |
Принять или отклонить следующий alert / confirm / prompt |
browser_element_screenshot |
Скриншот одного элемента (по умолчанию ссылка; output="base64" — инлайн) |
browser_visual_diff |
PASS/FAIL против baseline-картинки, которую вы передаёте — доля изменённых пикселей; на fail подсвеченный diff приходит ссылкой |
| Аудиты (из коробки — у Playwright MCP нет ни того, ни другого) | |
browser_perf |
Отчёт производительности: web-vitals уровня Lighthouse из Performance API — TTFB, DOM interactive / content-loaded, load, First Contentful Paint, best-effort Largest Contentful Paint, число запросов, переданные байты |
browser_a11y |
Аудит доступности: title/lang, структура заголовков, число landmark’ов и WCAG-проблемы — картинки без alt, контролы без label, кнопки/ссылки без доступного имени |
Мобильные инструменты (для спаренных раннеров)
Когда раннер подключён к серверу Mockarty И к нему подключено Android-устройство
(adb в PATH, отладка по USB включена), появляется мобильная поверхность —
всё, что нужно агенту для управления телефоном, без установки Appium:
| Инструмент | Что делает |
|---|---|
device_list |
Подключённые устройства |
device_info |
Размер экрана, плотность, версия Android, модель |
device_source |
Иерархия экрана как ref=N Class "текст" @x,y — действуйте по ref. Плотный экран обрезается и сообщает об этом; сузьте через filter или device_find |
device_find |
Поиск по тексту / content-desc / resource id |
device_tap |
Тап по ref (предпочтительно) или по x,y |
device_text |
Ввод текста в активное поле |
device_swipe |
Свайп по direction=up/down/left/right или координатам |
device_key |
back, home, enter, recent, delete, громкость… |
device_screenshot |
PNG экрана |
device_app |
launch / terminate / clear / current / list |
device_wait_for |
Ждать появления или исчезновения текста |
device_assert |
PASS/FAIL: text_contains, text_absent, element_visible, element_absent, app_is |
device_case |
Прогнать целый ТЕСТ-КЕЙС НА УСТРОЙСТВЕ одним вызовом — JSON-массив шагов app/tap/text/swipe/key/wait/assert → отчёт PASS/FAIL; останавливается на первом упавшем шаге (мобильный аналог browser_case) |
runner_info сообщает, в каком вы режиме, и — в публичном — как разблокировать
остальное.
Мобильный сценарий
device_app {"deviceId":"…","action":"launch","package":"com.example.app"}
device_wait_for{"deviceId":"…","text":"Вход"}
device_source {"deviceId":"…"} → ref=5 Button "Подключиться" @541,750
device_tap {"deviceId":"…","ref":5}
device_assert {"deviceId":"…","kind":"text_contains","expected":"Готово"}
Тап по ref, а не по пикселям, — именно это позволяет сценарию пережить другой
размер экрана и делает историю прогона читаемой.
Рабочий цикл
browser_open {"url": "https://example.com"} → sessionId
browser_snapshot {"sessionId": "s1"} → ref=2 textbox "Username", ref=1 button "Sign in"
browser_act {"sessionId": "s1", "ref": 2, "kind": "fill", "value": "demo"}
browser_act {"sessionId": "s1", "ref": 1} → клик
browser_wait_for {"sessionId": "s1", "text": "Welcome"} → дождаться асинхронного результата
browser_read {"sessionId": "s1", "mode": "markdown"} → проверить
browser_close {"sessionId": "s1"}
Используйте одну сессию на весь сценарий. browser_snapshot показывает, что
можно нажать; browser_read — что страница говорит. Действие по ref надёжнее
рукописного CSS-селектора и переживает перерисовки. После асинхронных действий —
browser_wait_for, а не опрос чтением.
Когда сценарий устоялся, оберните его в browser_case — прогон как один
повторяемый тест с единым вердиктом PASS/FAIL, как нужно CI:
browser_case {"url": "https://app/login", "steps": "[
{\"do\":\"fill_form\",\"fields\":[{\"selector\":\"#user\",\"value\":\"demo\"}],\"submit\":\"button[type=submit]\"},
{\"do\":\"wait\",\"text\":\"Welcome\"},
{\"do\":\"assert\",\"kind\":\"text_contains\",\"expected\":\"Welcome, demo\"}
]"}
→ CASE PASSED (3/3 steps) (или CASE FAILED at step N — останавливается там)
Чтобы прогнать кейс за логином одним вызовом, передайте storageState (JSON,
который вернул предыдущий browser_storage_state) вместе с url — свежая сессия
стартует уже аутентифицированной, и весь кейс идёт залогиненным без шага логина.
Требует движок Chromium (лёгкий движок не восстанавливает сохранённое состояние).
Типы шагов кейса: goto (переход), back/forward/reload (история навигации), act (click/fill/press по селектору или
ref), select (выбрать values в <select>), check (поставить галку —
checked по умолчанию true; передайте false, чтобы снять), key (клавиша —
Enter/Escape/Tab/ArrowDown), hover (навести указатель на селектор/ref —
раскрывает hover-меню), drag (перетащить источник селектор/ref на цель
to/toRef), resize (задать вьюпорт width×height — тестирование
адаптивности на любом разрешении), upload (прикрепить files с диска раннера
к file-<input> — по сетевому транспорту нужен RUNNER_MCP_UPLOAD_DIR),
fill_form (целая форма, опциональный
submit-селектор или submitRef), wait (ждать text/selector/gone, опциональный ms) и assert
(text_contains/text_matches/text_absent/visible/hidden/value/count/url_contains/title_contains/attribute/checked/enabled/disabled). Этого хватает на реальную форму —
включая выпадающие списки и чекбоксы — одним вызовом.
Разбор странного поведения страницы: browser_capture → воспроизвести →
browser_console и browser_network.
Чего он не сделает
- Только
httpиhttps.file://и прочие схемы отклоняются, поэтому
чтение страницы никогда не превратится в чтение локальных файлов. - Адреса проверяются. Метаданные облака и некорректные хосты отклоняются.
Если вы намеренно тестируете такой адрес —MOCKARTY_VIEW_ALLOW_ANY_TARGET=1.
Переключатель один и снимает ту же проверку для каждого браузера, которым
управляет Mockarty, — включая UI-тесты и интерактивные сессии, а не только
для этого. - Платформенные инструменты без рукопожатия не появятся. Устройства и
механики тестирования бэкенда — это возможности Mockarty; публичная
поверхность — браузер. - HTTP-транспорт требует токен. Эти инструменты управляют настоящим
браузером на вашей машине, поэтому без токена сетевой режим не стартует. - Относитесь к токену как к доступу к шеллу. Держатель токена может навести
браузер на любой http(s)-адрес, достижимый с вашей машины, прочитать то, что
рендерит любая страница, и приложить любой локальный файл, доступный процессу
раннера (browser_upload), к странице. Привязывайте--httpк loopback
(127.0.0.1) или доверенной сети и выдавайте токен только агентам, которым
доверяете так же, как доверили бы шелл на этой машине.
Параметры
| Флаг / переменная | Значение |
|---|---|
--http <адрес> |
Отдавать streamable HTTP вместо stdio |
--max-sessions N |
Лимит браузерных сессий (по умолчанию 100, LRU-вытеснение) |
--session-ttl D |
Время жизни простаивающей сессии (по умолчанию 10m) |
MOCKARTY_RUNNER_MCP_TOKEN |
Токен для HTTP-транспорта |
RUNNER_MCP_ENGINE |
Движок браузера: chromium (по умолчанию) или light |
RUNNER_BROWSER_AUTO_INSTALL |
0 — не скачивать браузер при первом запуске (изолированные хосты) |
RUNNER_ADB_BINARY |
Путь к adb, если его нет в PATH |
VIEWCORE_ACT_TIMEOUT_MS |
Сколько browser_act / browser_fill_form ждут элемент до ошибки (по умолчанию 15000). Уменьшите для ещё более быстрой реакции на неверный селектор; увеличьте для страниц, где элементы появляются медленно. |
RUNNER_MCP_UPLOAD_DIR |
По HTTP-транспорту browser_upload может читать файлы только из этой директории (удалённый клиент не должен читать произвольные файлы сервера). Не задано при HTTP = browser_upload запрещён. По stdio игнорируется — там клиент это локальный пользователь. |
Какой движок браузера
По умолчанию сервер работает на полном Chromium. Он открывает https://
на любой машине, а сам браузер скачивается автоматически при первом открытии
страницы — ставить руками ничего не нужно.
RUNNER_MCP_ENGINE=light переключает на лёгкий движок: он держит гораздо
больше параллельных сессий при той же памяти — это удобно для массовых
скриптовых прогонов. Для открытия https:// ему нужно хранилище сертификатов
хоста, поэтому используйте его на сервере, где оно есть, а не на ноутбуке.
servo-ядро рендера лёгкого движка меняет размер окна на каждый скриншот, поэтому
смена viewport между кадрами (browser_resize перед снимком, разные viewport)
действует на обоих движках — тест может снимать одну сессию в нескольких
разрешениях на любом из них.
Изолированная среда: раннер с движками внутри
Обычная сборка скачивает движок браузера при первом обращении. В изолированном
контуре скачивать неоткуда, поэтому раннер можно собрать самодостаточным:
лёгкие движки запекаются в бинарь и распаковываются при первом использовании.
Один файл, ноль сети, работает за air gap.
GOOS=linux GOARCH=amd64 bash scripts/fetch-engines.sh # один раз скачать движки
go build -tags embedengines -o mockarty-runner ./cmd/mockarty-runner
На выходе — единый бинарь (~270 МБ, движки внутри), который выполняет
browser_* вообще без скачиваний. Без тега получится «лёгкая» сборка (~70 МБ),
которая догружает движки по требованию — это правильный выбор, когда на машине
есть интернет.
Настоящие браузеры Playwright (Chromium/Firefox/WebKit) остаются отдельной
подключаемой историей — они не запекаются никогда. Либо укажите раннеру заранее
установленный браузер, либо пропишите пути к заранее положенным файлам движков
через RUNNER_LIGHTPANDA_PATH / RUNNER_SERVO_PATH.
Что дальше
Тот же раннер может войти в грид Mockarty (COORDINATOR_URL + API_TOKEN) и
выполнять UI-, нагрузочные, фаззинг- и мобильные тесты, приходящие с платформы,
с отчётами, историей и гридом устройств. См. Браузерный
раннер и Метки раннеров.