tv-debug-mcp
The tv-debug-mcp server is an MCP toolset for semi-manual QA testing of smart TV apps and local Chrome, using Chrome DevTools Protocol. It provides:
Device & App Management: List configured TVs/browsers, check capabilities, install .wgt/.ipk packages, and manage app sessions (launch, attach, reload, relaunch).
Remote Control & Navigation: Simulate remote key presses (direction, media, digits, color keys) with long-press and repeats; navigate to elements by direction keys; interact with app menus.
UI Inspection: Capture screenshots, get structured state (URL, focused element, scenes, popups), read console logs, and check video playback status.
Network Debugging: Log all requests with filtering, read response bodies, export as curl or HAR 1.2, and assert expected/absent requests.
Performance Profiling: Record CPU profiles (with source map deminification), take and compare heap snapshots to detect memory leaks, and collect metrics (JS heap, DOM nodes, layout/recalc counts).
Automated Test Sequences: Run multi-step test cases (launch, press, goto, wait, expect, etc.) with per-step verdicts and elapsed times.
Arbitrary JavaScript Execution: Evaluate JS in the app page for custom assertions or state manipulation (ES5 compatible).
Cross-Platform: Works on Tizen, webOS (including old Chrome 38), and local Chrome, with consistent tooling.
Allows testing Smart TV apps on Samsung Tizen devices via Chrome DevTools Protocol, providing tools for device management, app control, navigation, and debugging.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@tv-debug-mcpPress OK and check video state."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
Чтобы гонять своё приложение, а не фикстуру:
в
devices.jsonописать устройство (platform,appId,hostдля ТВ илиurlдля браузера) — поля и их проверки описаны в «Парк устройств»;завести
apps/<id>.jsonс селекторами приложения и сослаться на него полем"app"— см. «App-профиль», готовый пример лежит вapps/fixture.json;для ТВ — 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 и берут.
Переменные окружения
Переменная | Что задаёт | Если не задана |
| путь к |
|
| бинарь Chrome для |
|
| устройство для платформенных приёмок ( |
|
| куда |
|
| дополнительный прогон | прогон только против встроенной фикстуры |
| куда падают артефакты ( | системный временный каталог |
Зачем не 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)
Тул | Что делает |
| Парк из |
| Установка билда ( |
| Debug-запуск + attach по CDP. Режимы: свежий старт / |
| Клавиша пульта. |
| Структурный снимок: url, заголовок, видимые сцены, фокус (текст, класс, путь, индекс/всего), попапы, счётчики |
| Раскладка экрана одним вызовом: ряды вокруг фокуса, их элементы с рефами |
| Записать путь физическим пультом и скомпилировать в готовый |
| Ожидание условия вместо |
| Жать направление, пока сфокусированный элемент не совпадёт с целью (имя из профиля, текст, селектор, testid). Ограничен |
| Войти в меню приложения и выбрать раздел по имени; без имени — открыть и вернуть список разделов |
| Весь кейс одним вызовом: вердикт, время и результат по каждому шагу, под device-lock |
| PNG кадра. В браузере работает всегда; на Tizen деградирует с пометкой (secure/overlay plane). Движок, который вообще не отдаёт кадр, ловится один раз: первый вызов выжигает таймаут, все следующие в этой сессии отказывают мгновенно |
| Консоль / исключения / упавшие запросы с момента launch. Все уровни, фильтр, счётчик отброшенного буфером |
| Полный лог запросов с момента launch: url, метод, статус, тело POST. Чтение тела ответа по |
| Программный снимок |
| Произвольный JS в странице (escape hatch). На старых ТВ — только ES5 |
| Запись JS CPU-профиля ( |
| Снапшот кучи на устройстве ( |
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_press → tv_state → tv_press → tv_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):
profile— блокsnapshotизapps/<id>.json(row/item/label), либоtile/menu.item, если блока нет. Точно, и ряды могут нести подписи.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с таймаутом3×от замеренного (пол 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, исключённый из попап-сканов, снапшота и дедупликации наблюдений, и переустанавливаемый вместе с рекордером при реаттаче. Человек с пультом должен видеть, что запись идёт, иначе каждый прогон начинается с вопроса «а оно вообще пишет?». В скриншоты бейдж попадёт — про это есть строка в чек-листе.
Деградация по движкам:
Движок | Что не так | Что возвращаем |
весь парк | нет | поллинг — единственный путь, одна реализация |
Chrome 38 (webOS 3), WebKit 538 (webOS 2) | нет |
|
webOS 2 | доставка клавиш физического пульта в webview не гарантирована — часть кнопок съедает лаунчер |
|
Tizen | скриншот виснет | шаг скриншота не эмитится, вердикт по |
любой | обрыв сокета | реаттач ловится по идентичности соединения, рекордер переустанавливается, в предупреждениях сказано, что события в дыре потеряны |
Нового вида шага в 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— часы хоста, момент приёма события, а не CDPtimestamp: монотонные часы движков разных поколений несравнимы ни между собой, ни с хостом, а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— diffPerformance.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). diff — delta по тоталам плюс 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)
Developer Mode на ТВ: Apps → набрать
12345на пульте → Developer mode: On → вписать IP машины, с которой будете подключаться → перезагрузить ТВ. Обновление прошивки его выключает.Tizen Studio CLI в PATH — нужны
sdbиtizen:~/tizen-studio/toolsи~/tizen-studio/tools/ide/bin.Подключение:
sdb connect <ip>:26101(порт по умолчанию, вdevices.jsonпереопределяется полемsdbPort).Author-сертификат Samsung, которым подписан
.wgt. Билд, подписанный другим сертификатом, поверх старого не встаёт —tv_install {"uninstallFirst": true}сносит и ставит заново; это и есть лечение «Author certificate not match».
Проверка: sdb devices — устройство должно быть в состоянии device. unauthorized значит, что на ТВ не подтвердили подключение или Developer Mode слетел.
webOS (LG)
Developer Mode: поставить приложение Developer Mode из LG Content Store, войти аккаунтом с developer.lge.com, включить Dev Mode. Ключ живёт ограниченное время, в приложении есть продление; протухший ключ снаружи выглядит как «устройство не отвечает».
ares-cli:
npm i -g @webosose/ares-cli.Завести устройство:
ares-setup-device. ⚠️ Вdevices.jsonв полеdeviceидёт имя из ares, а не IP — адресация у webOS-адаптера именная.
Проверка: ares-device-info -d <name> отдаёт модель и версию webOS; ares-setup-device --list показывает всё заведённое.
PC (браузерный режим)
Chrome на машине. Путь по умолчанию — macOS-овый, переопределяется
TV_DEBUG_CHROMEили полемchromePathустройства.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 app-профиля: читается |
| все | человекочитаемые подписи, видны в выводе |
| tizen, webos | id приложения на устройстве ( |
| tizen, vidaa | IP телевизора. У vidaa это весь адрес: инспектор слушает на самом ТВ (в отличие от webOS, где |
| vidaa | порт DevTools-инспектора на ТВ. Не указан — узкий автоскан 9222–9230. На VIDAA 9 (50A53FEVS) это 9226, на старых прошивках встречался 9223. Dev-режим включается пультом: |
| tizen | порт sdb, по умолчанию |
| tizen | цель для |
| tizen | локальный порт под |
| webos | имя устройства из |
| pc | адрес dev-сервера, например |
| pc | бинарь Chrome именно для этого устройства; перебивает |
| pc | дополнительные аргументы к Chrome поверх обязательных |
| pc | использовать этот каталог профиля вместо одноразового. Тогда профиль считается чужим и на dispose не удаляется — так живёт залогиненный Chrome, который не хочется логинить заново каждый прогон |
| pc |
|
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 — почему это два разных эксперимента
ТВ | Браузер по умолчанию | Браузер | |
Механизм | page-side |
| page-side |
| нет | да | нет |
Куда летит |
| реально сфокусированный элемент |
|
Дефолтные действия браузера | нет | да | нет |
Кейс может быть зелёным в браузере и красным на ТВ (ветка 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-канала, поэтому канал закрывается сразу после разбора порта.Надёжный kill —
sdb 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 сериализован — параллельные вызовы не запускают апп дважды.
Проверено
Прогон | Что |
| 237/237 — честный статус офлайн-устройства, перечитка конфига без рестарта, отказ при дублях id, выживание без |
| 18/18 на Samsung UE50TU8510 — launch, движение фокуса, ES5-проба видео, |
| 20/20 на Samsung UE49MU6103 (Tizen 3.0) — |
| 19/19 на LG 40UF771V (webOS 2.2 / WebKit 538.2) — движок без CDP: аттач через |
| 25/25 на Samsung UE49MU6103 (Tizen 3.0 / Chromium 47, protocol 1.1) — движок без |
| 125/125 в Chrome — capabilities, отказ |
| LG 49UJ639V (webOS 3.9 / Chrome 38): снапшот 37 МБ / 406k нод / 1271 detached за 13 с; после сценария diff показал +8.3 МБ, +170k нод, +1859 detached с разбивкой по конструкторам. Целевой webos7 на момент прогона был недоступен |
| LG 49UJ639V (webOS 3.9 / Chrome 38): |
Что этот движок умеет и чего нет, видно по прогону выше: 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_networkon-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 toolstv_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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max entries per bucket (default 60). | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| filter | No | Case-insensitive substring filter on message text (and on failed request URLs). | |
| levels | No | Console levels to include. Omit for all levels. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| device | No | Device id from devices.json. Omit to use the default device. | |
| expression | Yes | JS expression to evaluate in the page. | |
| awaitPromise | No | Await a returned promise (default true). |
TDQS
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.
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.
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.
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.
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.
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".
| Name | Required | Description | Default |
|---|---|---|---|
| text | No | Stop when the focused element's text contains this (case-insensitive). | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| testid | No | Stop when the focused element has this data-testid / data-export-id. | |
| maxSteps | No | Maximum presses (default 30). | |
| selector | No | Stop when the focused element matches this CSS selector. | |
| direction | Yes | Direction to travel in. | |
| deadlineMs | No | Wall-clock budget (default 45000). |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | snapshot: where to write the .heapsnapshot. Defaults to a scratch path. | |
| topN | No | How many constructors to report (default 20). | |
| after | No | diff: path to the later .heapsnapshot. | |
| action | Yes | snapshot: take one on the device. diff: compare two files already on disk. | |
| before | No | diff: path to the earlier .heapsnapshot. | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| timeoutMs | No | snapshot: give up after this long (default 120000 — a full heap off a slow TV legitimately takes tens of seconds). |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Absolute path to the .wgt/.ipk package. | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| uninstallFirst | No | Uninstall the app id from this device before installing (fixes "Author certificate not match"). |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| attach | No | Attach to a running instance without killing it (skips the fresh-start kill). | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| reload | No | Reload the page in place when already attached (does not clear localStorage). | |
| relaunch | No | Force a fresh kill + debug-launch even if already attached. |
TDQS
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.
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.
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.
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.
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.
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_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.
| Name | Required | Description | Default |
|---|---|---|---|
| key | Yes | Key name (case-insensitive) or raw numeric keyCode. | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| repeat | No | Send the press N times (default 1). | |
| durationMs | No | Hold duration in ms for a long-press. Omit or 0 for a normal press. | |
| intervalMs | No | Delay between repeats in ms (default 250). |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | stop: where to write the .cpuprofile. Defaults to a scratch path. | |
| topN | No | stop: how many functions/files to report (default 20). | |
| action | Yes | start a recording, stop it and get the result, or just read the metrics right now. | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| sourceMap | No | stop: 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. | |
| collectGarbage | No | Force 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. | |
| samplingIntervalUs | No | start: sampling interval in microseconds (default 1000). Raise it (e.g. 4000) for long recordings on a weak TV, where sampling itself costs. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Where to write the PNG. Defaults to a scratch path. | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| timeoutMs | No | Give up after this many ms (default 6000). |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| steps | Yes | Ordered steps, see the tool description for the shapes. | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| stopOnFail | No | Stop at the first failing step (default true). |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| device | No | Device id from devices.json. Omit to use the default device. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| device | No | Device id from devices.json. Omit to use the default device. | |
| sampleGapMs | No | Gap between the two currentTime samples (default 600). |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| text | No | The page's visible text contains this. | |
| scene | No | A visible scene's class contains this (e.g. player). | |
| device | No | Device id from devices.json. Omit to use the default device. | |
| selector | No | A visible element matches this CSS selector. | |
| stableMs | No | Require the condition to hold this long before succeeding (default 0). | |
| focusText | No | Focused element's text contains this (case-insensitive). | |
| timeoutMs | No | Give up after this long (default 15000). | |
| expression | No | ES5 expression that must evaluate truthy. | |
| intervalMs | No | Poll interval (default 250). | |
| selectorGone | No | No visible element matches this CSS selector (spinner gone, popup closed). | |
| videoAdvancing | No | Wait until <video> currentTime is actually moving. |
TDQS
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.
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.
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.
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.
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.
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.
15 tool updates
v0.2.0- First observed
tv_console - First observed
tv_devices - First observed
tv_evaluate - First observed
tv_goto - First observed
tv_heap - First observed
tv_install - First observed
tv_launch - First observed
tv_menu - First observed
tv_press - First observed
tv_profile - First observed
tv_screenshot - First observed
tv_sequence - First observed
tv_state - First observed
tv_video_state - First observed
tv_wait_for
TDQS
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).
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.
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.
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
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
MCP server for Mint — AI-powered QA that runs your app in a real browser on every PR.
MCP server for understanding Javascript internals from ECMAScript specification.
MCP server to assist with JxBrowser development.
Remote MCP server for Web3TV creators — manage your account over MCP.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceModular testing and automation MCP servers for Android devices, desktop browsers, canvas games, and visual regression.-
- AlicenseNot gradedqualityDmaintenanceMCP server that connects to your browser to capture screenshots, inspect console logs, network requests, and more via Chrome DevTools Protocol.62MIT
- AlicenseAqualityDmaintenanceMCP server for controlling Chromium/Chrome via Chrome DevTools Protocol. Supports cross-platform automation, auto-launch, and automatic reconnection.251MIT
- AlicenseAqualityCmaintenanceMCP 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.44MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ediand11/tv-debug-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server