daedalus
Daedalus
Удалённое управление браузером через расширение Chrome. Eval-мост + постоянные хотфиксы + управление по вкладкам + скриншоты, CDP, куки, сетевой перехват на страницах, где Chrome запускает расширение.
Установка
Знайте это до установки: обычный eval использует инъекцию в MAIN-world без баннера. Если пробная CSP-проверка без исходного кода не может установить, что динамическая компиляция доступна, Daedalus пробует запасной вариант через CDP; успешное подключение показывает баннер Chrome «Daedalus started debugging this browser», пока работает этот запасной вариант. Сохранённая сессия CDP или перехват сети могут удерживать подключение дольше. Баннер обозначает подключение отладчика, а не гарантию целостности значений.
Загрузите распакованное расширение (extension/) в Chrome:
Откройте
chrome://extensionsВключите Режим разработчика
Нажмите Загрузить распакованное, выберите
extension/
Уникальный токен автоматически генерируется при первой установке и хранится в chrome.storage.local. Просмотреть/измените его на странице параметров расширения (значок пазла → Daedalus → Options).
Для параллельного скрапинга на нескольких вкладках отключите фоновую регулировку вкладок Chrome:
chrome --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgroundingКак это работает
Токен: генерируется один раз при установке через
crypto.randomUUID(), хранится вchrome.storage.localID вкладок: нативные API
tabsChrome — каждая вкладка идентифицируется своим ChrometabId, регистрируется при создании/обновлении с 30-секундным heartbeat черезchrome.alarmsОдин SSE-поток:
background.jsоткрывает одно постоянноеfetch-соединение SSE (tab=extension) и распределяет входящие команды по нужной вкладкеМост страницы:
content.js(мир ISOLATED) ретранслирует сообщенияwindow.GMмеждуbackgroundиpage.js(мир MAIN). Дляevalсначала используется инъекция в MAIN-world черезchrome.scripting. Проверка динамической компиляции без исходного кода маршрутизирует страницы, заблокированные CSP, на CDP; если подключение там не удалось, управление переходит к ретрансляции страницы. Каждое выполнение использует семантику MAIN-world страницы.Hotfixes: хранятся в расширении
chrome.storage.localпо ключуdaedalus-hotfixes(не привязаны к токену), воспроизводятся на каждой подходящей верхнеуровневой странице при загрузке и версионно ограничены для непостоянных исправлений. Смена токена не изолирует и не очищает это хранилище.
Отправка команд
Daedalus предоставляет командную поверхность расширения как MCP-сервер по адресу <your-bridge>/mcp (транспорт streamable-HTTP). Перед отправкой запроса он требует, чтобы значение Bearer точно совпадало с мостом токеном, заданным через существующий путь конфигурации CLI: TOKEN переопределяет DAEDALUS_TOKEN, включая необязательный провайдер _settings. Без настроенного токена MCP-поверхность закрыта по умолчанию и возвращает ошибку 401. Добавьте его в Claude Code (или любой MCP-клиент) с помощью:
{
"mcpServers": {
"daedalus": {
"url": "https://daedalus.example.com/mcp",
"headers": { "Authorization": "Bearer <your-bridge-token>" }
}
}
}Токен моста — это тот, который расширение генерирует при установке; он виден на странице параметров расширения (иконка пазла → Daedalus → Options).
Описанный пример показывает MCP-сервер за публичным именем, но транспорт по умолчанию разрешает только адреса из loopback (127.0.0.1:*,localhost:*): укажите публичное имя хоста в DAEDALUS_MCP_ALLOWED_HOSTS, иначе проксированные запросы будут отклонены. Настройки MCP — см. в разделе «Server» ниже.
40 инструментов в 7 группах — вкладки, eval/debug, медиа, cookies, CSS/blocking, хотфиксы, network/CDP. Полный список — в разделе <mcp> файла CLAUDE.md, либо вызовется tools/list на MCP endpoint.
Вспомогательный инструмент ручной проверки (MCP-клиент не нужен):
TOKEN=<tok> python3 scripts/mcp_probe.py list
TOKEN=<tok> python3 scripts/mcp_probe.py call title '{"tab_id":"<tabId>"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call screenshot '{"include_image":true}'CLI
В этом репозитории поставляется оболочка CLI как wheel daedalus-cli (daedalus_cli/), устанавливается как команда daedalus. Он читает DAEDALUS_URL и DAEDALUS_TOKEN из окружения: TOKEN — разовое переопределение, а ID=<tabId> — адресация на конкретную вкладку (опустите его — отправится всем):
DAEDALUS_TOKEN=<tok> daedalus tabs
DAEDALUS_TOKEN=<tok> ID=<tabId> daedalus title
DAEDALUS_TOKEN=<tok> daedalus exec myid 'document.title'daedalus --help перечисляет все подкоманды, у каждой есть собственный --help. Код exec — это выражение или тело функции, возвращаемое значение которого передаётся как результат — см. «Отправка команд» выше.
Есть и опциональная точка импорта: если модуль _settings импортируется через sys.path, CLI использует его функции setting(name, default) и required(name) для DAEDALUS_URL и DAEDALUS_TOKEN встроенной резервной среды. Переменная TOKEN остаётся разовым переопределением токена и имеет приоритет; без _settings для DAEDALUS_URL по умолчанию берётся default, а DAEDALUS_TOKEN обязателен. ID остаётся переменной окружения для конкретной вкладки.
Или atomically publish raw=command file (no MCP, no CLI). Direct записи в окончательный .json не поддерживается, потому что поток может прочитать файла раньше, чем писатель закончит. Создайте соседний файл, оканчивающийся на .tmp, и переименуйте его в той же директории:
# Broadcast to all tabs
commands_dir="$DAEDALUS_DIR/commands"
final="$commands_dir/<token>.json"
tmp="$(mktemp "$commands_dir/.<token>.XXXXXX.tmp")"
printf '%s\n' '{"id":"test1","code":"document.title"}' > "$tmp" &&
mv "$tmp" "$final"
# Target a specific tab
final="$commands_dir/<token>_<tabId>.json"
tmp="$(mktemp "$commands_dir/.<token>_<tabId>.XXXXXX.tmp")"
printf '%s\n' '{"id":"test1","code":"document.title"}' > "$tmp" &&
mv "$tmp" "$final"Читатель игнорирует соседние имена .tmp. Если старый писатель оставил некорректный JSON под финальным именем, читатель не трогает его и повторяет попытку, а не удаляет, возможно незавершённую запись. После атомарного переименования SSE-поток доставляет и обрабатывает команду. Результат попадает в /tmp/результаты/<token>_<tabId>.json (на вкладку) and $DAEDALUS_DIR/results/<token>.json (last-writer-wins), для page-main инъекции и page-relay результат может включать поле exec_ms — время выполнения в контексте страницы в миллисекундах. Страница может подделать или опустить это поле, определённое на странице, на любом канале. CDP-результат не содержит exec_ms. Результаты, связанные с отложенной доставкой, также содержат roundtrip_ms — полное время обхода, наблюдаемое сервером: от постановки команды в очередь (PUT /command) до прихода результата (POST /result) — включая ожидание в очереди + доставку SSE + ретрансляцию клиента + выполнение + обратного пути, поэтому roundtrip_ms − exec_ms приближённо равно транспортной/очередной задержке, когда оба поля присутствуют. Однако эти измерения используют разные часы: exec_ms использует performance.now() страницы, а roundtrip_ms — серверные настенные часы в миллисекундах, поэтому их разница — приближение, а не точное вычитание. Когда от старого без _did нет поля roundtrip_ms.
Поддержка Async
Канал по умолчанию page-main выполняет выражения через принадлежащий странице eval, а тела функций — через принадлежащий подбер. Асинхронная обёртка даёт верхний уровень await. Перед отправкой исходного кода фон вставляет только постоянный зонд Function. Если зонд вернул true, инъекция исходника пробуется один раз, и каждый исход результата терминален: значение, исключение или ошибка транспорта инъекции — возвращается без ретрая исходника на CDP. Страница владеет Function и может влиять на этот маршрут, но сам зонд не содержит отправляемого кода, поэтому выбор другого канала не может продублировать его побочные эффекты.
Если зонд без исходного кода не возвращает true (скорее всего, из-за того, что CSP страницы блокирует динамическую компиляцию), Daedalus пробует CDP. Успешное подключение показывает баннер отладчика в Chrome. Runtime.evaluate использует режим REPL для верхнего await; код, содержащий return, интерпретируется как тело функции, если обёртка-зонд не распарсит его как выражение. Эта обёртка-зонд — лишь эвристика парсера, а не неисполняемая граница безопасности: операторный код может её обойти и выполниться. После диспетчеризации финального вызова CDP любой результат становится окончательным. Только ошибка подключения или формы до выполнения отправленного кода передаётся в ретрансляцию страницы.
У метаболизма промисов CDP есть предел в 10 секунд, и объекты результата и исключений освобождаются даже если сессия или перехват удерживают подключение отладчика.
JavaScript на странице, которой вы не редактируете, вернёт значение, которое страница вправе выбрать — независимо от канала. Поле world фиксирует только канал, который выполнил код; это диагностические метаданные для CSP и отладки, а не признак доверия. Его значения: page-main для обычной MAIN-инъекции, cdp для резервного инспектора и page:<hostname> для финальной ретрансляции, где <hostname> — dynamic? Нет: <hostname> — это location.hostname контент-скрипта. Фон добавляет префикс page:, поэтому имя ретрансляции не может пересечься с cdp или page-main; такой факт именования ничего не говорит о результата.
CLI отображает поле как channel=..., то же делает dashboard, и MCP-вызовы exec, result и ping сохраняют сохранение точного значения world. Ничье не изображает это поле признаком доверия.
По умолчанию и CDP fallback сохраняют классическое правильное поведение для документированных случаев нестрогого режима: with компилируется, старые восьмеричные литералы принимаются, а глобальное присваивание без переменной создаёт глобальную. В REPL CDP также можно повторно объявлять let или const. Ожидание CLI и MCP по умолчанию — 15 секунд; таймаут вызова не отменяет уже запущенный код страницы. Blob relay separately waits up to 10 seconds for await kode, and in other cases 3 seconds before fire fallback timeout.
Корреляция ретрансляций описательна и ограничена: вкладка-отправитель должна совпадать, одно принятое сообщение потребляет случайный идентификатор relay, и идентификатор не регистрируется, пока не завершён безопробежный проф и pre-dispatch CDP fallback. Эти меры предотвращают перекрёстное завершение или дублирование ретранслей; но они не гарантируют целостность данных.
Допускается не более 1000 активных записей page relay. Новый fallback with load получает терминальную ошибку вместимости, не удаляя активные записи. Каждая запись истекает через 300 000 мс и получает терминальную ошибку по таймауту, если завершение в той же вкладке или предыдущая ошибка отправки не удалит её раньше.
Панель управления
Веб-панель управления по адресу <your-bridge>/dashboard, которая управляет расширением без CLI — list вкладок, eval REPL, скриншоты, cookies, hotfixes, CSS rules, network capture, CDP, CSS-injection, fetch timings, upload browser.
Откройте URL во вкладке, которую контролирует расширение.
Прокрутите до §12 Settings и вставьте токен со страницы параметров расширения (иконка пазла → Daedalus → Options). Сохраните.
Живая точка SSE в верхней панели станет голубой, когда панель подписана на живые события.
Приложение отдаёт server.py из каталога репозитория dashboard/ (чистый JS + ES-модули, без сборки). Живые обновления идут через существующий /stream: server.py ставит события в commands/<token>_dashboard/<ts>_<uuid>.json при обновлении существующего таба через /register, и на успешных пустых путях /sync-tabs, /unregister, /result; при этом /unregister генерирует даже если таба нет.
Панель получает их как события kind:'event'.
Внимание: контент и page.js скрипты расширения внедряются во подходящие страницы, включая вкладку панели. Команды broadcast (exec -b) работают также и внутри панели — выбирайте обращение к конкретной вкладке или закройте панель перед broadcast- по разрушительному коду.
Самопатч
Hotfixes
Постоянно применяются небольшие патчиy, перезаписываемые при загрузке каждой achievable top-level страницы. Через MCP tools: store_hotfix, list_hotfixes, clear_hotfix, clear_hotfixes, set_permanent. Пример (через probe script):
TOKEN=<tok> python3 scripts/mcp_probe.py call store_hotfix '{"fix_id":"my-fix","code":"console.log(\"patched\")"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call store_hotfix '{"fix_id":"always-on","code":"console.log(\"baseline\")","permanent":true}'
TOKEN=<tok> python3 scripts/mcp_probe.py call set_permanent '{"fix_id":"my-fix","permanent":true}'
TOKEN=<tok> python3 scripts/mcp_probe.py call list_hotfixes
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfix '{"fix_id":"my-fix"}'
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfixes
TOKEN=<tok> python3 scripts/mcp_probe.py call clear_hotfixes '{"include_permanent":true}'Хотфиксы по умолчанию ограничены версией расширения. После смены версии расширения сохранённые непостоянные исправления пропускаются, но не удаляются; сохранение исправления переводит запись на текущую версию и снова запускает её непостоянные. Пометив фикс как permanent (с permanent: true в store_hotfix или через set_permanent), вы принудительно применяете его при смене версии. clear_hotfixes by default removes non-permanent while preserving permanent; passoty include_permanent: true — удаляется весь store.
Команды расширения
Фоновая service worker принимает типизированные команды (отправленные через MCP или JSON с полем "type": "..." в $DAEDALUS_DIR/commands/):
Команда | Назначение |
| Сделать снимок видимой вкладки в формате PNG |
| Отправлять низкоуровневые вызовы Chrome DevTools Protocol |
| Полный перехват запросов/ответов через CDP |
| Доступ к хранилищу cookie |
| Управление вкладками |
| Внедрение CSS для конкретной вкладки |
| Блокировка через declarativeNetRequest |
| Управление хотфиксами (постоянные исправления переживают обновления версий) |
| Перезагрузить само расширение с диска |
| Диагностический кольцевой буфер для fetch-ретрансляций |
GM Bridge
window.GM (в page.js, в MAIN-мире) предоставляет такое подмножество в стиле Tampermonkey:
Метод | Описание |
| Читает нерезервированный строковый ключ из |
| Записывает нерезервированный строковый ключ в общее хранилище расширения |
| Удаляет нерезервированный строковый ключ из общего хранилища расширения |
| Перечисляет нерезервированные ключи хранилища |
| HTTP-запрос через фоновую ретрансляцию (не блокируется CSP) |
| Внедряет CSS |
| Записывает в буфер обмена |
| Показывает системное уведомление |
| Открывает новую вкладку |
| Запускает скачивание |
| Метаданные скрипта |
Доступ к cookie — это возможность оператора, а не страницы: он осуществляется через перечисленные выше команды cookies / set-cookie / remove-cookie / clear-cookies, защищённые токеном, и намеренно не открыт контексту страницы — page.js выполняется в каждой подходящей странице верхнего уровня, которая в противном случае могла бы читать cookie, недоступные её собственному document.cookie.
Архитектура
Browser (matching tab) Server (your bridge host)
┌────────────────────────────────────────────┐ ┌──────────────────────┐
│ MAIN world │ │ bridge (server.py) │
│ ├─ page-main: default injection channel │ │ /stream?token │
│ └─ page.js: GM + relay channel │ │ watches commands/ │
│ ▲ │ │ │
│ │ window.postMessage │ │ /result writes │
│ content.js (ISOLATED) │ │ results/ │
│ ▲ │ │ │
│ │ chrome.runtime │ │ │
│ background.js (service worker) │ │ │
│ ├─ CDP: CSP fallback channel │ │ │
│ ├─ single SSE stream ◄────────────────────┼───┤ │
│ └─ POST result, fetch ────────────────────┼──►│ │
└────────────────────────────────────────────┘ └──────────────────────┘Один SSE-поток: Фоновый процесс открывает единственное SSE-соединение через
fetch(tab=extension) и направляет команды на нужную вкладку черезchrome.tabs.sendMessage. Сторожевой таймер на 30 секунд принудительно переустанавливает соединение, если поток устарел.Маршрутизация по вкладкам: Команды адресуются конкретному
tabIdChrome либо рассылаются на все вкладки.Поведение CSP:
GM.xmlhttpRequestотправляет HTTP-работу вfetchфонового service worker. Eval по умолчанию использует внедрение в MAIN-мир без баннера. Если проверочный зонд без исходного кода сообщает, что динамическая компиляция недоступна, CDP предоставляет запасной вариант для CSP и показывает баннер отладчика Chrome, если подключение установлено; при неудачном подключении срабатывает ретрансляция на страницу/через Blob. Итоговое значениеworldописывает этот канал и не заявляет о целостности.
Сервер
DAEDALUS_DIR=<data-dir> DAEDALUS_PORT=<port> DAEDALUS_TOKEN=<bridge-token> python3 server.pyЗапускайте мост со всеми тремя обязательными настройками: без DAEDALUS_DIR или DAEDALUS_PORT он завершается при старте, а все маршруты управления мостом блокируются (fail closed) без настроенного токена.
server.py работает под любым супервизором, который вы используете (здесь — systemd unit). Если вы открываете его доступным не только через localhost, поместите перед ним TLS-терминирующий обратный прокси; сам мост работает по обычному HTTP.
Маршруты управления мостом и маршруты хранилища сравнивают токен с единственным настроенным секретом, который определяется через путь конфигурации CLI: TOKEN — одноразовое переопределение, в противном случае требуется DAEDALUS_TOKEN (встроенный модуль _settings может его предоставить). При отсутствующей конфигурации или несовпадении значений запросы закрываются отказом. Только маршруты, обращённые к странице, POST /segment и GET /segment-status, используют вместо этого полномочие, ограниченное заданием.
Встроенный MCP-интерфейс принимает три необязательных настройки, описанных вместе, поскольку они описывают один слушатель: DAEDALUS_MCP_PORT (по умолчанию 8086) — это loopback-порт, к которому он привязывается; его обработчики инструментов используют фактический привязанный loopback-URL моста, в том числе при DAEDALUS_PORT=0; DAEDALUS_LOCAL_URL явно переопределяет этот URL для автономного развёртывания MCP, стоящего перед мостом, работающим где-то ещё; DAEDALUS_MCP_ALLOWED_HOSTS (по умолчанию 127.0.0.1:*,localhost:*) — разделённый запятыми список разрешённых хостов, который принимает защита от DNS-rebinding, поэтому публикация /mcp за публичным именем хоста подразумевает указание этого имени в данном списке.
Эндпойнты: GET /stream, GET /tabs, GET /health, GET /dashboard[/<asset>], POST /register, POST /sync-tabs, POST /unregister, POST /poll, POST /result, PUT /command, GET /result, POST/GET/DELETE /upload, GET /screenshot, POST /segment-job, POST /segment + GET /segment-status. POST /segment-job требует настроенный токен моста; только POST /segment и GET /segment-status принимают sig, ограниченный заданием. Описание форматов и примечания к эндпойнтам — см. CLAUDE.md.
POST /poll при наличии потребляет и удаляет устаревший файл широковещательных команд.
PUT /command помещает команду в очередь FIFO на основе каталога для каждой цели (последовательные команды к одной вкладке больше не перезаписывают друг друга), помечает каждую из них идентификатором доставки (_did), чтобы расширение могло дедуплицировать повторно доставленное сообщение, и отбрасывает по TTL команды, не востребованные в течение DAEDALUS_CMD_TTL секунд (по умолчанию 90). Фоновый сборщик применяет этот TTL и удаляет пустые каталоги очереди, даже если никакой SSE-потребитель ни разу не подключался. GET /health сообщает о живости потока/реестра/последней доставки для обнаружения молчаливо умершего моста.
Мост отвергает повторяющиеся носители авторизации вместо того, чтобы выбрать одно из значений: это касается token в строке запроса или JSON-теле и job / sig на маршрутах сегментных capability, даже если повторные значения совпадают или пусты. Транспорт MCP точно так же отклоняет повторные заголовки Authorization, Mcp-Session-Id, Host и Origin, а также повторные аргументы job для сегментных инструментов.
Когда расширение публикует результат, сервер экспонирует _did команды как deliveryId и присваивает свежее значение resultGeneration. Ожидающие CLI и MCP сначала заглядывают в общий слот результата, сопоставляют id команды и deliveryId, после чего подвержённо одной, а потом потребляют эту генерацию с GET /result?...&consume=1&expected=<resultGeneration>. Если другой результат заменит слот между этими запросами, условное потребление оставляет новый результат на месте и сообщает, что результат извлечён не был. Одинокое consume=1 без expected по-прежнему является разрушительным совместимым чтением текущего слота.
GET /stream держит SSE-соединение открытым без ограничения времени, подтверждая живость keepalive-комментарием каждые DAEDALUS_STREAM_KEEPALIVE секунд (по умолчанию 15), а не пересоздавая соединение по таймеру; DAEDALUS_STREAM_MAX_AGE (по умолчанию 3600) — лишь крайняя верхняя граница. Путь закрытия закреплён тестом tests/test_stream_lifecycle.py.
DAEDALUS_MAX_BODY_SIZE (по умолчанию 64 * 1024 * 1024 байт, 64 MiB) ограничивает тело запросов, читаемых обработчиками POST, PUT и DELETE; при объявленном размере больше лимита сервер возвращает 413. Увеличьте его, когда требуется ретранслировать более крупную скачивания или другие данные.
Значения на файловоминании следуют единой политике для компонентов пути: отклоняются .., управляющие символы C0/C1 и суррогаты, недопустимые в Windows символы пути и имена устройств, завершающие точки или пробелы, а также UTF-8-последовательности длиннее 240 байт. Для токенов моста правила применены более бесплатно — они также запрещают точки и подчёркивания. Остальные UTF-8-имена заданий принимаются, поэтому клиенты должны URL-кодировать их в строках запроса.
POST /segment-job копирует три фиксированные квоты в каждую новую запись задания: DAEDALUS_MAX_SEGMENT_INDEX (по умолчанию 99999), DAEDALUS_MAX_SEGMENTS_PER_JOB (по умолчанию 10000) и DAEDALUS_MAX_SEGMENT_JOB_SIZE (по умолчанию 4 * 1024 * 1024 * 1024 байт, 4 GiB). Изменение этих настроек влияет на последующие создаваемые задания; задание с хранёнными квотами продолжает использовать записанные в нём значения. Кроме того, DAEDALUS_MAX_BODY_SIZE ограничивает каждый отдельный запрос сегмента.
Безопасность
Прочтите это перед установкой расширения.
Оно внедряет обёртку window.GM в каждую подходящую страницу верхнего уровня (<all_urls>, MAIN-мир), и это позволяет скрипту, отправленному через put, совершать запросы к другим источникам. К последствию стоит отнести
Маршруты управления мостом и хранения требуют настроенного токена моста; /segment и /segment-status — единственное исключение по использованию capability. Сервер сравнивает токены запросов с секретом, получаемым из TOKEN или DAEDALUS_TOKEN, и отклоняет запросы, когда секрет не настроен. POST /segment-job требует этот токен моста, потому что он выпускает capability, ограниченную задачей; недоверенный JavaScript на странице может использовать эту возможность только для отправки и опроса своей задачи. Тот, у кого есть токен моста, может управлять вашим браузером. Не открывайте порт моста за пределами loopback без обратного прокси, который завершает TLS, и относитесь к токену как к учётному данным.
Результаты eval не несут никакой гарантии целостности значений. JavaScript, выполненный на странице, которую вы не контролируете, возвращает значение, которое эта страница может выбрать, независимо от того, какой канал его выполнил. Поле world сообщает, как выполнялся поданный исходный код, а не стоит ли доверять его значению: page-main — обычная инъекция в MAIN-world, cdp — резервный путь инспектора при CSP, page:<hostname> — ретранслятор. Обязательный префикс page: добавляется вне контекста страницы, поэтому никакой hostname не может породить cdp или page-main; эта устойчивость к коллизиям носит описательный характер. В режиме page-main привязки eval и Function, принадлежащие странице, могут прочитать поданный исходный код, а также повлиять на его возвращаемое значение.
Компиляция через CDP не разрешает привязки eval и Function страницы; реализация получает прямые ссылки по ссылке перед их сериализацией. Эти транспортные механизмы не меняют границу доверия: поданный исходный код по-прежнему читает состояние страницы и может направить любое значение, включая примитив, через промис-инфраструктуру страницы до того, как его получит CDP. Когда поданный исходный код использует эти управляемые страницей пути, страница может выбирать и значение, возвращённое на cdp тоже.
Ретранслятор принимает только сохранённый вызов, связанный с этим случайным идентификатором, требует, чтобы вкладка отправителя совпадала, и потребляет запись один раз. Пониженный канал не раскрывает токен моста, URL сервера, идентификатор доставки или маршрут результата, не даёт дополнительных прав расширению или браузеру и не может затронуть вызов в другой вкладке. Эти свойства защищают маршрутизацию и полномочия браузера; они не превращают маркер промежуточной страницы или его возвращаемое JavaScript-значение в сигнал доверия.
Развёртывание
Мост говорит по обычному HTTP на loopback. Маршруты управления мостом и хранения требуют настроенного токена и закрываются при его отсутствии (fail closed); только /segment и /segment-status используют capability в рамках задачи. Две вещи он намеренно НЕ делает, потому что они относятся к тому, что располагается перед ним:
TLS и CORS.
server.pyне отправляет заголовки CORS. Когда мост используется с другого origin,fetch-вызовы на странице в примере HLS-ретранслятора требуют доступ и кGET /segment-status, и кPOST /segment; прокси должен разрешать origin страницы на обоих маршрутах и обрабатывать методы/заголовки, требуемые preflight-запросом для POST. Если GET статуса блокируется, недоступен, возвращает не-2xx или некорректен, пример считает задачу новой и повторно POST всех сегментов. Такие записи по-прежнему заменяют те же файлы по индексам, но запуск теряет экономию от возобновления/пропуска. Если развёртывание не сможет обеспечить такую CORS-политику, ведите оба запроса через GM-мост расширения.Отдача сохранённых файлов. Дашборд даёт ссылки на скачивание на
/uploads/<path>, но такого моста нет в составе моста. Не отдавайте имена и байты загруженных файлов, переданных вызывающей стороной, напрямую из origin дашборда, где исполняемое содержимое оказалось бы на одном origin с хранящимися токен-содержащими данными дашборда. Либо оставьте эти ссылки недоступными, либо настройте/uploads/редиректом на отдельный download-only origin, который нужно обязательно принудительно использоватьContent-Disposition: attachment, используетapplication/octet-streamи отправляетX-Content-Type-Options: nosniff.GET /upload(список) иGET /screenshotобслуживает сам мост, им прокси не нужен.
Запустите его напрямую с настроенным токеном — и обе эти вещи просто отсутствуют; всё остальное работает.
Файлы
Файл | Описание |
| Манифест MV3 |
| Service Worker — SSE, отправка команд, ретрансляция fetch, скриншоты, CDP, cookies, загрузки |
| Обмен сообщениями между страницей и фоновым модулем |
| Мост в MAIN-world — |
| UI настроек токена и сервера |
| Отладочный сервер (также запускает поток MCP-демона на 127.0.0.1:8086) |
| MCP-сервер — связывает командную поверхность расширения с |
| Минимальный вспомогательный MCP-клиент для ручного проверить |
| Проверка согласованности версий / их обнов осью по всем местам версий |
| Гейты согласованности версий pre-commit и pre-push |
| Консольный CLI, публикуемый в виде пакета |
| Интерфейс управления браузером, который |
| Скрипты для запуска на странице через |
| Наборы тестов; |
Примеры
В examples/ лежат скрипты, предназначенные для запуска на странице через put. Пять из шести показывают отдельные части моста, а не конкретный сайт; пример с Discord намеренно привязан к сайту, потому что прокрутку назад по виртуализированному элементу невозможно показать без настоящего виртуализированного списка:
Пример | Что демонстрирует |
| Постоянный hotfix в MAIN-world на |
| Ретрансляция через |
| Поиск и уничтожение экземпляра проигрывателя, который не прекращает повторные попытки |
| Заполнение поля, управляемого React, чтобы изменилось действительно состояние самого React |
| Прокрутка назад по виртуализированному списку сообщений и его извлечение |
| Минимально возможный вызов |
Они получают конфигурацию через подстановку ____INIT__ перед отправкой, потому что put отправляет скрипт, а не запускает функцию, и у него нет способа передать аргументы.
Каждый из них — это тело (BODY) асинхронной функции, а не отдельный скрипт: мост оборачивает то, что вы отправляете. Поэтому большинство заканчивается верхнеуровневым return, а один из них — scrape-discord-messages.js — также использует верхнеуровневый await; оба варианта допустимы в тех местах, где они реально работают. node --check разбирает каждый файл внутри обёртки CommonJS, которая разрешает верхнеуровневой return, но отвергает верхнеуровневой await — поэтому именно этот один файл падает на проверке синтаксиса.
Разработка
Строка версии живёт сразу в нескольких местах: в расширении, в дашборде и в CLI-пакете. python3 scripts/check_versions.py — это перечень: он называет каждое место и сообщает, сколько нашёл, поэтому в этом абзаце не продублировано количество, которое устарело бы при добавлении слешет.
Обновляйте эти версии все вместе и никогда вручную:
python3 scripts/check_versions.py --set 0.18.0 # rewrite every site
python3 scripts/check_versions.py # verify the working tree.githooks/pre-commit проверяет индекс, а .githooks/pre-push проверяет каждый отправленный коммит, поэтому частично не обновлённая версия не уйдёт. Каждый клон должен один раз явно включить хуки — иначе git не запустит их из директории, находящейся под контролем версий:
git config core.hooksPath .githooksThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Access Kernel's cloud-based browsers and app actions via MCP (remote HTTP + OAuth).
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Nitjsefnie-Harness-Commons/daedalus'
If you have feedback or need assistance with the MCP directory API, please join our Discord server