Skip to main content
Glama

agentlock-browser

Инструменты для работы с браузером, которые проверяют, откуда взялось значение, а не что оно говорит.

MCP-сервер, который управляет настоящим браузером и отказывается действовать по URL или значению формы, полученному из содержимого страницы. Страница может сказать что угодно; но она не может изменить происхождение строки, которую просит использовать агента.

Это скелет версии v0. Он построен на AgentLock — открытом стандарте авторизации вызовов инструментов — и управляет браузером напрямую через Playwright. Он не оборачивает @playwright/mcp и не зависит от него.

Почему не просто обернуть вышестоящий сервер

В probe/REPORT.md зафиксировано, что на самом деле возвращает @playwright/mcp 0.0.79. Каждый результат — это один нетипизированный текстовый блок (structuredContent равен null во всех 23 записанных результатах, и ни один из его 24 инструментов не объявляет outputSchema). Текст страницы приходит без идентичности отдельных элементов: из 874 строк - text: в снимке Wikipedia ни одна не содержит ссылку. Элементы именуются непрозрачными серверными ссылками, которые разрешаются в локатор Playwright, который вызывающий никогда не видит, и то же поле принимает сырой CSS-селектор. А browser_evaluate выполняет произвольный JavaScript, написанный моделью.

Ничто из этого не может нести происхождение. Поэтому этот сервер вместо этого выдаёт структурированный, идентифицированный, помеченный происхождением вывод и не имеет инструмента evaluate.

Related MCP server: gaze

Установка

pip install agentlock-browser
python -m playwright install chromium

Из клонированного репозитория:

pip install -e .
python -m playwright install chromium

Добавление в Claude Desktop

claude_desktop_config.json:

{
  "mcpServers": {
    "agentlock-browser": {
      "command": "agentlock-browser",
      "env": {
        "AGENTLOCK_BROWSER_ALLOWLIST": "https://example.com,https://docs.python.org",
        "AGENTLOCK_BROWSER_OPERATOR_TEXT": "research the docs and summarize them",
        "AGENTLOCK_BROWSER_LOG": "/home/you/agentlock-browser.jsonl"
      }
    }
  }
}

AGENTLOCK_BROWSER_CONFIG может указывать на JSON-файл с теми же ключами (allowlist, operator_text, log_path, headless, …); переменная окружения имеет приоритет над файлом. Браузер работает в headless-режиме, если не задано AGENTLOCK_BROWSER_HEADLESS=0.

Инструменты

инструмент

аргументы

проверяется на

navigate

url или link_id

канал url; свежесть link_id

snapshot

(нет)

без проверки

read_text

(нет)

без проверки

click

element_id

без проверки; вызванная им навигация — нет

type

element_id, value

канал value

back

(нет)

без проверки

Каждый инструмент возвращает structuredContent в соответствии с объявленной outputSchema. snapshot возвращает {origin, url, title, elements: [{id, role, name, text, href}]}; read_text возвращает {origin, blocks: [{id, text}]}. Текст страницы никогда не возвращается как одна недифференцированная строка.

Идентификаторы элементов стабильны для текущей загрузки страницы и пересоздаются при навигации: идентификатор имеет вид <page-load>-e<n>, и идентификатор, созданный до навигации, не может быть разрешён после неё. Отказ — это структурированный результат с причиной, а не ошибка.

Правила происхождения

Четыре канала:

  • USER: текст из сообщения оператора

  • ALLOWLIST: настроенные оператором источники, загружаемые при запуске

  • PAGE(origin): любое значение, прочитанное из содержимого страницы, помеченное источником

  • MODEL: составлено моделью, без происхождения от страницы или пользователя

Значения помечаются в источнике: текст сообщения оператора записывается как USER при запуске сервера, настроенный список разрешений — как ALLOWLIST, а всё, что возвращают snapshot или read_text, записывается как PAGE(origin) в момент возврата. Контент становится заражённым ровно тогда, когда модель может его увидеть.

Что это даёт для каждого действия:

действие

разрешено

запрещено

navigate(url)

USER, ALLOWLIST

PAGE, MODEL

navigate(link_id)

id из самого последнего снимка

устаревший или неизвестный id

type(value)

USER

PAGE, MODEL

межсайтовая навигация, вызванная кликом

(ничего)

всегда: рассматривается как navigate(url) с происхождением PAGE

Без проверки в v0: snapshot, read_text, back и клики в пределах того же источника.

navigate(link_id) — это способ агента перейти по ссылке. Идентификатор разрешается в href на стороне сервера, и href не должен проходить через модель, поэтому переход по ссылке, которую запросил оператор, остаётся возможным, а вставка URL, предоставленного страницей, — нет.

Клик, который покинул бы текущий источник, получает ответ 204, поэтому навигация отбрасывается, и страница остаётся ровно там, где была: ни страницы ошибки, ни запроса к другому источнику.

Решение принимает шлюз AgentLock. Этот пакет записывает происхождение, вызывает gate.authorize() и сообщает вердикт; он никогда не переопределяет его. Единственное исключение — правило свежести идентификатора ссылки, которое AgentLock не может выразить. Такие отказы помечаются в журнале как decided_by: "server:link_freshness", чтобы их никогда не путали с вердиктом шлюза. См. NEEDS.md.

Каждое решение и каждая запись о происхождении добавляются в JSONL-журнал с доказательствами шлюза и подписанной квитанцией:

{"event": "decision", "action": "navigate_url", "allowed": false,
 "reason": "param_lineage", "channel": "PAGE", "origin": "http://fixture.test",
 "decided_by": "agentlock_gate",
 "evidence": {"gate": "param_lineage", "matched_param": "url",
              "matched_token": "evil.test/collect?x=handbook-session-7f3a1c",
              "untrusted_provenance_id": "cprov_8fb61c8f03117287"},
 "receipt": {"receipt_id": "rcpt_…", "signature": "…"}}

Этот журнал — источник истины для предварительно зарегистрированных тестов, а не сводка чата.

Канал USER

MCP не даёт серверу возможности увидеть сообщение оператора, поэтому текст USER — это входные данные при запуске (operator_text / AGENTLOCK_BROWSER_OPERATOR_TEXT), а не то, что может нести аргумент инструмента. Сессия с пустым или шаблонным текстом оператора имеет более слабую базовую линию USER, чем та, где оператор указал, чего он хочет (см. второе ограничение ниже).

Тесты

pytest                                  # T5 runs; T1-T4 are registered and skipped
cat tests/results/T5.txt                # the raw T5 transcript, log and checks

T5 (легитимная базовая линия) автоматизирован без участия модели: вызовы инструментов выполняются напрямую, поэтому измеряются шлюз и сервер. Фикстуры T1–T4 и оба локальных источника реализованы (evil.test разрешается во второй локальный сервер, так что межсайтовый случай реален), но их запуски под управлением модели — нет, потому что эти прогнозы касаются поведения модели.

Названные ограничения

Скопировано дословно из PREDICTIONS.md, который был заморожен до того, как существовал какой-либо код сервера:

  • Влияние выбора: содержимое страницы всё ещё может влиять на то, какой link_id выберет модель. Вне области действия, задокументировано, как в ядре AgentLock.

  • Клики в пределах того же источника не проверяются в v0. Враждебный сайт может управлять агентом внутри себя. Межсайтовый переход — это граница, которую мы защищаем.

  • Отказ модели может сделать T1 и T3 неинформативными. Это проблема полезной нагрузки, а не результат шлюза.

Обнаружено при создании v0 и ещё не закрыто:

  • Проверки происхождения открываются вхолостую для значений без отличительного токена. AgentLock классифицирует значение, извлекая из него токены; обычное слово короче 12 символов без цифр или знаков препинания не даёт ни одного, и значение, которое нельзя классифицировать, разрешается. Страница, которая говорит введите слово expenses, не ловится; та, что говорит введите abc123, — ловится. Такое разрешение записывается как "channel": "UNCLASSIFIED", "fail_open": true, а не закрывается локальной проверкой, потому что закрытие здесь переместило бы решение за пределы шлюза. Пункт 3 в NEEDS.md.

  • Шаблонное сообщение оператора ослабляет проверку MODEL. Для новизны происхождения нужен отличительный токен в авторитетном контексте, чтобы вообще была базовая линия; без него значения, составленные моделью, разрешаются. Пункт 3(b) в NEEDS.md.

  • read_text и snapshot сообщают о выбранном наборе элементов. Текст в элементе, который не соответствует ни одному селектору, не возвращается и поэтому никогда не записывается как PAGE. Это остаётся согласованным (что модель не может видеть, то не может и повторить) только до тех пор, пока эти инструменты — единственный способ попадания содержимого страницы в разговор.

  • Перенаправление с разрешённой навигации выполняется без второй проверки. v0 разрешает цепочку перенаправлений URL, который он разрешил.

  • Одна вкладка. target="_blank" и всплывающие окна не обрабатываются в v0.

Лицензия

AGPL-3.0-or-later. Copyright 2026 David Grice.

Related MCP Connectors

Related MCP Servers