Решил я дать агентам, живущим в docker-контейнере, доступ к браузеру на хосте (хочу наблюдать, что он там в браузере делает). Быстро и просто не получилось, пришлось разобраться и решить несколько проблем.
MCP-сервер chrome-devtools-mcp позволяет подключаться к Chromium и браузерам на его основе. Установил его в контейнер рядом с агентом:
npm install -g --ignore-scripts chrome-devtools-mcp@latest
На хосте у меня стоит Chromium, который необходимо запустить в режиме удалённой отладки:
chromium --remote-debugging-port="9222" \
--profile-directory="MCP Profile" \
--user-data-dir="/tmp/chromium-mcp"
Но подключится к нему из контейнера «в лоб» не вышло.
⭐️ Проблема 1. Chromium разрешает подключение только с
localhost, соответственно из контейнера напрямую подключиться не получается.Решается утилитой socat, которая позволяет проксировать запросы с внешнего интерфейса на
localhost:# Установка socat
sudo apt install socat
# Проксирование запросов с внешнего интерфейса через порт 9223
# на localhost, порт 9222
socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222
Для удобства запуска Chromium в режиме удалённой отладки накидал небольшой скрипт:
#!/bin/bash
set -euo pipefail
PROFILE_DIR="/tmp/chromium-mcp"
DEBUG_PORT=9222
FORWARD_PORT=9223
cleanup() {
trap - EXIT INT TERM
kill "$CHROMIUM_PID" "$SOCAT_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
chromium \
--remote-debugging-port="$DEBUG_PORT" \
--profile-directory="MCP Profile" \
--user-data-dir="$PROFILE_DIR" &
CHROMIUM_PID=$!
socat TCP-LISTEN:$FORWARD_PORT,fork,reuseaddr TCP:127.0.0.1:$DEBUG_PORT &
SOCAT_PID=$!
wait -n "$CHROMIUM_PID" "$SOCAT_PID"
⭐️ Проблема 2. Chromium разрешает доступ только с явным указанием IP, без использования доменного имени. То есть подключиться по адресу
http://host.docker.internal:9223 не выйдет.В конфигурации MCP-сервера используем переменную окружения
CHROME_DEVTOOLS_MCP_BROWSER_URL, в которую при запуске контейнера будем записывать реальный URL:"mcpServers": {
"chrome-devtools": {
"type": "stdio",
"command": "chrome-devtools-mcp",
"args": [
"--no-usage-statistics",
"--browser-url",
"${CHROME_DEVTOOLS_MCP_BROWSER_URL:-}"
]
}
}Перед запуском контейнера получаем IP-адрес с помощью команды (вместо
%IMAGE% нужно подставить имя образа):docker run --rm --add-host=host.docker.internal:host-gateway --entrypoint getent "%IMAGE%" hosts host.docker.internal | awk '{print $1}'Передаём переменную окружения в контейнер:
-e "CHROME_DEVTOOLS_MCP_BROWSER_URL=http://172.17.0.1:9223"
Контейнер запускаю тоже с помощью скрипта, в котором выполняются все эти действия. Выглядит примерно так:
image = "..."
docker_args=(
--rm -it --init --read-only
...
)
host_gateway_ip="$(docker run --rm --add-host=host.docker.internal:host-gateway --entrypoint getent "$image" hosts host.docker.internal | awk '{print $1}')"
docker_args+=(-e "CHROME_DEVTOOLS_MCP_BROWSER_URL=http://$host_gateway_ip:9223")
docker_args+=("$image")
exec docker run "${docker_args[@]}"
Немного заморочно, конечно, но после настройки всё просто: Chromium запускаем одним скриптом, контейнер с агентом другим.
🧐 Ещё момент. Eсли Chromium установлен как snap-пакет, то из-за особенностей Snap с ним можно будет работать только в headless-режиме (флаг
--headless). То есть агент сможет им пользоваться, но вы ничего не увидите — браузер будет запущен в фоне.