Skip to main content
Glama

ypt-mcp

MCP-сервер для трекера учёбы YPT (열품타) на базе библиотеки ypt-python.

Работает по stdio-транспорту и через Model Context Protocol открывает доступ к дневным логам учёбы, рейтингам, учебным группам и таймеру занятий.

Возможности

  • Профиль и сводка за сегодня (get_profile)

  • Дневные логи учёбы по датам (get_day_log)

  • Твоё место в рейтинге категории и таблицы лидеров (get_my_rank, get_leaderboard)

  • Поиск групп, мои группы, участники (browse_groups, get_my_groups, get_group_members)

  • Таймер занятий: старт/стоп с учётом сессии (start_study, stop_study)

Related MCP server: UVG Local MCP Server

Требования

  • uv (для запуска через uvx / uv run)

  • Доступ к GitHub-репозиториям derived-functor/ypt-mcp и derived-functor/ypt-private-client (приватные — нужен настроенный git-auth: SSH-ключ или PAT)

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

Вариант 1 — через uvx (основной)

Одна команда, без клонирования:

uvx --from git+https://github.com/derived-functor/ypt-mcp ypt-mcp

uvx сам подтянет ypt-mcp и его зависимость ypt-python (из git+https://github.com/derived-functor/ypt-private-client), соберёт и запустит в изолированном окружении.

При необходимости можно пинить версию: --from git+...@main.

Вариант 2 — локальная разработка через uv run

git clone https://github.com/derived-functor/ypt-mcp
cd ypt-mcp
uv sync
uv run ypt-mcp

При работе над самой библиотекой ypt-python удобно временно вернуть в pyproject.toml локальный источник: [tool.uv.sources] = { path = "/path/to/ypt-private-client", editable = true }.

Аутентификация

Сервер использует кэш JWT-токена ~/.cache/ypt-python/token (общий с ypt-cli). Если токена нет, логинится по переменным окружения:

export YPT_EMAIL="you@example.com"
export YPT_PASSWORD="your-password"

Либо заранее выполни логин через библиотеку:

cd ~/probe/ypt-private-client && uv run ypt-cli login

Приоритет: закэшированный токен → YPT_EMAIL/YPT_PASSWORD. Если токен протух, сервер сам перелогинится и обновит кэш. Пароль нигде не хранится (в кэше только JWT).

Не коммить реальные YPT_EMAIL/YPT_PASSWORD в файлы конфигов — используй интерполяцию {env:VAR} (см. ниже), тогда значения попадут только в окружение.

Подключение клиентов

opencode (opencode.json)

Проектный opencode.json или глобальный ~/.config/opencode/opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "ypt": {
      "type": "local",
      "command": ["uvx", "--from", "git+https://github.com/derived-functor/ypt-mcp", "ypt-mcp"],
      "environment": {
        "YPT_EMAIL": "{env:YPT_EMAIL}",
        "YPT_PASSWORD": "{env:YPT_PASSWORD}"
      }
    }
  }
}

Заметки:

  • command — массив строк, оболочка не используется;

  • {env:VAR} подставляет значение из окружения opencode (не $VAR);

  • после изменения конфига перезапусти opencode — конфиг подхватывается только при старте.

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "ypt": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/derived-functor/ypt-mcp", "ypt-mcp"],
      "env": {
        "YPT_EMAIL": "you@example.com",
        "YPT_PASSWORD": "your-password"
      }
    }
  }
}

Тулы

Тул

Назначение

get_profile

Профиль, категория/страна, предметы, сводка за сегодня

get_day_log(date)

Лог учёбы за дату

get_my_rank(category_id, country_id)

Твоё место в рейтинге категории

get_leaderboard(category_id, country_id, date, ...)

Таблица лидеров категории

browse_groups(...)

Поиск публичных групп

get_my_groups

Твои группы

get_group_members(group_id, country_id)

Участники группы

start_study(subject, ...)

Старт таймера учёбы

stop_study(started_at, ...)

Стоп таймера учёбы

Время отдаётся в часах (study_hours/rest_hours, float) и в сырых миллисекундах (study_ms/rest_ms, int). Даты — YYYY-MM-DD.

get_profile

Без аргументов. Возвращает профиль и сводку за сегодня:

{
  "nickname": "derived-functor",
  "category_code": "ВУЗ",
  "category_id": 94,
  "country_id": 6,
  "subjects": [
    { "id": 131150462, "title": "матеша задачи", "study_hours": 0.0, "archived": false }
  ],
  "day_log": {
    "date": "2026-09-12",
    "study_hours": 0.44,
    "rest_hours": 2.15,
    "subjects": [
      { "subject_id": 131150462, "subject_title": "матеша задачи", "study_hours": 0.44 }
    ]
  }
}

get_day_log(date)

Аргумент

Тип

Описание

date

str

Дата в формате YYYY-MM-DD

{
  "date": "2026-09-12",
  "study_ms": 1586019,
  "study_hours": 0.44,
  "rest_hours": 2.15,
  "max_study_hours": 0.44,
  "added_hours": 0.0,
  "subjects": [
    { "subject_id": 131150462, "subject_title": "матеша задачи", "study_ms": 1586019, "study_hours": 0.44 }
  ]
}

get_my_rank(category_id, country_id)

Аргумент

Тип

Описание

category_id

int

ID категории из get_profile

country_id

int

ID страны из get_profile

Вернёт число (номер места) или null, если рейтинга нет.

get_leaderboard(category_id, country_id, date, page, rank_type, limit)

Аргумент

Тип

Дефолт

Описание

category_id

int

—

ID категории

country_id

int

—

ID страны

date

str

—

Дата YYYY-MM-DD

page

int

1

Номер страницы (20 записей)

rank_type

str

"day"

Период: "day" или "week"

limit

int

20

Сколько участников вернуть

{
  "total_count": 729,
  "members": [
    { "nickname": "lisha.rix", "user_id": 17526047, "study_hours": 11.08, "studicon_id": 0 }
  ]
}

browse_groups(category_id, page, country_id, order_type, only_available, only_open, only_cam)

Аргумент

Тип

Дефолт

Описание

category_id

int

0

Фильтр по категории (0 = все)

page

int

1

Номер страницы

country_id

int | null

null

Фильтр по стране

order_type

str

"promotedAt"

Сортировка

only_available

bool

false

Только группы со свободными местами

only_open

bool

false

Только открытые группы

only_cam

bool

false

Только с камерой

get_my_groups

Без аргументов. Список групп с id, title, owner, member_count.

get_group_members(group_id, country_id)

Аргумент

Тип

Описание

group_id

int

ID группы

country_id

int

ID страны

У каждого участника: user_id, nickname, category, study_hours, studying (учится ли прямо сейчас).

start_study(subject, device_model) / stop_study(started_at, device_model)

Аргумент

Тип

Дефолт

Описание

subject

str

—

Название предмета (для start)

started_at

int | null

null

Старт сессии epoch ms (для stop)

device_model

str

"ypt-mcp"

Модель устройства для API

start_study возвращает started_at (epoch ms) и дневной лог. stop_study без started_at использует записанную сессию (state-файл ~/.cache/ypt-python/study_started_at, тот же, что у ypt-cli).

Сценарии-примеры

Все примеры общаются с сервером по stdio через Python-клиент mcp:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

CMD = ["uvx", "--from", "git+https://github.com/derived-functor/ypt-mcp", "ypt-mcp"]

async def call(tool: str, args: dict):
    params = StdioServerParameters(command=CMD[0], args=CMD[1:])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            res = await session.call_tool(tool, args)
            return "".join(c.text for c in res.content)

async def main():
    # 1. Ежедневный отчёт
    profile = await call("get_profile", {})
    print(profile)

    # 2. Топ категории (id/страну берём из профиля)
    top = await call("get_leaderboard", {
        "category_id": 94, "country_id": 6, "date": "2026-09-12", "limit": 10,
    })
    print(top)

    # 3. Мои группы и участники
    groups = await call("get_my_groups", {})
    members = await call("get_group_members", {"group_id": groups[0]["id"], "country_id": 6})
    print(members)

    # 4. Таймер: старт → рядом повторяющихся вызовов нет → стоп
    started = await call("start_study", {"subject": "матеша задачи"})
    stopped = await call("stop_study", {"started_at": started["started_at"]})
    print(stopped)

asyncio.run(main())

Обработка ошибок

Ситуация

Поведение

Нет токена и нет YPT_EMAIL/YPT_PASSWORD

Ошибка с сообщением «credentials not found»

Протухший токен

Сервер перелогинится и повторит вызов автоматически

stop_study без записанной сессии

Ошибка — нужен started_at

Ошибки YPT API / сети

Пробрасываются выше с кодом/текстом из библиотеки ypt-python

Архитектура

LLM/клиент (opencode, Claude Desktop)
        │  MCP (stdio, JSON-RPC)
        ▼
ypt-mcp  (mcp SDK, тулы, auth-менеджмент)
        │  ypt-python (async-клиент, pydantic-модели)
        ▼
YPT REST API (https://pi.tgclab.com)
  • src/ypt_mcp/server.py — все тулы и логика аутентификации

  • ~/.cache/ypt-python/token — кэш JWT

  • ~/.cache/ypt-python/study_started_at — старт текущей сессии таймера

  • pyproject.toml — зависимости (mcp, ypt-python из git) и entry point

Проверка

Смоук-тест: запустить сервер, выполнить handshake и вывести список тулов.

uvx --from git+https://github.com/derived-functor/ypt-mcp python - <<'EOF'
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(
        command="uvx",
        args=["--from", "git+https://github.com/derived-functor/ypt-mcp", "ypt-mcp"],
    )
    async with stdio_client(params) as (r, w):
        async with ClientSession(r, w) as s:
            await s.initialize()
            print([t.name for t in (await s.list_tools()).tools])

asyncio.run(main())
EOF

Ожидаемый результат — 9 тулов: get_profile, get_day_log, get_my_rank, get_leaderboard, browse_groups, get_my_groups, get_group_members, start_study, stop_study.

Известные особенности

Для работы get_leaderboard в репозитории ypt-private-client исправлен хелпер _get() в src/ypt_python/_models.py: теперь null-значения (которые API присылает для si/tc) игнорируются и заменяются дефолтом вместо падения int(None). Фикс входит в текущий HEAD ветки main и подтягивается автоматически через git-источник ypt-python.

Available Tools

9 tools
browse_groupsB

Browse public study groups.

:param category_id: Filter by category ID; ``0`` means all categories.
    Default ``0``.
:type category_id: int
:param page: Page number. Default ``1``.
:type page: int
:param country_id: Filter by country ID; omit for all countries.
:type country_id: int | None
:param order_type: Sort order, e.g. ``"promotedAt"``. Default
    ``"promotedAt"``.
:type order_type: str
:param only_available: Show only groups with free slots. Default
    ``False``.
:type only_available: bool
:param only_open: Show only open groups. Default ``False``.
:type only_open: bool
:param only_cam: Show only groups with camera verification. Default
    ``False``.
:type only_cam: bool
:returns: List of group mappings with ``id``, ``title``, ``category``,
    ``owner``, ``slogan`` and ``member_count``.
:rtype: list[dict[str, Any]]
:raises RuntimeError: If YPT credentials are not configured.

Inputs: page, only_cam, only_open, country_id, +3 more. Returns: result

get_day_logA

Return your study log for the given date.

:param date: Date in YYYY-MM-DD format, e.g. ``"2026-09-12"``.
:type date: str
:returns: Mapping with ``date``, ``study_ms``/``study_hours``,
    ``rest_ms``/``rest_hours``, ``max_study_ms``/``max_study_hours``,
    ``added_ms``/``added_hours`` and a ``subjects`` list of
    ``{subject_id, subject_title, study_ms, study_hours}``.
:rtype: dict[str, Any]
:raises RuntimeError: If YPT credentials are not configured.

Inputs: date (required). Returns: object

get_group_membersA

Return the members of a study group with their study stats.

:param group_id: Study group ID, e.g. from ``get_my_groups``.
:type group_id: int
:param country_id: Country ID, as returned by ``get_profile``.
:type country_id: int
:returns: List of member mappings with ``user_id``, ``nickname``,
    ``category``, ``study_ms``/``study_hours`` and ``studying``
    (bool, whether the member is currently studying).
:rtype: list[dict[str, Any]]
:raises RuntimeError: If YPT credentials are not configured.

Inputs: group_id (required), country_id (required). Returns: result

get_leaderboardA

Return the category leaderboard (top studiers) for a date.

:param category_id: Category ID, as returned by ``get_profile``.
:type category_id: int
:param country_id: Country ID, as returned by ``get_profile``.
:type country_id: int
:param date: Leaderboard date in YYYY-MM-DD format.
:type date: str
:param page: Page number, 20 entries per page. Default ``1``.
:type page: int
:param rank_type: Ranking period, ``"day"`` or ``"week"``. Default
    ``"day"``.
:type rank_type: str
:param limit: Maximum number of members to return. Default ``20``.
:type limit: int
:returns: Mapping with ``total_count`` and the ``members`` list of
    ``{nickname, user_id, study_ms, study_hours, studicon_id}``.
:rtype: dict[str, Any]
:raises RuntimeError: If YPT credentials are not configured.

Inputs: date (required), country_id (required), category_id (required), page, +2 more. Returns: object

get_my_groupsA

Return the list of study groups you have joined.

:returns: List of group mappings with ``id``, ``title``, ``category``,
    ``owner``, ``slogan`` and ``member_count``.
:rtype: list[dict[str, Any]]
:raises RuntimeError: If YPT credentials are not configured.

Inputs: no named parameters. Returns: result

get_my_rankA

Return your position in a category leaderboard.

:param category_id: Category ID, as returned by ``get_profile``.
:type category_id: int
:param country_id: Country ID, as returned by ``get_profile``.
:type country_id: int
:returns: Your 1-based rank as ``int``, or ``None`` when no rank is
    available for the category.
:rtype: int | None
:raises RuntimeError: If YPT credentials are not configured.

Inputs: country_id (required), category_id (required). Returns: result

get_profileA

Return the logged-in YPT profile with today's study overview.

:returns: Mapping with ``nickname``, ``email``, ``category_code``,
    ``category_id``, ``country_id``, the ``subjects`` list
    (``id``, ``title``, ``study_ms``/``study_hours``, ``archived``) and
    the ``day_log`` for today (see the day-log shape in
    ``get_day_log``).
:rtype: dict[str, Any]
:raises RuntimeError: If YPT credentials are not configured.

Inputs: no named parameters. Returns: object

start_studyA

Start studying a subject and record the session start time.

The session start timestamp is stored on disk (so ``stop_study`` can be
called without arguments) and also returned as ``started_at`` for the
caller to keep.

:param subject: Subject title to start studying, e.g. ``"Матеша"``.
:type subject: str
:param device_model: Device model reported to the YPT API. Defaults to
    ``"ypt-mcp"``.
:type device_model: str
:returns: Mapping with ``started_at`` (epoch ms) plus the full day-log
    shape described in ``get_day_log``.
:rtype: dict[str, Any]
:raises RuntimeError: If YPT credentials are not configured.

Inputs: subject (required), device_model. Returns: object

stop_studyA

Stop the study timer and finish the current session.

If ``started_at`` is omitted the timestamp recorded by :func:`start_study`
is used; the recorded value is cleared after a successful stop.

:param started_at: Session start time in epoch milliseconds, as returned
    by ``start_study``. Optional; defaults to the recorded session.
:type started_at: int | None
:param device_model: Device model reported to the YPT API. Defaults to
    ``"ypt-mcp"``.
:type device_model: str
:returns: The full day-log shape described in ``get_day_log``.
:rtype: dict[str, Any]
:raises RuntimeError: If there is no recorded session and ``started_at``
    is not provided, or if YPT credentials are not configured.

Inputs: started_at, device_model. Returns: object

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 9 tools

    Disambiguation4/5

    Each tool targets a distinct resource or action: groups (joined, browse, members), study sessions (start, stop), profile, day log, rank, and leaderboard. Minor overlap exists because get_profile includes today's day_log, but the descriptions clearly differentiate their primary purposes.

    Naming Consistency5/5

    All tool names follow a consistent snake_case verb_noun pattern (get_, browse_, start_, stop_). The 'my' qualifier is used consistently for personal resources, with no mixing of conventions.

    Tool Count5/5

    Nine tools cover groups, study sessions, logging, and rankings without redundancy. This is well within the 3-15 range for a focused MCP server.

    Completeness4/5

    Core read operations and the study timer lifecycle are fully covered. Missing group join/leave actions and subject creation are minor gaps that an agent can work around, but they prevent full group interaction.

    Maintenance

    ActivityStale
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables local text analysis, statistical calculations, and system information retrieval via the Model Context Protocol over stdio.
      -
    • A
      license
      A
      quality
      B
      maintenance
      Enables any MCP-compatible agent to query a local OpenRhyme activity timeline, search history, and issue control commands over stdio while keeping all data on-machine.
      5
      1
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables MCP clients to connect to Agenzax's REST API over stdio, providing tools for messaging, session management, and review-mode oversight.
      18
      74 npm
      5
      MIT