Skip to main content
Glama

tv-debug-mcp

MCP-сервер для полуручного прогона QA-кейсов на реальных Smart TV (Tizen / webOS) и на локальном Chrome — через Chrome DevTools Protocol. Агент управляет приложением: навигация пультом, лонгтап с точными таймингами, переходы по меню, чтение консоли и состояния плеера. Человек подтверждает то, что можно проверить только глазами.

Закрывает боль ручного тестирования сложных кейсов (лонгтап, перемещения, меню) на всём парке устройств, включая старые.

Быстрый старт

git clone https://github.com/Ediand11/tv-debug-mcp.git
cd tv-debug-mcp
npm install                                    # за корп-прокси: env -u HTTP_PROXY -u HTTPS_PROXY npm install
cp devices.example.json devices.json           # devices.json в .gitignore — ваш парк остаётся локальным
npm run check:browser                          # зелёный прогон без ТВ: свой Chrome + встроенная фикстура

Дальше — зарегистрировать сервер в своём MCP-клиенте, см. «Установка в MCP-клиенты». Для Claude Code это одна команда из корня репозитория:

claude mcp add tv-debug --scope user -- node "$PWD/src/server.js"

Тулы появятся как mcp__tv-debug__*. Проверить, что MCP видит парк: попросить агента вызвать tv_devices.

Чтобы гонять своё приложение, а не фикстуру:

  1. в devices.json описать устройство (platform, appId, host для ТВ или url для браузера) — поля и их проверки описаны в «Парк устройств»;

  2. завести apps/<id>.json с селекторами приложения и сослаться на него полем "app" — см. «App-профиль», готовый пример лежит в apps/fixture.json;

  3. для ТВ — Developer Mode на устройстве и подключённый sdb / ares.

Node ≥ 18. Зависимости: @modelcontextprotocol/sdk, ws, source-map-js (чистый JS-порт source-map 0.6, без wasm — важно для офлайн-запуска).

Related MCP server: paparazzi

Установка в MCP-клиенты

Сервер — обычный stdio-MCP: команда node <абсолютный путь>/src/server.js, ни портов, ни демона. Дальше отличается только синтаксис конкретного клиента.

Claude Code

claude mcp add tv-debug --scope user -- node "$PWD/src/server.js"

⚠️ "$PWD" раскрывает оболочка в момент claude mcp add, а не Claude при запуске сервера: в конфиг уезжает уже готовый абсолютный путь. Поэтому команду обязательно выполнять из корня репозитория — иначе в конфиге окажется путь к тому каталогу, где вы стояли. Проверка — claude mcp list: там должен стоять абсолютный путь до src/server.js.

Codex CLI

~/.codex/config.toml:

[mcp_servers.tv-debug]
command = "node"
args = ["/absolute/path/to/tv-debug-mcp/src/server.js"]
startup_timeout_sec = 30

[mcp_servers.tv-debug.env]
TV_DEBUG_CONFIG = "/absolute/path/to/devices.json"

Переменные окружения — отдельная таблица [mcp_servers.<имя>.env], а не ключ внутри блока сервера: в TOML всё, что идёт после [mcp_servers.tv-debug], принадлежит этой таблице, и вложенный объект объявляется своим заголовком.

OpenCode

~/.config/opencode/opencode.json, ключ mcp, тип local. Переменные окружения здесь — ключ environment, не env, а команда — массив, а не строка:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "tv-debug": {
      "type": "local",
      "command": ["node", "/absolute/path/to/tv-debug-mcp/src/server.js"],
      "enabled": true,
      "environment": {"TV_DEBUG_CONFIG": "/absolute/path/to/devices.json"}
    }
  }
}

Cursor, Windsurf, Cline, VS Code — схема mcpServers

Один и тот же объект, различается только файл (~/.cursor/mcp.json, .vscode/mcp.json, панель настроек расширения):

{
  "mcpServers": {
    "tv-debug": {
      "command": "node",
      "args": ["/absolute/path/to/tv-debug-mcp/src/server.js"],
      "env": {"TV_DEBUG_CONFIG": "/absolute/path/to/devices.json"}
    }
  }
}

Стабильное имя команды вместо пути

npm link                       # из корня репозитория

npm link кладёт tv-debug-mcp в PATH (поле bin в package.json) и заодно ставит exec-бит: в репозитории у src/server.js права 644 при живом шебанге, то есть напрямую он не запускается. После линка в любом конфиге можно писать "command": "tv-debug-mcp" с пустым args.

Публикации в npm и запуска через npx нет и не планируется. devices.json и apps/<id>.json лежат рядом с пакетом, а глобальная установка кладёт их в каталог, который переписывается на каждом обновлении. Работать это будет только с TV_DEBUG_CONFIG на парк и абсолютными путями в поле app — то есть ровно та ручная настройка, ради избавления от которой npx и берут.

Переменные окружения

Переменная

Что задаёт

Если не задана

TV_DEBUG_CONFIG

путь к devices.json

devices.json рядом с пакетом

TV_DEBUG_CHROME

бинарь Chrome для platform: "pc"

/Applications/Google Chrome.app/Contents/MacOS/Google Chrome; поле chromePath устройства перебивает и то и другое

TV_DEBUG_DEVICE

устройство для платформенных приёмок (check:webos2, check:tizen3, webos4-regress-probe)

webos2 / tizen3 / webos4 соответственно

TV_DEBUG_CASES_DIR

куда tv_record кладёт записанные кейсы

cases/recorded/ рядом с пакетом (в .gitignore)

TV_DEV_URL + TV_DEV_APP

дополнительный прогон check:browser против живого dev-сервера; нужны обе

прогон только против встроенной фикстуры

TMPDIR

куда падают артефакты (.cpuprofile, .heapsnapshot, .png, HAR), когда path не задан явно

системный временный каталог

Зачем не Appium / не playwriter

  • Appium TV-драйверы тянут chromedriver, который мёртв на Tizen с Chrome ≤ 57 и держится на хаке подмены UA на webOS 3. Тяжёлая инфра, два разных драйвера.

  • playwriter / Playwright connectOverCDP требует свежий Chromium — не заведётся на webOS 3/4 (Chrome 38/53).

  • Этот MCP говорит с инспектором по «голому» CDP. Один кодовый путь от Chrome 38 до 120+, ноль зависимостей на устройстве. Тот же набор тулов работает и против браузера на ноуте.

Инструменты (18)

Тул

Что делает

tv_devices

Парк из devices.json: доступность и реальные capabilities каждого устройства

tv_install

Установка билда (.wgt / .ipk). uninstallFirst:true лечит «Author certificate not match»

tv_launch

Debug-запуск + attach по CDP. Режимы: свежий старт / reload / relaunch / attach. Дожидается bootReady из app-профиля и кладёт вердикт в attached.bootReady

tv_press

Клавиша пульта. durationMs = лонгтап; repeat+intervalMs = серия. Возвращает фокус до/после и inputMode

tv_state

Структурный снимок: url, заголовок, видимые сцены, фокус (текст, класс, путь, индекс/всего), попапы, счётчики

tv_snapshot

Раскладка экрана одним вызовом: ряды вокруг фокуса, их элементы с рефами e1, e2…, и neighbours — ближайший реф в каждую сторону. tv_goto {ref} ходит по ним точно

tv_record

Записать путь физическим пультом и скомпилировать в готовый tv_sequence + чек-лист. start перезапускает приложение, на экране горит «● REC», stop показывает кейс, файл создаёт write

tv_wait_for

Ожидание условия вместо sleep: focusText / element / elementGone / sceneName / selector / selectorGone / scene / text / expression / videoAdvancing

tv_goto

Жать направление, пока сфокусированный элемент не совпадёт с целью (имя из профиля, текст, селектор, testid). Ограничен maxSteps, дедлайном и детектом «фокус встал» / «обернулись по кругу». select: true — нажать ENTER по прибытии

tv_menu

Войти в меню приложения и выбрать раздел по имени; без имени — открыть и вернуть список разделов

tv_sequence

Весь кейс одним вызовом: вердикт, время и результат по каждому шагу, под device-lock

tv_screenshot

PNG кадра. В браузере работает всегда; на Tizen деградирует с пометкой (secure/overlay plane). Движок, который вообще не отдаёт кадр, ловится один раз: первый вызов выжигает таймаут, все следующие в этой сессии отказывают мгновенно

tv_console

Консоль / исключения / упавшие запросы с момента launch. Все уровни, фильтр, счётчик отброшенного буфером

tv_network

Полный лог запросов с момента launch: url, метод, статус, тело POST. Чтение тела ответа по requestId, экспорт в curl и HAR 1.2, ассерт expectRequest шагом кейса

tv_video_state

Программный снимок <video>: тикает ли currentTime (два замера), readyState, размеры, MediaError. Если <video> на странице нет вообще — читает объектный плеер Tizen (webapis.avplay) теми же полями плюс source: "avplay", кодек, битрейт и лестницу ABR

tv_evaluate

Произвольный JS в странице (escape hatch). На старых ТВ — только ES5

tv_profile

Запись JS CPU-профиля (start → действия → stop): файл .cpuprofile для DevTools + топ функций и файлов по self time. sourceMap деминифицирует топ на прод-сборке. Плюс метрики Performance.getMetrics (heap, DOM-узлы, слушатели, layout) — снимок на start и на stop, в ответе diff; action:"metrics" снимает их отдельно, без записи профиля

tv_heap

Снапшот кучи на устройстве (.heapsnapshot для DevTools → Memory → Load) + сводка по конструкторам и счётчик detached-нод; action:"diff" сравнивает два файла, как Comparison view

tv_sequence — шаги

{"launch": {"relaunch": true}}            // привести апп в известное состояние
{"press": "RIGHT", "repeat": 2}
{"longpress": "ENTER", "durationMs": 1600}
{"goto": {"direction": "DOWN", "text": "Library"}}
{"menu": "Settings"}
{"wait": {"scene": "player"}, "timeoutMs": 30000}
{"expect": {"selector": "[class*=context-menu]"}}
{"networkMark": true}                     // «считать запросы с этого места»
{"expectRequest": {"urlPattern": "track", "method": "POST", "bodyContains": "event_id"}}
{"eval": "document.title"}
{"sleep": 1500}
{"videoState": true, "expectAdvancing": true}
{"state": true}
{"profileStart": {"samplingIntervalUs": 1000}}
{"profileStop": {"path": "/tmp/scroll.cpuprofile", "sourceMap": "…/app.js.map"}}
{"metrics": true}                         // снимок Performance.getMetrics; {"collectGarbage": true} — с GC
{"snapshot": {"detail": "focus"}}         // структурный снимок раскладки в чекпоинте

expect — то же, что wait, но невыполнение валит шаг. stopOnFail по умолчанию true.

tv_press — клавиши

UP DOWN LEFT RIGHT ENTER BACK MENU INFO GUIDE SEARCH TOOLS CAPTION RED GREEN YELLOW BLUE PLAY PAUSE PLAY_PAUSE STOP REWIND FAST_FORWARD TRACK_NEXT TRACK_PREV RECORD CHANNEL_UP CHANNEL_DOWN PAGE_UP PAGE_DOWN VOLUME_UP VOLUME_DOWN VOLUME_MUTE EXIT DIGIT_0..9 (регистр не важен, можно сырой числовой keyCode). Коды взяты из платформенных input-слоёв Tizen (TvKeyCode) и webOS.

Лонгтап: {"key":"ENTER","durationMs":1600} — keydown, hold, keyup. Механика LongPressService: таймер стартует на keydown, keyup решает «клик или лонгтап». LG SSAP-пульт hold не выражает — поэтому синтетика, а не пульт.

tv_snapshot — раскладка экрана за один round-trip

Навигация без снапшота — это tv_presstv_statetv_presstv_state: агент не знает раскладку и щупает вслепую, а каждый ответ оседает в контексте. Снапшот отдаёт достаточно, чтобы спланировать 3–5 ходов сразу.

{"tier": "profile", "g": 1, "bytes": 1544,
 "focus": {"text": "Второй ролик", "ref": "e2", "index": 1, "total": 6},
 "rows": [{"i": 0, "focused": true, "items": [
            {"ref": "e1", "i": 0, "t": "Первый ролик про котиков"},
            {"ref": "e2", "i": 1, "t": "Второй ролик про горы", "focused": true}],
           "more": 3}],
 "neighbours": {"LEFT": "e1", "RIGHT": "e3", "UP": null, "DOWN": "e7"}}

detail: focus (только фокус, сцены, попапы — самый дешёвый), rows (по умолчанию), full (без фильтра по вьюпорту). Плюс maxRows, maxItemsPerRow, release. Шагом кейса — {"snapshot": true}, чтобы снять структуру в чекпоинте под операционной локой.

Два яруса рядов, и ответ говорит, какой сработал (tier):

  1. profile — блок snapshot из apps/<id>.json (row/item/label), либо tile/menu.item, если блока нет. Точно, и ряды могут нести подписи.

  2. generic — вообще без знания о приложении: ряд фокуса — это то, что уже считает tv_state (сиблинги с тем же первым классом), соседние ряды — сиблинги контейнера с такими же элементами.

Ни один не дал рядов — rows: [] и warning с указанием, что дописать в профиль. Структура не выдумывается: агент по ней пойдёт навигировать, и выдуманный ряд хуже отсутствующего.

neighbours — это геометрия раскладки, а не навигационный граф приложения. Ближайший центр в каждом направлении среди собранных элементов. Он доказывает, что ход tv_goto {ref} — одно нажатие; что приложение сделает по этому нажатию, он не знает.

Рефы протухают, и протухший отвергается, а не переразрешается. Их сносит следующий снапшот, навигация и TTL 60 с (та же константа, что у слота видео-сэмпла: карта живых Element на window — ложный ретейнер в tv_heap diff). Номера сквозные между снапшотами, поэтому e12 из прошлого поколения не может молча попасть на другой элемент. On-device всплывает и третий случай: список с переиспользованием DOM выкидывает ноду сам — ответ ref e1 points at an element that has left the DOM.

Компактность, по убыванию эффекта: фильтр по вьюпорту (на каталоге в 40 рядов решает всё), maxRows/maxItemsPerRow со счётчиком more, текст ≤32 символов, элемент несёт {ref, i, t} и больше ничего. bytes — размер самого ответа, чтобы было видно цену контекста.

Многоосевого pathfinding в tv_goto намеренно нет: снапшот уже сказал, на какой оси цель, и «дойти до X» — это два tv_goto, а не N проб. Неверная ветка 2D-поиска не бесплатна и не откатывается — вход в плитку стартует плеер и шлёт аналитику.

tv_record — записать путь пультом, получить кейс

Кейс сегодня пишется руками, а знание «как дойти до этого экрана» живёт в голове у того, кто дошёл. tv_record переворачивает это: человек проходит путь настоящим пультом, MCP пишет и компилирует.

tv_record {"action": "start"}                      # перезапускает приложение, зажигает «● REC»
   … человек ходит пультом …
tv_record {"action": "status"}                     # сколько клавиш реально дошло до страницы
tv_record {"action": "stop", "title": "Лонгтап на плитке"}    # компилирует и ПОКАЗЫВАЕТ, на диск не пишет
   … человек читает кейс: сохранить / поправить / выбросить …
tv_record {"action": "write"}                      # вот теперь файл

start сам перезапускает приложение. Скомпилированный кейс всегда открывается шагом {"launch": {"relaunch": true}} — значит запись, начатая посреди сессии, даёт кейс, чей первый шаг противоречит всем остальным: реплей стартует с каталога, а запись стартовала тремя экранами глубже, и кейс красный по причине, не имеющей отношения к приложению. Запись с холодного старта делает эти два состояния одним и тем же. relaunch: false — для пути, в который свежий запуск не приводит; тогда кейс несёт предупреждение об этом, потому что это свойство кейса, а не сессии.

stop ничего не пишет на диск. Скомпилированный кейс — это черновик: шаги выведены из того, что человек нажал, и отличить настоящий путь от неверного поворота может только он. Поэтому stop возвращает steps инлайном (то, что сразу скармливается в tv_sequence) и markdown — ровно тот файл, который был бы записан, плюс wouldWriteTo и exists. Показываете кейс человеку, спрашиваете — и только action: "write" создаёт файл.

«Поправить» — это тоже write. write принимает steps и сохраняет их вместо скомпилированных: выкинуть неверный поворот или дописать expect можно, не сочиняя markdown руками. Чек-лист при этом сохраняется и помечается как относящийся к исходной записи — он компилировался против других шагов.

Почему поллинг, а не Runtime.addBinding. Нормальный канал page→host — Chrome 51+, а парк начинается с Chrome 38 и WebKit 538. Поллинг здесь не деградация, а единственный режим, который есть на всех движках: одна реализация и ни одной непротестированной быстрой ветки.

Что делает компилятор (и почему именно так):

  • Серия одинаковых нажатий → один goto, но только пока фокус двигался на каждом нажатии. Встал на полпути — человек перелетел край списка; лишние нажатия выбрасываются с предупреждением. Записать чужой перелёт в кейс — это кейс, зелёный по неверной причине.

  • Физический автоповтор → {press, repeat}, а не longpress. Удержание DOWN на ТВ — это платформа, повторяющая клавишу, а синтетический лонгтап шлёт ровно один keydown и не прокрутит ничего. Удержание не-стрелки → {longpress} с реальной длительностью.

  • Наблюдения → ассерты: смена сцены даёт wait с таймаутом от замеренного (пол 5 с, потолок 30 с), появившийся/исчезнувший попап — expect по селектору, выведенному из его класса.

  • Простой выбрасывается целиком. sleep не эмитится никогдаcases/README.md запрещает его прямым текстом, а запечь в кейс чужое время на подумать хуже всего.

  • Сеть — только по whitelist record.watch из app-профиля, и только то, что в записи действительно случилось: ассерт на запрос, которого сценарий не делал, красен на первом же реплее по причине, не имеющей отношения к приложению. bodyContains не выводится автоматически — записанное тело несёт токены и id, такой ассерт зелёный один раз и красный всегда потом; вместо него строка в чек-листе.

  • Первым шагом всегда {"launch": {"relaunch": true}} — правило №1 из cases/README.md.

assert: minimal (только клавиши — кейс, зелёный при сломанном приложении), normal по умолчанию (сцены и попапы), rich (плюс ассерты на движение видео — хрупкость с первого дня, если она не нужна).

Реплей не запускается сам. На живом ТВ он стартует плеер и шлёт аналитику — это не побочный эффект остановки записи. Прогон через tv_sequence — отдельное действие по явной команде.

Коллизия имён — вопрос человеку, а не решение за него. stop заранее говорит exists: true, если по этому пути уже что-то лежит; write в такой файл не пишет и молча не суффиксует — возвращает {"written": false, "conflict": "<path>"}, а скомпилированный кейс держится в сессии до следующего start. Дальше — write с явным path либо overwrite: true.

Записи по умолчанию идут в cases/recorded/ и этот каталог в .gitignore: запись несёт селекторы, названия разделов и urlPattern'ы аналитики конкретного приложения. Публикация — осознанный ручной перенос. Переопределяется path или TV_DEBUG_CASES_DIR.

REC-бейдж (overlay: false отключает) — элемент с зарезервированным классом __tvdbg-rec, исключённый из попап-сканов, снапшота и дедупликации наблюдений, и переустанавливаемый вместе с рекордером при реаттаче. Человек с пультом должен видеть, что запись идёт, иначе каждый прогон начинается с вопроса «а оно вообще пишет?». В скриншоты бейдж попадёт — про это есть строка в чек-листе.

Деградация по движкам:

Движок

Что не так

Что возвращаем

весь парк

нет addBinding

поллинг — единственный путь, одна реализация

Chrome 38 (webOS 3), WebKit 538 (webOS 2)

нет Event.isTrusted

trusted: null у каждой клавиши и warning на start: синтетические нажатия других тулов попадут в запись как нажатия пульта. ⚠️ Chromium 47 (Tizen 3) isTrusted сообщает — порог Chrome 46 он проходит, и предупреждение там не выдаётся

webOS 2

доставка клавиш физического пульта в webview не гарантирована — часть кнопок съедает лаунчер

status отдаёт keysSeen; stop при нуле возвращает ok: false и причину, а не пустой кейс, похожий на успех

Tizen

скриншот виснет

шаг скриншота не эмитится, вердикт по tv_video_state — и это в чек-листе

любой

обрыв сокета

реаттач ловится по идентичности соединения, рекордер переустанавливается, в предупреждениях сказано, что события в дыре потеряны

Нового вида шага в tv_sequence намеренно нет: sequence — это агент за рулём, рекордер — человек за рулём; их смешение даёт кейс, записывающий сам себя.

tv_network — лог сети, тела, curl/HAR и ассерты

tv_console показывает только упавшие запросы. Успешный запрос с неправильным телом невидим ни одному кейсу — а это целый класс регрессов: аналитика потеряла поле, из параметров API выпал один, стат-событие ушло дважды. tv_network — про это.

tv_network {"action": "list", "urlPattern": "track", "method": "POST"}   # action по умолчанию
tv_network {"action": "body", "requestId": "1234.5"}                     # тело ответа
tv_network {"action": "curl", "requestId": "1234.5"}                     # команда для терминала/тикета
tv_network {"action": "har",  "path": "/tmp/case.har", "urlPattern": "api."}
tv_network {"action": "mark"}                                            # сдвинуть окно ассертов

list — фильтры urlPattern (подстрока или /regex/), method, status ("failed" | число | {"min":200,"max":299}), limit (по умолчанию 50, новейшие первыми). Запись: requestId, receivedAt, method, url (обрезан до 500), status, mimeType, resourceType, encodedDataLength, postData (обрезан до 1000, флаг postDataTruncated), failed + errorText, fromCache, redirectFrom / redirectedTo, inFlight. Плюс dropped — сколько вытеснено из кольцевого буфера: ассерт по вытесненному запросу провалился бы молча, поэтому счётчик едет в каждом ответе.

Ассерт в кейсе — шаг expectRequest (и условие {"request": {...}} в tv_wait_for):

{"networkMark": true}
{"menu": "Настройки"}
{"expectRequest": {"urlPattern": "track", "method": "POST",
                   "bodyContains": "event_id", "statusMax": 399, "timeoutMs": 8000}}
{"expectRequest": {"urlPattern": "stat.gif", "count": {"max": 1}, "timeoutMs": 3000}}
{"expectRequest": {"urlPattern": "ads", "absent": true, "timeoutMs": 3000}}

Окно матчинга — начало своего шага, как у остальных wait-условий. Но запрос — событие мгновенное, и тот, что улетел на предыдущем шаге, в окно уже не попадает: перед действием ставится {"networkMark": true}, и все expectRequest дальше считают от метки. Это главный практический момент тула.

absent: true и count.max ждут весь timeoutMs по определению: «ещё не пришло» и «не придёт» различимы только в конце окна, а дубль, прилетевший последним, — это ровно то, что ищут. Остальные формы возвращаются, как только матч есть.

Границы, каждая — свойство протокола, а не недоделка:

  • тела ответов не буферизуются на нашей стороне. getResponseBody читает буфер движка, и после навигации или релонча тела там нет. Поэтому action:"body" отвечает на «почему каталог пустой» сейчас и честно падает потом; повторить историю нельзя — ловить надо ассертом в момент кейса;

  • POST-тела несут токены и куки. В list тело режется до 1000 символов, целиком (до 64 КБ) хранится только ради curl/HAR и в отчёты не попадает. Гард на тело ответа — 256 КБ, на весь HAR — 50 МБ;

  • receivedAt — часы хоста, момент приёма события, а не CDP timestamp: монотонные часы движков разных поколений несравнимы ни между собой, ни с хостом, а wallTime в Chrome 38 нет. Для QA-ассертов скью приёма несуществен;

  • буфер сети — 1000 записей (у консоли 500): апп стреляет сетью на порядок чаще;

  • редирект переиспользует один requestId, поэтому каждый хоп пишется отдельной записью (redirectFrom / redirectedTo), а action:"body"/"curl" берут последний.

curl: Cookie, Authorization и *token*-заголовки заменяются на REDACTED, полный вариант — явным "raw": true. На движке без requestWillBeSentExtraInfo (Chromium <63 — весь парк старше tizen55) заголовки берутся из requestWillBeSent.request.headers, то есть это то, что знал апп, до того как движок навесил Cookie и UA; репро авторизованного запроса может не совпасть — приходит warning, а не тихое расхождение.

har: HAR 1.2 (creator tv-debug-mcp), открывается в DevTools → Network → Import, Charles, Insomnia — готовое вложение-пруф к багу. Заголовки пишутся как есть, без редактирования: HAR без Cookie ничего не воспроизводит. Отсюда правило — в публичный тикет такой файл не класть. Тела — best-effort и только «сейчас»: HAR в конце кейса будет с телами, снятый позже — метаданные, у таких entries comment: "body evicted", счётчик bodiesMissing в ответе. Тайминги — из response.timing; чего движок не дал, то -1 по спеке, а не выдуманное число.

Платформы: домен Network жив на всём парке (он и так включается на connect, cdp.js), getResponseBody — тоже. requestWillBeSentExtraInfo/responseReceivedExtraInfo (реальные wire-заголовки) — Chromium 63+, ниже curl предупреждает про куки.

tv_profile — CPU-профиль и метрики

CPU-профиль — единственный перф-домен, который жив на всём парке: Profiler.start/stop есть и в Chromium 69 (tizen55), и в Chrome 38 (webos3) — в отличие от Tracing. Метрики Performance.getMetrics требуют Chromium 60+, поэтому они едут прицепом и никогда не ценой профиля (см. «Метрики» ниже).

tv_profile {"action": "start"}          # опц. samplingIntervalUs, по умолчанию 1000
tv_goto    {"direction": "DOWN", …}     # то, что меряем
tv_profile {"action": "stop", "sourceMap": "…/app.js.map", "topN": 20}

stop отдаёт:

  • path — файл .cpuprofile. Открывается в Chrome DevTools → Performance → Load profile (кнопка ⤒). Сырой профиль в ответ тула не кладётся никогда — это сотни килобайт JSON;

  • summary.topFunctions — self time и % по функциям (аггрегат по одинаковым фреймам; total time рекурсивной функции считается один раз, а не на каждом уровне);

  • summary.topFiles — то же по файлам;

  • summary.special(program) / (garbage collector) / (idle) отдельно, в топ функций они не лезут;

  • metrics — diff Performance.getMetrics за окно записи (или null на движке без домена);

  • warning — если карта не прочиталась, если ни один топовый фрейм в ней не нашёлся, если формат легаси или если метрик на этом движке нет.

Self time = hitCount × средний интервал семплинга, где интервал выводится из самой записи (длительность / число хитов), а не из запрошенного samplingIntervalUs — старый движок вправе его проигнорировать.

Прод-сборка без sourceMap — это топ вида Xy/abc. Карту брать из той же сборки, что стоит на ТВ (<каталог сорсмапов сборки>/app.js.map); деминифицируются только топ-N фреймов, остальное DevTools разберёт сам по файлу.

Внутри tv_sequence — шагами profileStart/profileStop: сценарий держит операционный лок, отдельный tv_profile в него не влезет.

Форматы профиля различаются между поколениями движков и нормализуются оба: современный (nodes[], 0-based строки, микросекунды) и легаси Chrome 38 (head-дерево, 1-based строки, секунды). Строки в саммари всегда 1-based, как показывает DevTools. Файл легаси-формата современный DevTools может не открыть — об этом приходит warning, саммари при этом валидное.

tv_profile — метрики (heap, DOM, layout)

CPU-профиль показывает, где горит JS, и не видит ни память, ни layout. Performance.getMetrics — один дешёвый вызов, который отдаёт JSHeapUsedSize, JSHeapTotalSize, Nodes, Documents, JSEventListeners, LayoutCount, RecalcStyleCount и кумулятивные счётчики времени (LayoutDuration, RecalcStyleDuration, ScriptDuration, TaskDuration).

tv_profile {"action": "metrics"}                      # снимок здесь и сейчас
tv_profile {"action": "metrics", "collectGarbage": true}

start и stop снимают метрики сами, поэтому охота на утечку — это обычная запись:

tv_profile {"action": "start"}
tv_press   {"key": "DOWN", "repeat": 20}
tv_profile {"action": "stop", "collectGarbage": true}

stop вернёт

"metrics": {
  "windowSec": 12.4,
  "collectedGarbage": true,
  "values": {
    "Nodes":            {"before": 1200,     "after": 1650,     "diff": 450},
    "JSEventListeners": {"before": 340,      "after": 352,      "diff": 12},
    "JSHeapUsedSize":   {"before": 20000000, "after": 24500000, "diff": 4500000},
    "LayoutDuration":   {"before": 0.1,      "after": 0.4,      "diff": 0.3}
  }
}

Читать так: Nodes вырос на 450 после того, как навигация вернулась туда же — сцена не разбирает свой DOM. LayoutDuration — секунды layout-времени именно за окно записи.

Детали:

  • Отдаётся весь список метрик, какой прислал движок, без белых списков: набор в Chromium 69 и в свежем Chrome разный, а фильтр молча съел бы то, чего мы не ждали. Метрика, которую знает только один из двух снимков, остаётся в diff со стороной null — это тоже информация. Нечисловые значения проходят насквозь с diff: null;

  • windowSec — из Timestamp (монотонные часы движка), не из часов хоста: раунд-трипы CDP в окно не входят;

  • кумулятивные *Duration считаются с момента старта движка — смысл имеет только diff, не абсолют;

  • collectGarbage по умолчанию выключен. Форсированный GC — это пауза: внутри записи она искажает и профиль, и поведение слабого ТВ. Включать под охоту за утечкой, где несобранный мусор как раз и подделывает рост heap. Метод, которого на движке нет, даёт warning, а не ошибку;

  • снимок на start берётся до Profiler.start, на stopпосле Profiler.disable, чтобы сами вызовы метрик не попали в запись, которую они описывают.

Платформы: tizen55 (Chromium 69) ✓ и pc ✓ — полный набор из Performance.getMetrics. Движок без этого домена (Chromium ≤ 53: webos4, tizen3, webos3) не падает, а переключается на Memory.getDOMCounters: Nodes, Documents, JSEventListeners и Timestamp из performance.now() — именно те счётчики, на которых держится охота за утечкой DOM. В ответе — warning о том, что это фолбэк. Чего в нём нет намеренно: JSHeapUsedSize (единственный источник — квантованный по 100 КБ performance.memory, то есть стабильная ложь вместо честного отсутствия) и layout/style-счётчики (их пришлось бы выводить из счёта событий Tracing — другое измерение под тем же именем). Если и getDOMCounters нет, action:"metrics" честно падает с сообщением, а start/stop возвращают metrics: null плюс warning — потерять CPU-профиль из-за отсутствующих метрик нельзя.

В tv_sequence — шаг {"metrics": true}: им можно обрамить любой кусок сценария, не только тот, что покрыт записью профиля. Diff между двумя такими шагами считает вызывающий.

tv_heap — снапшоты кучи и diff

Метрики говорят, что выросло (JSHeapUsedSize, Nodes); снапшот кучи — кто это держит. Охота на утечку:

tv_heap {"action": "snapshot", "path": "/tmp/before.heapsnapshot"}
tv_menu {"item": "Настройки"}   # сценарий: то, после чего память не возвращается
tv_menu {"item": "История"}
tv_heap {"action": "snapshot", "path": "/tmp/after.heapsnapshot"}
tv_heap {"action": "diff", "before": "/tmp/before.heapsnapshot", "after": "/tmp/after.heapsnapshot"}

snapshot отдаёт path, bytes, chunks, durationMs и summary — это Summary view в числах: totalNodes, totalSize (shallow), detachedCount и topConstructors (count + shallow size). diffdelta по тоталам плюс topGrowth / topShrink: deltaCount, deltaBytes, countBefore, countAfter по каждому конструктору, ровно как Comparison view.

Границы, они же причина хранить файл:

  • retained size (доминаторы) и retainer-пути не считаются. Для «кто держит эту ноду» — открыть сохранённый файл в Chrome DevTools → Memory → Load. Тул отвечает на «что выросло», DevTools — на «за что зацепилось»;

  • парсится только nodes + strings; edges (в разы больше) не читается — на нём и стоит ретейнер-граф;

  • снапшот > 500 МБ не парсится вообще: JSON.parse такого файла стоит гигабайты RAM в Node. Ответ — summary.ok:false + warning, файл при этом целый и открывается в DevTools;

  • detached-ноды ловятся двумя способами: по имени (Detached HTMLDivElement) и по колонке detachedness (есть с ~Chromium 80). Флагнутая, но не переименованная нода попадает в тот же бакет Detached …, чтобы diff видел рост одной строкой.

Снапшот пишется на диск потоком, по чанкам HeapProfiler.addHeapSnapshotChunk (37 МБ = ~365 чанков): держать кучу ТВ целиком ещё и в памяти MCP незачем. Оборванный снапшот (таймаут, разрыв сокета) удаляется — половина файла это невалидный JSON, который не откроет ни DevTools, ни парсер; в ошибке сказано, что файл удалён.

Снапшот отвергается во время записи CPU-профиля: это полный GC и длинная пауза V8, внутри записи она измеряла бы саму себя. Сначала tv_profile action:"stop".

action:"diff" — чисто файловая операция: device не нужен, ТВ может быть выключен. Кэша нет, оба файла парсятся заново — кэш по пути соврал бы на перезаписанном снапшоте.

Платформы: pc ✓, tizen55 ✓, webos3 (Chrome 38) ✓ — HeapProfiler жив даже там (37 МБ / 406k нод / 13 с на живом LG 49UJ639V, diff после сценария показал +8.3 МБ и +1859 detached). Оговорка Chrome 38: у нативных нод self_size = 0, поэтому detachedSize там всегда 0 — считать надо detachedCount.

С tv_sequence намеренно не интегрирован: снапшот на слабом ТВ — это десятки секунд, тяжёлый шаг внутри сценария размыл бы тайминги остальных шагов. Порядок «снапшот → сценарий → снапшот → diff» точности окна не теряет.

Требования к устройствам

Три независимых чек-листа: ТВ Samsung, ТВ LG, браузер на ноуте. Каждый кончается командой, которая отвечает «готово / не готово» до того, как MCP скажет «устройство недоступно».

Tizen (Samsung)

  1. Developer Mode на ТВ: Apps → набрать 12345 на пульте → Developer mode: On → вписать IP машины, с которой будете подключаться → перезагрузить ТВ. Обновление прошивки его выключает.

  2. Tizen Studio CLI в PATH — нужны sdb и tizen: ~/tizen-studio/tools и ~/tizen-studio/tools/ide/bin.

  3. Подключение: sdb connect <ip>:26101 (порт по умолчанию, в devices.json переопределяется полем sdbPort).

  4. Author-сертификат Samsung, которым подписан .wgt. Билд, подписанный другим сертификатом, поверх старого не встаёт — tv_install {"uninstallFirst": true} сносит и ставит заново; это и есть лечение «Author certificate not match».

Проверка: sdb devices — устройство должно быть в состоянии device. unauthorized значит, что на ТВ не подтвердили подключение или Developer Mode слетел.

webOS (LG)

  1. Developer Mode: поставить приложение Developer Mode из LG Content Store, войти аккаунтом с developer.lge.com, включить Dev Mode. Ключ живёт ограниченное время, в приложении есть продление; протухший ключ снаружи выглядит как «устройство не отвечает».

  2. ares-cli: npm i -g @webosose/ares-cli.

  3. Завести устройство: ares-setup-device. ⚠️ В devices.json в поле device идёт имя из ares, а не IP — адресация у webOS-адаптера именная.

Проверка: ares-device-info -d <name> отдаёт модель и версию webOS; ares-setup-device --list показывает всё заведённое.

PC (браузерный режим)

  1. Chrome на машине. Путь по умолчанию — macOS-овый, переопределяется TV_DEBUG_CHROME или полем chromePath устройства.

  2. Dev-сервер приложения поднимает пользователь. MCP только проверяет, что url отвечает; он не запускает и не гасит чужой сервер.

Проверка: npm run check:browser — полный прогон против встроенной фикстуры, ТВ не нужен.

Парк устройств

devices.json (или путь в TV_DEBUG_CONFIG) — он в .gitignore, заводится копией devices.example.json. Файл перечитывается по mtime — правка подхватывается без рестарта MCP; дубли id и портов отвергаются с внятной ошибкой.

{
  "defaultDevice": "tizen",
  "devices": [
    {"id": "tizen", "platform": "tizen", "app": "myapp", "appId": "AbCdEfGhIj.myapp",
     "host": "192.168.1.10", "sdbPort": 26101, "localPort": 9955},
    {"id": "webos", "platform": "webos", "app": "myapp", "appId": "com.example.myapp", "device": "webos7"},
    {"id": "vidaa", "platform": "vidaa", "app": "myapp", "host": "192.168.1.13", "port": 9226},
    {"id": "pc-dev", "platform": "pc", "app": "myapp", "url": "http://localhost:1337"},
    {"id": "pc-dev-parity", "platform": "pc", "app": "myapp", "url": "http://localhost:1337",
     "inputMode": "synthetic"}
  ]
}

Поля устройства:

Поле

Для кого

Что задаёт

id

все

имя устройства в тулах и в TV_DEBUG_DEVICE. Уникально — дубли отвергаются на загрузке конфига

platform

все

tizen | webos | vidaa | pc

app

все

id app-профиля: читается apps/<app>.json. Без него доступна только дженерик-часть тулов

name, engine

все

человекочитаемые подписи, видны в выводе tv_devices

appId

tizen, webos

id приложения на устройстве (AbCdEfGhIj.myapp, com.example.myapp)

host

tizen, vidaa

IP телевизора. У vidaa это весь адрес: инспектор слушает на самом ТВ (в отличие от webOS, где device — имя из ares)

port

vidaa

порт DevTools-инспектора на ТВ. Не указан — узкий автоскан 9222–9230. На VIDAA 9 (50A53FEVS) это 9226, на старых прошивках встречался 9223. Dev-режим включается пультом: Home×3 → Up×2 → Right-Left-Right-Left-Right. ⚠️ Инспектор без авторизации — любой в LAN может подключиться; только для dev-девайса. appId не нужен: апп hosted, sideload/kill по сети недоступны (relaunch = перезагрузка страницы)

sdbPort

tizen

порт sdb, по умолчанию 26101

cliTarget

tizen

цель для tizen install -t, чтобы билд поехал в нужный ТВ на парке. Можно не указывать — выводится из третьей колонки sdb devices

localPort

tizen

локальный порт под sdb forward. Не указан — берётся свободный; два устройства с одним и тем же пином отвергаются, иначе они перекрёстно склеились бы

device

webos

имя устройства из ares-setup-device --list (не IP)

url

pc

адрес dev-сервера, например http://localhost:1337

chromePath

pc

бинарь Chrome именно для этого устройства; перебивает TV_DEBUG_CHROME

chromeArgs

pc

дополнительные аргументы к Chrome поверх обязательных

profileDir

pc

использовать этот каталог профиля вместо одноразового. Тогда профиль считается чужим и на dispose не удаляется — так живёт залогиненный Chrome, который не хочется логинить заново каждый прогон

inputMode

pc

trusted (по умолчанию) | synthetic — см. «Trusted vs synthetic». На ТВ клавиши всегда синтетические, поле там не читается

App-профиль

apps/<id>.json, привязка полем "app". Здесь живёт всё знание о приложении — чем помечен фокус, как выглядит сцена, где меню. Это то, что делает MCP переносимым: для другого приложения заводится второй файл, а не форк. Рабочий пример — apps/fixture.json (профиль встроенной фикстуры).

{
  "focus": ["._active"],
  "scene": {"container": "._scene", "strip": "layer__container|fullscreen"},
  "popup": ["[class*=popup]", "[class*=context-menu]"],
  "menu": {"openKey": "LEFT", "exitKey": "BACK",
           "root": ".menu__primary", "item": ".menu__primary .menu-cell",
           "title": ".menu-cell__title"},
  "tile": ".video-tile, .media-tile",
  "bootReady": {"selector": ".video-tile", "timeoutMs": 40000},
  "elements": {"catalog.tile": ".video-tile", "player.play": {"testid": "play-button"}},
  "scenes": {"catalog": "s-catalog", "player": "s-player"},
  "checks": {"homeSection": "Main", "popup": ".context-menu"}
}

Два неочевидных момента, ради которых профиль вообще существует:

  • Фреймворк может вешать класс фокуса на всю цепочку scene → container → list → tile, поэтому сфокусированный виджет — это самый глубокий match, а не первый. Первый — это сцена, и по нему навигация выглядит неподвижной.

  • root/item пришивайте к первому уровню меню. Вложенный раздел легко рисует свои строки теми же классами, и одна из них может называться как раздел верхнего уровня — тогда матч по всему меню выбирает вложенную строку и рапортует успех, пока апп никуда не уходил. По той же причине есть exitKey: внутри раздела клавиша открытия меню может не возвращать в сайдбар, надо сначала выйти по BACK.

Необязательный блок checks читают приёмочные скрипты (test/phase1-check.mjs), чтобы не быть прибитыми к одному приложению: homeSection — раздел, в который возвращаемся после захода в меню, popup — как выглядит контекстное меню тайла.

bootReady — вердикт приезжает с launch

bootReady дожидается tv_launch сам, сразу после аттача (только на свежем старте и на reload — аттач к живому приложению, которое стоит в плеере, не должен ждать плитку каталога). В ответе — attached.bootReady: {ok, elapsedMs, condition}, из кейсов уходит открывающий шаг {"wait": …}, повторяющий профиль.

Приложение, которое так и не загрузилось, вызов не валит: аттач-то удался, а это находка — бросок отнял бы tv_console/tv_network ровно тогда, когда они нужны. Приходит ok: false + warning. Отключается waitBoot: false (например, чтобы посмотреть на сам процесс загрузки). Условие проверяется со stableMs: 300, потому что «селектор виден» ≠ «контент отрисован».

Именованные элементы и сцены

Кейс, который пишет {"element": "catalog.tile"}, переживает правку вёрстки; кейс с .video-tile--v2 — нет, и один и тот же селектор расползается по десятку файлов. Реестр живёт в профиле:

"elements": {
  "catalog.tile":  ".demo-tile",
  "menu.settings": {"selector": ".demo-menu-item", "text": "Settings"},
  "player.play":   {"testid": "play-button"}
},
"scenes": {"catalog": "s-fixture", "player": "s-player"}

Строка — сокращение для selector. Имена принимают tv_goto (element), tv_wait_for (element / elementGone / sceneName) и те же шаги внутри tv_sequence.

Три правила, каждое — из грабель:

  • Разрешение возвращается эхом: в ответе resolvedFrom: {"element": "catalog.tile", "selector": ".demo-tile"}. Красный кейс обязан назвать селектор, который реально проверялся, иначе индирекция стоит дороже, чем экономит.

  • Опечатка падает громко и со списком известных имён — тот же контракт, что у tv_menu без блока menu. Молчаливый промах, притворившийся таймаутом, — худший вид отладки.

  • Текстовый квалификатор не теряется. menu.settings — это «.demo-menu-item и текст Settings»; выродиться в «любой .demo-menu-item» такое условие не имеет права, поэтому оно едет в предикат целиком. Элемент, заданный только текстом, wait-условием стать отказывается (CSS-селектора у него нет) — для этого есть focusText.

Мердж elements/scenesпоключевой, в отличие от focus/popup («непустой список побеждает целиком»): реестр имён аддитивен, и профиль, определивший один элемент, не должен терять остальные.

Браузерный режим (platform: "pc")

Тот же набор тулов против локального Chrome. Быстро, и скриншоты реально работают — на Tizen они виснут.

  • Chrome — наш: свой временный --user-data-dir, --remote-debugging-port=0 (порт читается из DevToolsActivePort, а не прибит к 9333), гасится и подчищается на dispose. К обычному браузеру пользователя MCP не цепляется.

  • Dev-сервер — ваш: MCP проверяет, что url отвечает, и не запускает и не гасит его. Запускать npm start в проекте приложения.

  • --disable-web-security обязателен: приложение, чей бутстрап ходит за токеном на другой origin, без него умирает на CORS и не стартует.

  • Network.setCacheDisabled(true) обязателен: dev-сервер отдаёт ES-модули, и переиспользованный браузер молча гоняет вчерашний код.

  • Тротлинг фоновых окон выключен (--disable-background-timer-throttling и два соседних флага). Как только живо больше одного pc-устройства, все окна кроме последнего Chrome считает фоновыми и режет им таймеры — а приложение под тестом на таймерах и держится (контракт лонгтапа — это setTimeout). У ТВ такой оптимизации нет, поэтому тротленный прогон — не «более строгий», а другой эксперимент.

Trusted vs synthetic — почему это два разных эксперимента

ТВ

Браузер по умолчанию

Браузер inputMode: "synthetic"

Механизм

page-side KeyboardEvent

Input.dispatchKeyEvent

page-side KeyboardEvent

isTrusted

нет

да

нет

Куда летит

document

реально сфокусированный элемент

document

Дефолтные действия браузера

нет

да

нет

Кейс может быть зелёным в браузере и красным на ТВ (ветка TV-keyCode не задействована) — и наоборот (Backspace уводит браузер назад). Поэтому: режим пишется в каждый вердикт, тихого фолбэка между режимами нет, а навигационные кейсы прогоняются ещё и на pc-dev-parity перед выводом «на ТВ будет так же».

Ключевые находки on-device (Tizen 5.5, sdb 4.2.36)

  • Debug-запуск: sdb -s <serial> shell 0 debug <appId> без аргумента-таймаута. С таймаутом launchpad отвечает closed. Инспектор на device-порту переживает закрытие sdb-канала, поэтому канал закрывается сразу после разбора порта.

  • Надёжный killsdb shell 0 was_kill <appId>. kill_app на retail-шелле молча no-op.

  • attach работает только через живой инспектор: второй debug по уже отлаживаемому аппу отвечает closed. Порт берётся из памяти сессии или из правила sdb forward --list, которое переживает рестарт MCP; поэтому forward намеренно не снимается на dispose.

  • Скриншот Page.captureScreenshot виснет (secure/overlay plane, HDCP) — тул отдаёт ok:false с пометкой. Для плейбека — tv_video_state + взгляд на ТВ.

  • localStorage переживает debug-релонч на 5.5 (проверено: маркер на месте после was_kill + свежего debug).

  • Загрузка каталога — 3.6–6.1 с, а не «22 секунды на всякий случай»: tv_wait_for быстрее и детерминированнее слепой паузы.

  • relaunch в браузерном режиме переиспользует ту же throwaway-профиль-директорию, а Chrome оставляет в ней DevToolsActivePort от прошлого запуска. Файл сносится перед спавном — иначе адаптер отдаёт порт, на котором уже никто не слушает (no inspectable page at http://127.0.0.1:…).

  • Весь page-side JS — строго ES5: Array.prototype.find появился в Chrome 45, а webOS 3 — это Chrome 38, и одна такая строчка роняла tv_video_state ровно на самом старом устройстве парка.

Как это устроено

Claude Code ── stdio ── server.js
                         ├── config.js      devices.json (перечитка по mtime + валидация)
                         ├── appprofile.js  apps/<app>.json — знания о приложении
                         ├── adapters/
                         │    tizen.js      sdb -s: install/was_kill/debug/forward
                         │    webos.js      ares: close→launch→inspect
                         │    pc.js         свой Chrome + navigate + setCacheDisabled
                         │    spawn-until-match.js  общий супервизор CLI-детей
                         ├── input/
                         │    synthetic.js  page-side KeyboardEvent (ТВ + parity)
                         │    trusted.js    Input.dispatchKeyEvent (браузер)
                         ├── cdp.js         CDP по WebSocket, единый путь дисконнекта
                         ├── keymaps.js     KeySpec {code, key, domCode} по платформам
                         ├── inject.js      page-side ES5: key dispatch, focus, video-state
                         ├── state.js       page-side ES5: снимок состояния и фокуса
                         ├── snapshot.js    page-side ES5: ряды, рефы, соседи по геометрии
                         ├── record-inject.js page-side ES5: слушатель пульта, дренаж, REC-бейдж
                         ├── recorder.js    таймлайн и компилятор кейса (чистые функции)
                         ├── wait.js        поллинг условий (общий для wait/goto/sequence)
                         ├── network.js     лог запросов: фильтры, curl, HAR
                         ├── profile.js     CPU-профиль: оба формата, саммари, sourcemap
                         ├── heap.js        .heapsnapshot: свод по конструкторам и diff
                         ├── ports.js       свободный локальный порт под forward
                         └── session.js     живая сессия: два лока, авто-реконнект, навигация

Устойчивость: упавший ТВ, выдернутый сокет или отсутствующий sdb валят один вызов тула, а не процесс MCP. ensureConnected сериализован — параллельные вызовы не запускают апп дважды.

Проверено

Прогон

Что

npm run check:offline

237/237 — честный статус офлайн-устройства, перечитка конфига без рестарта, отказ при дублях id, выживание без sdb; парсер CPU-профиля на фикстурах обоих форматов (совпадающие числа, спец-узлы отдельно, рекурсия не удваивается) и деминификация топа с деградацией до warning; парсер .heapsnapshot (свод по конструкторам, detached по имени и по колонке detachedness, diff роста/убыли, движок без detachedness, битый файл) и tv_heap action:"diff" вообще без устройства; сетевой лог — жизненный цикл записи на событиях, скормленных сессии без сокета (редирект двумя хопами, отказ, ранний extra-info, вытеснение из буфера со счётчиком), фильтры, генератор curl (секреты, экранирование, warning'и) и сборка HAR, плюс семантика expectRequest (absent и count.max ждут всё окно); легаси-путь evaluate без awaitPromise целиком на заглушенном _evaluateRaw (синхронное выражение — один раунд-трип, промис досетлливается на хосте, statement откатывается на необёрнутый вызов, зависший промис честно истекает и убирает за собой слот) и AVPlay-ветка видео-зондов в vm против фейкового webapis.avplay, включая негативный контроль «поток не открылся — found, но не advancing»; разрешение именованных элементов (шорткат-строка и объект, поключевой мердж, resolvedFrom в эхе, опечатка падает со списком известных имён, текстовый квалификатор не теряется); билдеры снапшота и рекордера как ES5 плюс жизненный цикл рефа в vm (протухший, вылетевший из DOM, отпущенный); и весь компилятор кейса на рукописных таймлайнах — схлопывание в goto и его гард на перелёте, автоповтор против лонгтапа, wait с выведенным таймаутом, простой без единого шага и без sleep, сеть только по whitelist, markdown round-trip, конверсия часов

node test/phase0-check.mjs

18/18 на Samsung UE50TU8510 — launch, движение фокуса, ES5-проба видео, limit:1, attach из другого процесса с сохранением состояния, выживание при обрыве сокета

node test/phase1-check.mjs

20/20 на Samsung UE49MU6103 (Tizen 3.0) — wait_for вместо сна, структурный фокус, goto до цели и его границы, заход в раздел меню и возврат обратно, кейс лонгтапа целиком; bootReady приезжает вместе с launch, tv_goto {element} ходит по именам из профиля устройства и эхом отдаёт селектор, tv_snapshot собрал 4 ряда из 20 элементов за 1544 байта, tv_goto {ref} дошёл по neighbours, реф прошлого поколения отвергнут. Селекторы берутся из app-профиля устройства, поэтому прогон не привязан к конкретному приложению

npm run check:webos2

19/19 на LG 40UF771V (webOS 2.2 / WebKit 538.2) — движок без CDP: аттач через /pagelist.json, page-side throw доезжает ошибкой (wasThrown, а не тихий undefined), фокус двигается через createEvent-фолбэк, tv_console ловит Console.messageAdded, скриншот отказывает честно и сессия его переживает, tv_sequence проходит целиком; рекордер ставится и на этом движке — дренаж видит клавиши, stop компилирует кейс, бейдж за собой убран. Устройство задаётся TV_DEBUG_DEVICE, по умолчанию webos2

npm run check:tizen3

25/25 на Samsung UE49MU6103 (Tizen 3.0 / Chromium 47, protocol 1.1) — движок без awaitPromise: промисное выражение вернуло значение, reject приехал ошибкой, throw не сломался; tv_video_state снял вердикт с webapis.avplay (PLAYING, advancedBy 1.218 с, h264 1280×720) и tv_wait_for {videoAdvancing:true} прошёл; action:"metrics" отдал DOM-счётчики вместо отказа; второй tv_screenshot отказал мгновенно; консоль и сеть живы; рекордер собрал клавиши и наблюдения и скомпилировал кейс, а предупреждение про isTrusted сверено с тем, что движок реально отвечает (Chromium 47 его сообщает — порог Chrome 46 он проходит). Устройство задаётся TV_DEBUG_DEVICE, по умолчанию tizen3

npm run check:browser

125/125 в Chrome — capabilities, отказ tv_install, свой Chrome на порту 0, навигация, реальный скриншот, кейс лонгтапа в trusted и synthetic, CPU-профиль (именованная busy-функция видна в топе, двойной start и сиротский stop отвергнуты, профилирование шагами сценария), tv_heap (файл на диске, подсаженная утечка TvDebugLeakItem видна в diff по имени вместе с detached-нодами, снапшот во время записи профиля отвергнут), tv_network (expectRequest по телу реального XHR, негативный ассерт, упавший запрос с errorText, чтение тела ответа, сгенерированный curl исполняется шеллом и доносит тело до сервера, HAR парсится как 1.2), bootReady (зелёный вердикт с launch, недостижимое условие даёт ok:false и всё равно аттачится, waitBoot:false пропускает ожидание), именованные элементы и сцены сквозь тулы, tv_snapshot (ряды, neighbours, tv_goto {ref} ровно за 3 нажатия, отказ по протухшему рефу, жёсткий бюджет bytes < 2000), и главная приёмка рекордера: start сам перезапустил приложение и вернул фокус в начало списка, запись → компиляция → зелёный реплей через tv_sequence, stop не создал файла и отдал ровно тот markdown, который потом лёг на диск байт в байт, правка шагов через action:"write" сохранилась вместе с пометкой в чек-листе, пустой override отвергнут, коллизия имён названа, REC-бейдж появляется и исчезает и не попадает в снапшот; уборка за собой

tv_heap on-device

LG 49UJ639V (webOS 3.9 / Chrome 38): снапшот 37 МБ / 406k нод / 1271 detached за 13 с; после сценария diff показал +8.3 МБ, +170k нод, +1859 detached с разбивкой по конструкторам. Целевой webos7 на момент прогона был недоступен

tv_network on-device

LG 49UJ639V (webOS 3.9 / Chrome 38): expectRequest по телу реального стат-запроса зелёный на живой навигации, красная ветка падает по таймауту с причиной, absent выжидает всё окно; body читает ответ, сгенерированный curl воспроизводится в терминале (200), HAR на 361 запись собрался с 360 телами и настоящими таймингами

Что этот движок умеет и чего нет, видно по прогону выше: postData приходит прямо в requestWillBeSent, редиректы и getResponseBody работают, а *ExtraInfo нет (Chromium <63) — заголовки в логе до-движковые, без Cookie, о чём curl предупреждает. resourceType на Chrome 38 врёт (главный документ пришёл как Image), фильтровать надо по URL.

webOS-адаптер (close → launch → inspect, честный freshLaunch) прогнан on-device на LG 49UJ639V; остальные LG из ares-setup-device --list бывают недоступны (connection timed out) — это про сеть, не про адаптер.

Диалект Runtime.evaluate определяется по протоколу, а не по движку, поэтому двухзвонковый сэмпл берут все до-M54 движки. Проверено on-device на LG (webOS 4 / Chromium 53): awaitPromise: true там тоже отдаёт {}, то есть промисное выражение возвращало пустой объект и tv_video_state на этом устройстве был тихо сломан — теперь отдаёт полный набор полей. Слоты сэмпла ведут себя так же, как на webOS 2 (два токена сосуществуют, потерянный отвечает sampleLost, на странице ничего не остаётся). Tizen 3 (Chromium 47) — из той же протокольной эпохи.

Промис на таком движке больше не теряется нигде, а не только в видео-зонде: evaluate сам досетлливает его на хосте — выражение оборачивается так, что результат промиса ложится JSON-строкой в персональный слот на window, а хост опрашивает слот до значения или таймаута. Синхронное выражение при этом стоит ровно один раунд-трип, как раньше; statement (throw new Error(...)) в обёртку не влезает и откатывается на необёрнутый вызов, поэтому page-side throw по-прежнему доезжает ошибкой. Регресс on-device на LG (webOS 4 / Chromium 53), 19/19: промисное tv_evaluate вернуло значение (было {}), reject приехал ошибкой, throw не сломался, фокус/tv_state/tv_sequence живы, скриншот снимается обоими вызовами, а action:"metrics" — который на этом движке раньше просто падал — отдал Nodes/Documents/JSEventListeners/Timestamp из Memory.getDOMCounters с честным warning и без выдуманного heap.

npm run check:tizen3 (test/tizen3-check.mjs, устройство через TV_DEBUG_DEVICE) — приёмка того же набора на Chromium 47, 19/19 on-device: пре-M54-движок, промис и его reject, throw, AVPlay-зонд на играющем потоке (если апп до плеера не доехал — шаг помечается SKIP и просит перезапуск с TV_DEBUG_PLAYING=1), metrics-фолбэк, мгновенный отказ второго скриншота, консоль и сеть.

Две ловушки этого движка, всплывшие на приёмке: UA у Tizen-вебвью вообще без токена Chrome/ (SMART-TV; LINUX; Tizen 3.0 … AppleWebKit/538.1) — сравнивать версию Chromium по UA там нечего; и getCurrentStreamInfo().extra_info отдаёт строки (Width: "1280", Bit_rate: "2986443"), поэтому зонд приводит их к числам — иначе сравнение битрейта с порогом молча сравнивало бы строки.

tv_video_state on-device на webOS 2: awaitPromise: true на этом движке отдаёт {} — промис не дожидается, поэтому промисное выражение там бесполезно и сэмпл идёт двумя вызовами с паузой на стороне хоста. На играющем видео — advancing: true, advancedBy: 2 за паузу 2000 мс, 1920×800. Слот сэмпла ключуется по номеру вызова: два перекрывающихся сэмпла вернули независимые результаты (advancedBy 21.52 и 11.48 от своих баз), потерянный слот отвечает sampleLost, а не выдуманным сэмплом, и на странице не остаётся ничего. Лока здесь намеренно нет: tv_sequence уже держит операционный лок на шаге {"videoState":true}, а он не реентрантный.

test/smoke.mjs — ad-hoc прогон произвольного списка вызовов; test/harness.mjs — общий stdio-клиент для всех проверок и хелпер appTargets, который вытаскивает селекторы из app-профиля.

check:browser дополнительно прогоняется против вашего живого dev-сервера, если задать обе переменные:

TV_DEV_URL=http://localhost:1337 TV_DEV_APP=myapp npm run check:browser

Демо-кейсы

cases/fixture-smoke.md — кейс против встроенной фикстуры, исполним сразу после клона, без ТВ и без dev-сервера. Формат и правила, выведенные из реальных прогонов, — в cases/README.md.

Статику фикстуры под этот кейс поднимает человек и оставляет работать:

python3 -m http.server 8080 --bind 127.0.0.1 --directory test/fixture

Устройство pc-fixture с этим адресом и профилем apps/fixture.json уже есть в devices.example.json. Не путать с npm run check:browser: приёмочный скрипт поднимает свою статику на свободном порту сам и пишет себе одноразовый devices.json — ему ничего заранее запускать не надо.

Дальше

  • webOS on-device прогон (в т.ч. webOS 3 = Chrome 38: ES5-инъекция, работоспособность скриншота и легаси-формат CPU-профиля — парсер написан по спецификации Chrome 38 и проверен на фикстуре, но не на живом LG).

  • Прогон tv_profile на ТВ с sourceMap от прод-сборки (карта Closure парсится и позиции разрешаются — проверено офлайн).

  • tv_network on-device: приёмка «инструмент отвечает на исходный вопрос» — переход по разделам и зелёный expectRequest по аналитике на здоровой сборке; на webOS 3 (Chrome 38) факт-чек протокола: postData в requestWillBeSent, getResponseBody, поведение по редиректам. Пока прогонялось только в браузере.

  • Остальные перф-инструменты (FPS, Tracing) — отдельным заходом, они не покрывают весь парк.

  • Sampling heap profiler (HeapProfiler.startSampling/stopSampling) — кто аллоцирует; tv_heap отвечает на другой вопрос (кто держит уже живую память).

  • Авто-повтор удержанной d-pad-клавиши (holdRepeatMs): сейчас durationMs шлёт один keydown, что верно для лонгтапа, но не воспроизводит скролл ленты зажатой стрелкой. Обход списков закрывает tv_goto, а tv_record не заминает разницу — физический автоповтор он компилирует в {press, repeat}, а не в лонгтап.

  • Доставка клавиш физического пульта на webOS 2 проверена только синтетикой: рекордер там ставится и дренаж клавиши видит, но часть кнопок LG съедает лаунчер, и ответ даст только человек с пультом в руках — на то и tv_record action:"status" с keysSeen.

  • Параллельный прогон одного кейса на N ТВ (адресация -s для этого уже есть).

  • Allure TestOps (чтение кейсов) + allurectl (заливка результатов).

  • WS-пульт (SSAP / Samsung remote) для системных кейсов HOME/suspend, которые page-level синтетика не покрывает.

Available Tools

15 tools
tv_consoleA

Console output, uncaught exceptions and failed network requests buffered since launch. Filter by substring and/or level. Reports how many entries were dropped from the ring buffer.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax entries per bucket (default 60).
deviceNoDevice id from devices.json. Omit to use the default device.
filterNoCase-insensitive substring filter on message text (and on failed request URLs).
levelsNoConsole levels to include. Omit for all levels.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description discloses key behavioral details: it buffers since launch, includes uncaught exceptions and failed network requests, and reports dropped ring buffer entries. It does not mention return structure or pagination, but provides substantial behavioral context beyond trivial details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that front-loads the purpose, then adds filtering capability and output behavior. Every clause contributes meaningful information with no redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description explains what data is buffered and mentions the dropped entry count, which is useful given there is no output schema. It does not fully describe the return format, but the tool is relatively simple and the schema covers parameters, so the description is adequately complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the input schema already describes each parameter (limit, device, filter, levels). The description only summarizes 'filter by substring and/or level' without adding new semantic meaning, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves buffered console output, uncaught exceptions, and failed network requests. It specifies the resource (console/network logs) and the action (buffered since launch, filterable). This distinguishes it from sibling tools like tv_screenshot or tv_state.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context: logs are buffered since launch, so the tool is for historical console data. It mentions filtering by substring and level, which tells the agent how to narrow results. However, it does not explicitly state when not to use this tool or point to alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_devicesA

List configured TVs, their reachability (sdb/ares) and which operations each supports. Start here to see the park.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It explicitly states that the tool lists devices and their reachability/operations, implying a read-only inventory behavior with no side effects. This is transparent and sufficient for a list tool, though it doesn't explicitly state 'read-only'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise, using two short sentences. The first front-loads the verb and resource, and the second adds directional guidance ('Start here') without wasted words. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple list tool with no parameters and no output schema, the description adequately covers what it does and how it fits into the broader toolset. It states the output content and positions itself as the starting point. While it doesn't detail return format, that's not necessary for a list operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema fully covers the input side (empty object). The description adds meaning beyond the schema by explaining what will be listed, which is relevant context. Given the 0-parameter baseline of 4, this is appropriately scored.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool's purpose with a specific verb ('List') and resource ('configured TVs'), and specifies the exact output content: reachability (sdb/ares) and supported operations. This distinguishes it from sibling tools that perform actions on individual TVs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'Start here to see the park' provides clear contextual guidance that this is the entry point for discovering available TVs and their capabilities. While it doesn't explicitly mention alternatives or exclusions, the instruction to start here implicitly tells the agent to use this tool before engaging with device-specific operations.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_evaluateA

Run arbitrary JavaScript in the app page and return the value (escape hatch). Use for custom assertions, reading app state, or restoring localStorage after a debug relaunch. Old TVs are Chrome 38 — keep the expression ES5.

ParametersJSON Schema
NameRequiredDescriptionDefault
deviceNoDevice id from devices.json. Omit to use the default device.
expressionYesJS expression to evaluate in the page.
awaitPromiseNoAwait a returned promise (default true).

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses the arbitrary JavaScript execution nature and 'escape hatch' status, indicating a powerful and potentially side-effectful operation. It also adds the ES5 compatibility constraint for old TVs, which affects how expressions must be written.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three concise sentences front-loaded with the primary function, followed by use cases and a constraint. It contains no filler and every sentence contributes necessary information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is complex (arbitrary code execution) with three parameters and no output schema. The description covers the main purpose, usage contexts, and an important environmental constraint, which is enough for an agent to select and invoke it correctly. It doesn't detail error behavior, but that is not critical for selection.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds value by noting the ES5 constraint for the expression parameter and giving concrete use cases (e.g., localStorage restoration) that clarify expected expression semantics, going beyond the bare schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool runs arbitrary JavaScript in the app page and returns the value, positioning it as an 'escape hatch.' This verb+resource+output structure distinguishes it from sibling tools like tv_state or tv_console, which focus on other actions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly lists use cases: 'custom assertions, reading app state, or restoring localStorage after a debug relaunch.' It frames the tool as an escape hatch, implying use when standard tools don't suffice, though it doesn't name specific alternatives or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_gotoA

Press a direction repeatedly until the FOCUSED element matches a target (text / CSS selector / testid). Bounded by maxSteps, a deadline, and two structural stops: focus that stopped moving (edge of a list) and focus that wrapped around to a position already visited. Use this instead of guessing "press DOWN 7 times".

ParametersJSON Schema
NameRequiredDescriptionDefault
textNoStop when the focused element's text contains this (case-insensitive).
deviceNoDevice id from devices.json. Omit to use the default device.
testidNoStop when the focused element has this data-testid / data-export-id.
maxStepsNoMaximum presses (default 30).
selectorNoStop when the focused element matches this CSS selector.
directionYesDirection to travel in.
deadlineMsNoWall-clock budget (default 45000).

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and does well: it discloses safety bounds (maxSteps, deadline) and two specific structural stop conditions (focus not moving, focus wrapping to a previously visited position). It does not describe failure/return semantics, which is a minor gap, but the core runtime behavior is transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences: the first states purpose, the second lists stopping safeguards, and the third gives usage guidance. No filler, information is front-loaded, and every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has 7 parameters and no output schema, but the description plus schema explain what the tool does, how it stops, and when to use it. The only notable gap is lack of explicit return/failure behavior, but the core navigation semantics are well covered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already provides 100% parameter coverage with meaningful descriptions (target types, defaults, allowed directions). The description adds contextual use information but no new parameter-level semantics, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's action: pressing a direction repeatedly until the focused element matches a target (text/CSS selector/testid). It distinguishes itself from sibling tv_press by emphasizing repeated conditional navigation rather than a single fixed press, and the 'use this instead' phrasing makes its unique role obvious.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly recommends using this tool instead of guessing a fixed number of presses, which is actionable guidance for when to choose it. It does not enumerate all alternative sibling tools or explicit when-not cases, but the context is clear enough for an agent to make a reasonable choice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_heapA

Take a heap snapshot on the device and/or compare two of them — the tool for "the heap grew and never came back". Leak hunt: tv_heap action:"snapshot" (before) -> do the scenario (tv_press / tv_menu / tv_sequence) -> tv_heap action:"snapshot" (after) -> tv_heap action:"diff" with the two paths. A snapshot writes a .heapsnapshot file (open it in Chrome DevTools -> Memory -> Load) and returns the Summary view in numbers: total nodes and shallow size, how many DETACHED DOM nodes are still retained, and the top-N constructors by shallow size. diff returns the deltas — which constructors gained objects and bytes (topGrowth) and which lost them (topShrink), like the DevTools Comparison view. Retainer paths ("who holds this") and retained/dominator sizes are deliberately NOT computed: load the saved files in DevTools for those. A snapshot forces a full GC and pauses V8 for a long time (it can take a minute on a TV), so it is refused while a tv_profile recording is running. Needs the HeapProfiler domain — fine on tizen55/webos7/pc, best-effort on webOS 3. diff is a pure file operation: no device needed.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNosnapshot: where to write the .heapsnapshot. Defaults to a scratch path.
topNNoHow many constructors to report (default 20).
afterNodiff: path to the later .heapsnapshot.
actionYessnapshot: take one on the device. diff: compare two files already on disk.
beforeNodiff: path to the earlier .heapsnapshot.
deviceNoDevice id from devices.json. Omit to use the default device.
timeoutMsNosnapshot: give up after this long (default 120000 — a full heap off a slow TV legitimately takes tens of seconds).

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden. It discloses side effects (full GC, long V8 pause, can take a minute on TV), refusal while a profile runs, platform compatibility (tizen55/webos7/pc, best-effort on webOS 3), and what is deliberately NOT computed (retainer paths, retained/dominator sizes). This is exemplary transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than typical but every sentence carries valuable information: purpose, workflow, output, limitations, platform support, and operational constraints. It is front-loaded with the main purpose and then logically structured. It could be slightly tighter, but the density is justified for a complex tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite having no output schema, the description explains exactly what the return values are (Summary view numbers, diff deltas with topGrowth/topShrink). It covers prerequisites (HeapProfiler domain), edge cases (diff is pure file operation with no device needed), and performance caveats. This is complete enough for an agent to decide when and how to invoke it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description adds workflow context (snapshot before/after, diff with two paths), clarifies the output of each action, and disambiguates the 'path' default ('Defaults to a scratch path') and 'topN' semantics. It enriches the schema's bare descriptions with meaningful usage context.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Take a heap snapshot on the device and/or compare two of them'. It clearly distinguishes from sibling tools (e.g., tv_profile, tv_console) by focusing on heap analysis. It also explains the two actions (snapshot/diff) and their outputs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides an explicit leak-hunting workflow with before/after steps, names sibling tools (tv_press/tv_menu/tv_sequence) as part of the scenario, and explicitly states when NOT to use it (refused during tv_profile recording). It also directs users to DevTools for retainer paths, covering alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_installA

Install an app package on a TV (.wgt for Tizen, .ipk for webOS). Provide an absolute path. Set uninstallFirst:true when an app signed with a different certificate is already installed.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesAbsolute path to the .wgt/.ipk package.
deviceNoDevice id from devices.json. Omit to use the default device.
uninstallFirstNoUninstall the app id from this device before installing (fixes "Author certificate not match").

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the safety burden. It discloses the mutating nature of install, the file type requirements, and the conditional uninstall behavior with a specific reason ('Author certificate not match'). Missing details like error handling or device state are minor for this tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the main action. No redundant text; every phrase carries meaningful information or a practical instruction.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple three-parameter tool with no output schema and no annotations, the description covers the essential behavioral nuances. It could mention missing details like prerequisites (e.g., device on) but the schema and description together are sufficient for basic usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so parameters are already documented. The description adds value by explaining when to set uninstallFirst (different certificate case) and reinforcing the absolute path requirement, going beyond the raw schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description starts with a specific verb and resource: 'Install an app package on a TV', and further distinguishes platforms (.wgt for Tizen, .ipk for webOS). This clearly differentiates the tool from siblings like tv_launch or tv_state.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides concrete usage instructions: provide an absolute path, and set uninstallFirst:true when a certificate mismatch is expected. It does not explicitly mention when not to use the tool or alternatives, but the usage context is clear enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_launchA

Debug-launch the app and attach over CDP. Establishes the session used by all other tools. By default it kills any running instance first for a deterministic fresh start. reload:true reloads the page in place (same process, keeps localStorage). relaunch:true forces a fresh kill+launch. attach:true reuses the inspector of an app already running in debug, keeping its state.

ParametersJSON Schema
NameRequiredDescriptionDefault
attachNoAttach to a running instance without killing it (skips the fresh-start kill).
deviceNoDevice id from devices.json. Omit to use the default device.
reloadNoReload the page in place when already attached (does not clear localStorage).
relaunchNoForce a fresh kill + debug-launch even if already attached.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations to fall back on, the description covers the key side effects: the default kill-and-relaunch for a fresh start, state preservation on reload, and inspector reuse on attach. This goes well beyond the schema in explaining behavioral nuances.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a tight paragraph of five sentences, front-loading the core purpose and then efficiently explaining each mode. Every sentence contributes value without redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no output schema and four optional parameters, it covers the essential behavior, the default destructive start, and the three modes. It omits details about return values or readiness signaling, but these are not critical for tool selection.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Though schema coverage is 100%, the description adds extra meaning—e.g., 'attach' reuses the inspector and keeps the app's state, and 'reload' happens in the same process. These details are not present in the schema's parameter descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies a specific action ('Debug-launch the app and attach over CDP') and a unique role ('Establishes the session used by all other tools'), clearly distinguishing it from sibling tools that operate after launch.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explains the primary usage context (starting the session) and gives explicit guidance on when to use each mode (reload, relaunch, attach). It does not explicitly name alternatives, but the session-establishing role makes the intended order clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_menuA

Move focus into the app's main menu and pick a section by name. Omit item to just open the menu and list its sections. Requires a menu block in the app profile (apps/.json) — the MCP itself knows nothing about any particular app's markup.

ParametersJSON Schema
NameRequiredDescriptionDefault
itemNoSection title to select (case-insensitive substring). Omit to only open the menu and list items.
deviceNoDevice id from devices.json. Omit to use the default device.
selectNoPress ENTER on the item once focused (default true).
maxOpenPressesNoUpper bound on presses of the menu key while travelling to the sidebar (default 20; it takes one press per column).

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses a key limitation about the menu block requirement and suggests a non-destructive focus action. However, it does not mention that by default it also presses ENTER after focusing (via the `select` parameter), nor does it describe potential side effects or failure modes. It adds some context but is not rich.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences long, starting with the primary action and then covering the optional mode and prerequisite. Every sentence is informative and there is no filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with four optional parameters fully described in the schema and no output schema, the description provides enough context about the purpose, the optional modes, and the necessary app profile prerequisite. It could mention more about the use of the `menu` key or behavior when no menu block exists, but overall it is adequately complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% parameter description coverage; each of the four parameters already has a meaningful explanation. The description adds no new parameter semantics beyond what is in the schema, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function with a specific verb and resource: 'Move focus into the app's main menu and pick a section by name.' It also explains the alternate use case (omitting `item` to just open the menu and list sections), and this distinguishes it from siblings like tv_press or tv_goto which are more generic navigation tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use the tool (to interact with an app's main menu) and an explicit prerequisite: 'Requires a `menu` block in the app profile (apps/<app>.json)'. It also discloses a limitation ('the MCP itself knows nothing about any particular app's markup'). It does not explicitly name alternatives or exclusions, but the context is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_pressA

Send a remote key. Names: UP/DOWN/LEFT/RIGHT/ENTER/BACK/MENU/RED/GREEN/YELLOW/BLUE/PLAY/PAUSE/PAGE_UP/... (or a raw numeric keyCode). durationMs holds the key (long-press); repeat+intervalMs sends a burst (e.g. move several tiles). Returns the focused element after the press.

ParametersJSON Schema
NameRequiredDescriptionDefault
keyYesKey name (case-insensitive) or raw numeric keyCode.
deviceNoDevice id from devices.json. Omit to use the default device.
repeatNoSend the press N times (default 1).
durationMsNoHold duration in ms for a long-press. Omit or 0 for a normal press.
intervalMsNoDelay between repeats in ms (default 250).

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the transparency burden and does so well. It discloses that durationMs causes a long-press, that repeat+intervalMs sends a burst, and that the tool returns the focused element after the press. These are valuable behavioral details beyond basic 'send a key'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three concise sentences with the main action front-loaded. The key list is compact and the burst/long-press explanation is efficient. No filler or redundancy with the schema; every clause adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is complete for a tool of this complexity: it covers the input format, timing behaviors, and return value. The output schema is absent, but the description explicitly states what the call returns (focused element), making the tool self-contained. Sibling tools are many, but the description's clarity prevents confusion.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema already describes all 5 parameters (100% coverage), so the baseline is 3. The description adds meaning by explaining the combo effect of repeat+intervalMs as a burst and giving a concrete use case ('move several tiles'). It also clarifies that durationMs 'holds' the key, reinforcing the schema's long-press semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clear verb+resource: 'Send a remote key.' It explicitly lists supported key names and raw numeric keyCodes, distinguishing this generic key-press tool from sibling tools like tv_menu or tv_goto. The description's scope (remote key press with timing options) leaves no ambiguity about what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear context for when to use the options: durationMs for long-press, repeat+intervalMs for bursts (e.g., moving several tiles). It doesn't explicitly name alternatives or exclusion criteria, but the description's focus on raw key presses implies it's the tool for direct input, while other tools like tv_sequence or tv_goto handle higher-level actions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_profileA

Record a JS CPU profile on the device, and/or read memory & layout metrics. action:"start" begins sampling, then do the thing you want to measure (tv_press / tv_goto / a scroll), then action:"stop" writes a .cpuprofile file (open it in Chrome DevTools -> Performance -> Load profile) and returns a top-N summary of self time by function and by file. start and stop each also take a Performance.getMetrics reading, so stop reports before/after/diff per metric (JSHeapUsedSize, Nodes, JSEventListeners, LayoutCount, RecalcStyleCount, cumulative Duration counters) — that is how you catch growth the CPU profile cannot see. action:"metrics" is just that reading, with no recording. On a minified production build pass sourceMap (the app.js.map of THAT build) to get readable names. The CPU profile works on the whole park (Profiler exists down to Chrome 38); metrics need Chromium 60+ (tizen55, pc) — on webOS 3 action:"metrics" fails with a clear message, while start/stop still return the profile with metrics:null and a warning.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNostop: where to write the .cpuprofile. Defaults to a scratch path.
topNNostop: how many functions/files to report (default 20).
actionYesstart a recording, stop it and get the result, or just read the metrics right now.
deviceNoDevice id from devices.json. Omit to use the default device.
sourceMapNostop: path to the .map of the build running on the device. Only the top-N frames are de-minified; a map that fails to load degrades to a warning.
collectGarbageNoForce a GC right before this reading (default false). Turn it on for leak hunting — on stop it makes the heap diff show what is really retained instead of garbage not collected yet. It costs a GC pause, which is why it is off by default inside a recording.
samplingIntervalUsNostart: sampling interval in microseconds (default 1000). Raise it (e.g. 4000) for long recordings on a weak TV, where sampling itself costs.

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so thoroughly: it discloses side effects (writes a .cpuprofile file), failure behavior on unsupported platforms (metrics fails with a clear message, start/stop return metrics:null with a warning), GC side effects, and degradation of sourceMap handling. No contradictions with structured data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence earns its place: overview front-loads the purpose, then action behavior, output format, sourceMap handling, platform caveats, and parameter advice. There is no redundancy or filler; the structure follows a logical flow from what → how → edge cases.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description fully covers return values: the top-N self-time summary, before/after/diff metrics, and the metrics:null + warning case. It also covers error modes, parameter interactions, and usage patterns, making the tool fully understandable without external references.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Even though the schema covers 100% of parameters, the description adds substantial context beyond the schema: the action enum values are explained in narrative, path/topN/sourceMap are tied to the stop action, collectGarbage is recommended for leak hunting with a GC-pause tradeoff, and samplingIntervalUs is advised for weak devices. This exceeds the schema's basic descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource pair: 'Record a JS CPU profile on the device, and/or read memory & layout metrics.' It clearly distinguishes from sibling tools like tv_heap by focusing on CPU profiling and Performance metrics, and the three actions (start/stop/metrics) are precisely defined.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit workflow is given: 'action:"start" begins sampling, then do the thing you want to measure (tv_press / tv_goto / a scroll), then action:"stop"'. It also explains when to use the metrics action to catch growth invisible to CPU profiles, and provides platform-compatibility guidance for when metrics will fail.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_screenshotA

Capture the app frame via CDP and save a PNG. NOTE: on Samsung/Tizen the secure video/overlay plane often makes captureScreenshot hang or return black — for playback verdicts prefer tv_video_state and a human glance at the physical TV. UI screens (menus, focus, tiles) usually capture fine.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNoWhere to write the PNG. Defaults to a scratch path.
deviceNoDevice id from devices.json. Omit to use the default device.
timeoutMsNoGive up after this many ms (default 6000).

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses the meaningful risk that captureScreenshot can hang or return black on Samsung/Tizen due to the secure video/overlay plane, and clarifies that UI screens are generally safe. This goes well beyond a bare 'take a screenshot' statement, though it could mention error codes or recovery behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences: the first states the action and output; the second delivers the crucial caveat and alternative usage. No filler, information is front-loaded and immediately actionable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and no output schema, the description is complete: it explains the primary purpose, the failure modes, the safe use cases, and points to an alternative for video playback checks. All parameters are already fully documented in the schema, so no additional param context is necessary.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description doesn't add extra detail about path, device, or timeout beyond what the schema already provides, but it confirms the tool saves a PNG and gives a sense of intended output.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description uses a specific verb ('Capture') and clearly identifies the resource ('app frame via CDP') and output (PNG). It also distinguishes itself from the sibling tv_video_state by advising that for playback verdicts, tv_video_state and a human glance are preferred.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use the tool ('UI screens, menus, focus, tiles usually capture fine') and when not to ('for playback verdicts prefer tv_video_state and a human glance at the physical TV'). Names the alternative tool and provides contextual reasoning about Samsung/Tizen limitations.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_sequenceA

Run a whole case body in ONE call, with a verdict, elapsed time and result per step. Steps are objects, one key each: {"launch":{"relaunch":true}} (start from a known state) | {"press":"RIGHT","repeat":2} | {"longpress":"ENTER","durationMs":1500} | {"goto":{"direction":"DOWN","text":"..."}} | {"menu":"Settings"} | {"wait":{"scene":"player"},"timeoutMs":30000} | {"expect":{"selector":"[class*=popup]"}} | {"eval":"ES5 expression"} | {"sleep":1000} | {"videoState":true} | {"state":true} | {"profileStart":true} | {"profileStop":{"path":"…"}} | {"metrics":true}. expect and wait take the same conditions as tv_wait_for; a failing one fails the step. Runs under the device lock so nothing interleaves.

ParametersJSON Schema
NameRequiredDescriptionDefault
stepsYesOrdered steps, see the tool description for the shapes.
deviceNoDevice id from devices.json. Omit to use the default device.
stopOnFailNoStop at the first failing step (default true).

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Describes the lock behavior, step failure semantics for expect/wait, and the result format (verdict, elapsed time, result per step). This goes beyond the schema and provides useful behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but information-dense, with each sentence contributing to understanding. The step examples are structured and easy to parse, though a more compact summary could reduce verbosity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex sequence runner with no output schema, the description covers step types, failure behavior, output summary, and locking. Given the sibling tools list, it gives enough context to invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema covers device and stopOnFail clearly, and the steps parameter is thoroughly explained with concrete object shapes and semantics. The description adds substantial meaning to the steps array, while the other parameters are already self-explanatory.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool runs a sequence of steps in one call and produces a verdict, elapsed time, and per-step results. The detailed step format distinguishes it from the single-action sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'Run a whole case body in ONE call' implies use for multi-step scenarios instead of invoking individual tools. It also references tv_wait_for for condition semantics, giving a clear tie-in to existing alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_stateA

Structured snapshot of the app right now: url, title, visible scenes, the focused element (text, class, path, index/total among its siblings), visible popups and element counts. Read-only — use it to assert a step without pressing anything.

ParametersJSON Schema
NameRequiredDescriptionDefault
deviceNoDevice id from devices.json. Omit to use the default device.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the transparency burden. It explicitly discloses that the tool is read-only and returns a structured snapshot with specific fields. This is useful behavioral context beyond what the schema provides, though it does not discuss error conditions or prerequisites like whether the app must be running.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences long and front-loaded: the first sentence lists the snapshot contents, the second clarifies read-only usage. No filler words or redundant information; every phrase contributes meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has no output schema, so the description's enumeration of returned fields (url, title, visible scenes, focused element, popups, counts) provides essential return-value information. It is complete for a simple read-only snapshot tool, though it could mention prerequisites or error scenarios for full completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100% for the single optional 'device' parameter, so the schema fully explains it. The description adds no parameter-specific information, but the baseline of 3 is appropriate because the schema already documents the parameter adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('snapshot') and resource ('the app'), and enumerates exact contents (url, title, visible scenes, focused element, popups, counts). It clearly distinguishes from sibling tv_video_state by focusing on app state, and the read-only qualifier further differentiates it from mutation tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear usage context: 'use it to assert a step without pressing anything.' This tells the agent when to use it (for assertions) and when not (when interaction is needed). However, it does not explicitly name alternative tools or provide exclusion scenarios, so it falls slightly short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_video_stateA

Programmatic snapshot: whether currentTime is advancing (two samples), readyState, size, muted, src and MediaError code. The reliable way to confirm playback when a screenshot would be black.

ParametersJSON Schema
NameRequiredDescriptionDefault
deviceNoDevice id from devices.json. Omit to use the default device.
sampleGapMsNoGap between the two currentTime samples (default 600).

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of disclosure. It explains the sampling behavior (two samples to determine if currentTime is advancing) and lists the return fields. It does not mention prerequisites (e.g., a video element must exist) or failure modes, but covers the core behavior well.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence that packs all essential information without waste. Every phrase contributes: the resource, the output fields, and the use case.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given there is no output schema, the description compensates by listing the return fields. It also explains the two-sample methodology. It lacks a bit of detail on return value structure or edge cases, but for a simple snapshot tool with 2 optional params, it is largely complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description mentions 'two samples' which relates to sampleGapMs, but does not add meaningful detail beyond what the schema already provides for device and sampleGapMs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('snapshot') and resource ('<video>') and clearly lists the exact fields returned (currentTime advancing, readyState, size, muted, src, MediaError code). It distinguishes itself from sibling tools like tv_screenshot by positioning itself as the reliable way to confirm playback when a screenshot would be black.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear use case: confirming playback when a screenshot would be black. This implies when to use it over tv_screenshot, though it does not explicitly name alternatives or state when not to use it. The guidance is present but not exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tv_wait_forA

Wait until a condition holds, instead of sleeping. Give exactly one condition. stableMs additionally requires it to keep holding, which avoids acting on a half-rendered frame. Returns the elapsed time and the final state.

ParametersJSON Schema
NameRequiredDescriptionDefault
textNoThe page's visible text contains this.
sceneNoA visible scene's class contains this (e.g. player).
deviceNoDevice id from devices.json. Omit to use the default device.
selectorNoA visible element matches this CSS selector.
stableMsNoRequire the condition to hold this long before succeeding (default 0).
focusTextNoFocused element's text contains this (case-insensitive).
timeoutMsNoGive up after this long (default 15000).
expressionNoES5 expression that must evaluate truthy.
intervalMsNoPoll interval (default 250).
selectorGoneNoNo visible element matches this CSS selector (spinner gone, popup closed).
videoAdvancingNoWait until <video> currentTime is actually moving.

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the full behavioral burden. It explains stableMs semantics, the rationale about half-rendered frames, and the return value. However, it does not mention that waiting is bounded by timeoutMs or what happens on timeout, leaving a meaningful gap for an agent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is four short sentences, front-loaded with the purpose, and every sentence adds useful information. There is no fluff or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 11 optional parameters and no output schema, the description ties together the core behavior, the stability requirement, and the return format. It lacks timeout/error semantics, but the schema documents timeoutMs and defaults, making this fairly complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents all 11 parameters with 100% coverage, so by the rubric the baseline is 3. The description adds the stableMs rationale and the 'exactly one condition' rule, but it does not provide substantial additional parameter-level meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb and resource: 'Wait until a condition holds, instead of sleeping.' It also distinguishes the tool from siblings by emphasizing conditional waiting rather than state inspection, and it notes the return value. This is specific and unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when to use the tool ('instead of sleeping') and an important constraint ('Give exactly one condition'). However, it does not explicitly name alternative tools or define when-not-to-use scenarios beyond contrasting with sleeping.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 15 tool updatesv0.2.0
    • First observedtv_console
    • First observedtv_devices
    • First observedtv_evaluate
    • First observedtv_goto
    • First observedtv_heap
    • First observedtv_install
    • First observedtv_launch
    • First observedtv_menu
    • First observedtv_press
    • First observedtv_profile
    • First observedtv_screenshot
    • First observedtv_sequence
    • First observedtv_state
    • First observedtv_video_state
    • First observedtv_wait_for

TDQS

A4.4/5.0
Disambiguation5/5

Each tool targets a clearly distinct aspect of the TV app debugging workflow: state inspection, waiting, input, navigation, video, profiling, heap, etc. There is no meaningful overlap; even similar tools like tv_state and tv_video_state differ in scope (whole app vs. media playback).

Naming Consistency5/5

All tools use the 'tv_' prefix and snake_case naming, with a consistent verb-oriented pattern for actions (install, launch, press, goto, evaluate) and noun-style for state/snapshots. The naming convention is uniform and predictable across the entire set.

Tool Count5/5

15 tools is at the upper edge of the 'well-scoped' range but every tool serves a distinct purpose for TV debugging, covering setup, interaction, verification, and performance analysis. None are redundant, and the count feels appropriate for the domain.

Completeness4/5

The surface covers the full workflow: discover devices, install, launch, interact, inspect state, capture screenshots/video, wait for conditions, run sequences, evaluate JS, and profile/heap. Minor gaps exist (e.g., no dedicated network traffic tool, no uninstall without reinstall), but agents can work around these with the provided tools.

Maintenance

ActivityMaintained
ResponsivenessNo issues

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

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server that connects to your browser to capture screenshots, inspect console logs, network requests, and more via Chrome DevTools Protocol.
    6
    2
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    MCP server for controlling Chromium/Chrome via Chrome DevTools Protocol. Supports cross-platform automation, auto-launch, and automatic reconnection.
    25
    1
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    MCP server for debugging Electron apps: discover, launch, and control apps via Chrome DevTools Protocol and Node inspector, enabling JavaScript evaluation, network/console monitoring, screenshots, profiling, and breakpoints.
    44
    MIT

Latest Blog Posts

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/Ediand11/tv-debug-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server