Skip to main content
Glama
tvtracker-mcp

tvtracker-mcp

by seno76

tvtracker-mcp

MCP-сервер, который находит сериал по описанию сюжета, ведёт прогресс просмотра и отвечает на вопрос «что включить» одним вызовом.

Python MCP SDK Spec Tests Coverage License

Ты: найди сериал, я не помню названия — там сотрудникам хирургически разделяют память

  Severance (2022–, Apple TV+, идёт) · Drama/Science-Fiction/Mystery · 49 мин · 7.6
  Mark Scout leads a team at Lumon Industries, whose employees have undergone a
  severance procedure, which surgically divides their memories between their work…
  → отслеживаю, остановился на 2x03

Найдено за один вызов среди 89 664 сериалов. В TVmaze такого запроса нет вообще: их API ищет только по названию.


Зачем он нужен, если есть API

Обёртка транслирует эндпоинты в инструменты один к одному. Ценность такого сервера нулевая: то же самое агент получил бы через generic-инструмент HTTP.

Сервер оправдан, только когда отвечает на вопросы, на которые ни TVmaze, ни локальная база поодиночке ответить не могут. Здесь таких три:

Вопрос

Почему API не отвечает

Кто считает

«Найди сериал по описанию»

в TVmaze поиска по тексту описаний нет

FTS5 + BM25 по локальному корпусу

«Что у меня накопилось»

нужен личный прогресс, которого у API нет

пересечение трекинга с airstamp

«Стоит ли продолжать»

оценки серий лежат мёртвым грузом

агрегация рейтингов по сезонам

Всё три считает наш код. Это и есть граница между продуктом и обёрткой.

Related MCP server: Movie Search MCP Server

Установка

Нужен uv и Python 3.11+. Ключ API не нужен: TVmaze отдаёт данные без авторизации.

git clone https://github.com/seno76/tvtracker-mcp.git && cd tvtracker-mcp
uv sync --all-groups
cp .env.example .env

uv run tvtracker sync --full     # корпус: ~380 запросов, около четырёх минут
uv run tvtracker status          # сериалов: 89664

Подключение к хосту:

uv run mcp install src/tvtracker/server.py    # Claude Code / Claude Desktop
uv run mcp dev src/tvtracker/server.py        # MCP Inspector, посмотреть примитивы

Транспорт — stdio. Обслуживание базы вынесено в CLI: четыре минуты внутри вызова инструмента — гарантированный таймаут у хоста.


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

Инструмент — это сценарий, а не эндпоинт. Типовой запрос закрывается за один-два вызова; изменения состояния собраны в один инструмент с enum-действием вместо шести. Все возвращают текст, а не сырой JSON, и каждый сериал в выдаче помечен твоим состоянием отслеживания — этого в TVmaze нет ни при каком запросе.

show_search(query="", by="auto"|"title"|"description", genre=None, status=None,
            language=None, year_from=None, year_to=None, min_rating=None,
            limit=8, response_format="concise"|"detailed")

Три режима в одном инструменте, потому что для пользователя это один сценарий:

Режим

Как вызвать

Что делает

по названию

query="Severance"

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

по описанию

query="офис, где разделяют память"

BM25 по очищенным аннотациям — главная функция сервера

по фильтрам

query пустой, genre="Comedy"

отбор каталога без поиска

by="auto" решает сам: короткий запрос — название, фраза — описание. Если название не нашлось, он пробует корпус, прежде чем сказать «ничего», — это экономит целый вызов.

Severance (2022–, Apple TV+, идёт) · Drama/Science-Fiction/Mystery · 49 мин · 7.6
Mark Scout leads a team at Lumon Industries, whose employees have undergone a severance
procedure, which surgically divides their memories between their work and personal lives.
→ отслеживаю, остановился на 2x03

I Need Romance (2011–2014, tvN, завершён) · Drama/Comedy/Romance · 60 мин · 6.8
A drama about the lives of employees at a home shopping company and the love between
friends in their 30s.
→ брошен (1x04)

Это лучшие совпадения по описанию, а не полный список.
Сузить: genre="Science-Fiction" или year_from=2020.

Шесть полей на сериал, а не сорок. image, _links, externals отбрасываются при загрузке и физически не могут попасть в контекст.

show_profile — расскажи про сериал

show_profile(show, response_format="concise"|"detailed")

Агрегат четырёх эндпоинтов TVmaze плюс два блока, которых в API нет: рейтинговый профиль по сезонам и позиция пользователя. Обёртка потратила бы здесь четыре вызова и не дала бы ни того ни другого.

Severance (2022–, Apple TV+, идёт) · Drama/Science-Fiction/Mystery · 49 мин · 7.6
Mark Scout leads a team at Lumon Industries, whose employees have undergone a severance
procedure, which surgically divides their memories between their work and personal lives.
→ отслеживаю, остановился на 2x03

Рейтинг по сезонам — с1: 8.1 (7) · с2: 7.7 (5). Тренд: проседает.

Ты на 2x03. Впереди 2 серии, ~1 ч 40 мин.

Строка «с1: 8.1 · с2: 7.7 · тренд проседает» — это и есть то, ради чего затевалась агрегация: без неё формулировка «перетерпи до 3x05» невозможна.

episode_search(query, show=None, season=None, include_specials=False,
               include_unwatched=False, limit=5)

«Серия, где они застряли в лифте» — запрос, на который TVmaze не отвечает. Работает по кэшу эпизодов, то есть по отслеживаемым сериалам; если сериал указан, а эпизодов нет, подгружает их на лету.

Защита от спойлеров. По умолчанию описания серий после текущей позиции не показываются — только факт наличия. Такого поведения у обёртки не может быть в принципе: оно требует знания прогресса.

Severance 2x01 «Season Two, Part 1» — The severed floor deals with the aftermath.
Severance 2x02 «Season Two, Part 2» — The severed floor deals with the aftermath.
Severance 2x03 «Season Two, Part 3» — The severed floor deals with the aftermath.
Severance 2x04 «Season Two, Part 4» — ты до неё ещё не дошёл, описание скрыто
                                       (include_unwatched=true покажет).
Severance 2x05 «Season Two, Part 5» — ты до неё ещё не дошёл, описание скрыто.

Граница проходит ровно по позиции: просмотренное показано, непросмотренное скрыто, но о его существовании сказано честно.

tracking_update — изменить состояние просмотра

tracking_update(show, action="track"|"untrack"|"progress"|"rate"|"pause"|"finish",
                episode=None, rating=None, note=None)

Один инструмент вместо шести. episode принимает человеческий формат "2x03" (кириллическая «х» тоже). При action="track" эпизоды сериала загружаются и индексируются.

Ответ подтверждает изменение и сразу даёт следующий шаг — иначе «отметил серию» превращается во второй вызов «а что дальше»:

Отмечено: Severance 2x04. Дальше — 2x05, «Season Two, Part 5», 50 мин, вышла.

Единственный инструмент, помеченный как изменяющий данные (readOnlyHint = false). После него сервер шлёт уведомление об изменении ресурсов, и хост перечитывает их сам.

watch_next — что включить

watch_next(minutes=60, include_new=False, limit=3)

Не поиск, а решение. Берёт бэклог, фильтрует по свободному времени (с запасом 10 %), ранжирует по своим оценкам и размеру долга. Тот вызов, ради которого затевается сервер: вопрос «что посмотреть» закрывается одним обращением, без предварительных поисков.

Из накопившегося за 60 мин:
Severance 2x04 «Season Two, Part 4» — 50 мин, накопилось 2, твоя оценка сериала 9
The Bear 3x09 «Course 9» — 30 мин, накопилось 2, твоя оценка сериала 7

Если ничего не укладывается — одна альтернатива, а не список: список из шести вариантов возвращает пользователя ровно в ту растерянность, из-за которой он и спросил.


Ресурсы (5)

Ресурс — данные, которые подаёт приложение, а не добывает модель. Критерий включения один: содержимое нужно в контексте до того, как модель начала рассуждать. Всё, что «может понадобиться, а может нет», — инструмент.

Общие решения:

  • text/plain, а не JSON. JSON тратит на скобки и повторяющиеся имена полей в полтора-два раза больше токенов при той же информации, а читает это только модель.

  • Всё считается из локальной БД. Ответ за миллисекунды, работает даже когда TVmaze лежит.

  • Бюджет — 275 токенов на все пять при ориентире дизайна ~1100. Это цена, которую платишь в каждом запросе, где хост их подставил.

Шаблонных ресурсов (show://{slug}) в дизайне сознательно нет: данные по конкретному сериалу нужны не всегда, а по запросу — это работа show_profile.

tracking://watching — что я смотрю сейчас

Профиль пользователя в этом домене. Любой вопрос про сериалы требует его; оформив это инструментом, вы обязали бы модель догадаться сходить за историей.

Смотрю сейчас (3):
Severance — 2x03, обновлено 2026-09-08, моя оценка 9
The Bear — 3x08, обновлено 2026-09-08, моя оценка 7
Slow Horses — 4x06, обновлено 2026-09-08, моя оценка 8

На паузе (1): Foundation (2x04)

tracking://backlog — накопившиеся серии

Сердце проекта. Такого ответа нет ни в TVmaze, ни в локальной базе: он рождается на их пересечении — позиция из трекинга минус вышедшие по airstamp серии, с отброшенными спецвыпусками и суммированием длительности.

Накопилось (по свежести выхода):
Severance — 2 сер., 1 ч 40 мин, свежая вышла 2 дн. назад, 2x04, 2x05
The Bear — 2 сер., 1 ч, свежая вышла 4 нед. назад, 3x09, 3x10
Slow Horses — 0 серий, новых серий нет

Итого: 4 сер., 2 ч 40 мин. Спецвыпуски не учитываются.

Строка «0 серий, новых серий нет» — тоже ответ: видно, ждать ли продолжения.

  • сериал закончился и долгов больше не будет;

  • серия вышла сегодня, но по UTC ещё не наступила — поэтому берётся airstamp, а не airdate;

  • runtime = null → падаем на averageRuntime сериала, а при отсутствии обоих не выдумываем;

  • дейли-шоу с нумерацией по годам (season: 2026, number: 178);

  • пропущенный сезон — это долг, а не повод считать сериал досмотренным;

  • спецвыпуски и insignificant в долг не идут.

tracking://schedule — календарь моих сериалов

Единственный из пяти, который меняется сам по себе, без действий пользователя. Модель не может знать, что его стоит перепроверить, — значит подавать его должен хост.

Ближайшие 14 дней (сегодня 2026-09-08):
сб 12.09  Severance 2x06 «The Next One»
пн 14.09  Slow Horses 4x07 «Cold Water»

tracking://taste — профиль вкуса

Основание любой рекомендации. Если добывать его инструментом, модель будет то запрашивать его, то нет, и качество советов станет случайным от разговора к разговору.

Профиль вкуса (по 6 сериалам, из них 6 с оценкой):
Люблю:      Drama 7.6 (5)
Прохладно:  Comedy 7 (3)
Досматриваю: 67% из закрытого.
Длина:      предпочитаю 30–60 мин.
Площадки:   Apple TV+ (2), Channel 4 (1), FX (1)
Языки:      English (5), Korean (1)

Последние оценки: Severance 9, The Bear 7, Shogun 9, Slow Horses 8, I Need Romance 5

При выборке меньше пяти оценок добавляется строка «выборка мала — считай эти цифры наброском». Без неё модель выдаёт статистику из четырёх сериалов за устойчивый вкус.

catalog://facets — допустимые значения фильтров

Значения для фильтров show_search:
genre:    Drama, Comedy, Romance, Crime, Action, Adventure, Mystery, Anime, History, …
status:   Ended | Running | To Be Determined | In Development
type:     Scripted | Documentary | Reality | Animation | Variety | Talk Show | Game Show
language: English (44.9k), Russian (6.4k), Japanese (6.2k), … всего 30

Жанры чувствительны к написанию: "Science-Fiction", не "Sci-Fi" и не "sci fi".

Последняя строка — не украшение, а профилактика самой частой ошибки. Без справочника модель угадывает написание, получает пустую выдачу и делает ложный вывод «в каталоге ничего нет».


Промпты (4)

Промпт задаёт рассуждению рамку целиком; инструмент решает подзадачу внутри него. Общее правило: промпты втягивают контекст из ресурсов, а не добывают его инструментами. Шаблон, начинающийся с «сначала вызови такой-то инструмент, чтобы узнать историю», означал бы, что примитивы разложены неверно.

Промпт

Аргументы

Втягивает

Инструментов

Отвечает

whats_next

minutes, mood?

backlog, watching, taste

≤ 1

2–3 варианта, по строке обоснования

catch_up

since?

backlog, schedule, watching

0

что вышло / что впереди / один совет

find_by_vibe

description

facets, taste

1–2

3–5 кандидатов либо один уточняющий вопрос

season_verdict

show

watching, taste

ровно 1

«продолжай» / «перетерпи до N» / «бросай»

Два решения, которые стоит заметить. catch_up требует ровно один совет, а не список: список из шести вариантов возвращает пользователя в ту же растерянность. season_verdict прямо разрешает ответ «бросай» и запрещает подслащивать — доля брошенного в профиле показывает, что пользователь и сам так делает.


Архитектура

Смысл разделения один: доменную логику можно проверить на выдуманных данных за миллисекунды, а транспорт поменять со stdio на HTTP, не тронув её ни на строку. Импорт разрешён только вниз; правило проверяется прогоном по AST.

7  server.py, cli.py         точки входа
6  tools/, resources.py,     слой MCP: регистрация примитивов, никакой логики
   prompts.py
5  runtime.py                как обработчики добираются до зависимостей
4  mapping.py, services.py   строки БД → доменные структуры
3  storage/                  SQLite: схема с FTS5, репозиторий, синхронизация
2  tvmaze/, index/           источник и полнотекстовый поиск
1  domain/                   чистая логика: бэклог, вкус, вердикт, рендер
0  config, errors, log,      основание
   clock

Доменный слой не знает ни про SQL, ни про сеть, ни про MCP, ни про системные часы: «сейчас» приходит аргументом — только поэтому случай «серия вышла сегодня, но по UTC ещё не наступила» вообще поддаётся проверке.

Подробнее — docs/ARCHITECTURE.md.

Данные

Таблица

Что хранит

shows + shows_fts

корпус ~90k сериалов, FTS5 внешнего содержимого

episodes + episodes_fts

лениво: только для отслеживаемых сериалов

tracking

позиция, состояние, оценка, заметка

sync_state

метки последней синхронизации

Корпус полный, эпизоды ленивые. 90k сериалов с описаниями — это один обход и ~77 МБ; эпизоды для всех — миллионы строк. Классический just-in-time retrieval: серии тянутся в момент tracking_update(action="track").

Политика бюджета контекста

  1. Список эпизодов целиком не отдаётся никогда — только окно вокруг позиции.

  2. image, _links, url, externals отбрасываются при загрузке, а не при рендере.

  3. summary чистится от HTML и режется до ~200 символов в concise-режиме.

  4. Любое усечение сопровождается счётчиком и конкретной подсказкой, чем сузить.

  5. Целевой размер concise-ответа — до ~600 токенов.


Тестирование

uv run pytest -m unit -q      # домейн, миллисекунды, без сети и без БД
uv run pytest -q --cov        # весь набор с покрытием
uv run ruff check . && uv run mypy

207 тестов, покрытие 91 % (domain/ — 95–98 % при пороге 90 %). Сети нет ни на одном уровне: HTTP мокается через respx, включая искусственный 429 для проверки backoff и потолка попыток.

Маркер

Что покрывает

Шт.

unit

чистый домейн на фикстурах

127

integration

SQLite в tmp_path, клиент через respx, рендеры ресурсов

43

contract

примитивы MCP через in-memory клиент SDK

24

regression

закреплённые сценарии evals и сам прогонщик

13

Контрактные тесты подключают Client прямо к объекту сервера — без подпроцесса и порта (документация SDK).

Оценка качества

uv run python -m evals.runner    # 12 сценариев, отчёт в evals/reports/

12 из 12 сценариев проходят, все пороги дизайна выполнены:

Порог

Значение

Факт

Типовой сценарий

1–2 вызова

медиана 1

Ответ concise

до ~600 токенов

максимум 351

Сериал с 450 сериями

до 2000 токенов

195

Все пять ресурсов

~1100 токенов

275

Прогон измеряет цену намеченного плана вызовов, а не то, угадает ли модель этот план: для второго нужен агент в петле. Разбор всех правок, сделанных по числам, — таблица «было/стало» в docs/EVALS.md.


Отказы и ограничения

Ошибки чинят следующий вызов. Не «404», а что именно не так и что подставить:

По запросу «Zzqqwx» ничего не найдено.
Или опиши сюжет — я умею искать по описанию (by="description").

Ожидаю "2x03" или season=2, number=3. Получено "S02E03".

Отмечено: Arrested Development. Список серий подтянуть не удалось: источник временно
ограничил доступ. Сама подписка сохранена, повтори через ~10 секунд.

Лимит источника. TVmaze разрешает ~20 запросов за 10 секунд на IP. Клиент разносит запросы (1.8 rps), на 429 уходит в экспоненциальный backoff с джиттером и уважает Retry-After; число попыток ограничено явно, бесконечного ретрая нет.

Непрямая инъекция промпта. Описания сериалов и серий на TVmaze пишут пользователи, и этот текст попадает в контекст модели. Меры: очистка HTML, жёсткое усечение, обрамление маркером источника. Полностью проблема не решается — это ограничение, которое надо знать. Не давайте серверу инструментов с побочными эффектами вне трекинга.

Прочее по безопасности. SQL только параметризованный; строка пользователя экранируется перед попаданием в FTS5-запрос, так что операторы NEAR, *, OR не выполняются; путь к БД берётся из конфига без интерполяции ввода.

Что не сделано

Честный список, а не умолчание:

  • Неделя реального использования — единственная проверка из дизайна, которую не заменяет ни один прогон: какие вопросы задаются на самом деле.

  • sync --updates не гонялся против живого API — покрыт только моками. Полный обход при первом реальном запуске вскрыл коллизию slug, которую моки не ловили.

  • Нет CI и pre-commit — контур гоняется вручную.

  • Python 3.12 не проверялся, хотя requires-python = ">=3.11" его обещает.

  • Опционально по дизайну: гибридный поиск (fastembed + sqlite-vec, слияние с BM25 через RRF) и Streamable HTTP.

Документация

Файл

О чём

docs/LAYOUT.md

что где лежит и зачем — разбор каждого файла и формата

docs/DESIGN.md

дизайн примитивов и обоснование каждого решения

docs/ARCHITECTURE.md

карта слоёв, правило зависимостей, что где тестируется

docs/IMPLEMENTATION_PLAN.md

этапы, система логирования, регрессионный контур

docs/EVALS.md

замеры, таблица «было/стало», разбор правок

Стек

Официальный Python SDK для MCP 2.x, httpx, pydantic-settings. SQLite с FTS5 — из стандартной библиотеки, отдельной поисковой системы не требуется. Разработка: pytest, respx, ruff, mypy --strict.

Атрибуция

Данные о сериалах предоставлены TVmaze и распространяются по лицензии CC BY-SA. Этот проект использует их на тех же условиях и ссылается на TVmaze как на источник. API: https://www.tvmaze.com/api.

Лицензия

MIT для кода — см. LICENSE. Данные — CC BY-SA (см. выше).

Available Tools

5 tools
show_profileПрофиль сериалаA
Read-only

Профиль сериала: факты, рейтинги по сезонам и твоя позиция в нём.

    Показывает, где сериал раскачивается и где проседает, — этого нет ни в одном
    клиенте TVmaze, хотя оценка каждой серии в API лежит.
    

Inputs: show (required), response_format. Returns: result

tracking_updateОтметить просмотрA
Idempotent

Изменить состояние просмотра: подписаться, отметить серию, оценить, бросить.

    При `action="track"` эпизоды сериала загружаются и индексируются — после этого
    по ним работает поиск и считается бэклог.
    

Inputs: show (required), action (required), note, rating, +1 more. Returns: result

watch_nextЧто включитьA
Read-only

Выбрать, что посмотреть прямо сейчас, из накопившегося.

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

Inputs: limit, minutes, include_new. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 5 tools

    Disambiguation5/5

    Each tool targets a distinct workflow stage: show discovery, show profile viewing, episode search within tracked shows, tracking state updates, and next-episode recommendation. The descriptions clearly separate show-level search from episode-level search, so there is little risk of selecting the wrong tool.

    Naming Consistency3/5

    show_search and episode_search follow a consistent entity_search pattern, but show_profile uses entity_noun, tracking_update uses action-based naming, and watch_next is a verb phrase. All names are readable, but the set mixes naming conventions rather than following a uniform verb_noun or object_action pattern.

    Tool Count5/5

    Five tools is well-scoped for a personal TV tracking server: discovery, detail, search, state mutation, and recommendation are each covered by one focused tool. No tool feels redundant or missing from the core set.

    Completeness3/5

    The main tracking workflow is mostly covered, but there is no explicit way to list all tracked shows, which is a notable gap for a tracking server. An agent would have to rely on broad searches or the watch_next recommendation to reconstruct the user's watchlist, creating potential dead ends.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      D
      quality
      D
      maintenance
      An MCP server that allows users to search for movies, get detailed information, receive genre-based recommendations, and discover popular/trending films using OMDb and TMDb APIs.
      5
      15 npm
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      An MCP server that enables users to search for movie and TV show resources across multiple sources and validate video link playability. It supports both STDIO and SSE transport modes for seamless integration with AI applications.
      46 npm
      5
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      An MCP server that wraps the TMDB API, enabling search of movies and TV shows, retrieval of details, trending titles, recommendations, and streaming provider information.
      8
      -