tbank-myt
# MyT MCP — рабочий календарь и парковка
MCP-сервер для **MyT**, внутреннего приложения Т-Банка для сотрудников: расписание
встреч, ответы на приглашения, отмена встречи, свободные машиноместа и бронь
парковки в офисе.
Это **не банковский** MCP. Другой аккаунт, другие хосты, другая сессия. Если тебе
нужны счета, переводы и продукты — это [tbank-mcp](https://github.com/icyberdeveloper/tbank-mcp),
отдельный сервер; они не пересекаются и не мешают друг другу.
## Что умеет
**10 тулов.** Календарь: `calendar_schedule`, `calendar_event`, `calendar_respond`,
`calendar_cancel`. Парковка и офис: `parking_places`, `parking_book`,
`parking_cancel`, `office_bookings`. Сессия: `myt_status`, `myt_refresh_session`.
**2 скила** — `myt-calendar` и `myt-parking`: порядок вызовов, правила подтверждения
и то, что через MCP делать нельзя.
**Чего тут нет, намеренно:** создать или перенести встречу, забронировать
переговорку, снять бронь рабочего места или локера. Соответствующих запросов у
клиента не наблюдалось, а собирать их наугад на живом рабочем сервисе — это
разослать мусор коллегам или снять чужую бронь.
## Установка
```bash
git clone <этот репозиторий> tbank-myt && cd tbank-myt
python3 -m venv .venv && .venv/bin/pip install -e .
# Пакет называется tbank_myt, а не src: соседний банковский MCP ставит «src», и в
# одном окружении они затирали бы друг друга.
# MCP-сервер:
claude mcp add myt -- ./.venv/bin/python -m tbank_myt.server
# Скилы — КОПИЯ, за репозиторием она не следует. Повторяй после каждого pull,
# иначе установленные скилы описывают старую версию тулов:
cp -r skills/* ~/.claude/skills/
```
## Вход — пароль не доходит до агента
```bash
.venv/bin/python login_cli.py <корпоративный логин> # из клона
# или, если пакет установлен: tbank-myt-login <корпоративный логин>
# [1/2] Пароль (не отображается): ****
# SMS отправлена: +7 XXX ***-**-XX
# [2/2] SMS-код: ****
#
# ✓ ГОТОВО! Сессия сохранена: ~/.local/share/tbank-myt/session.json (права 0600).
```
Тула, принимающего пароль, здесь **нет**. Рабочий пароль открывает не один сервис, а
все рабочие системы, и цена его попадания в транскрипт несопоставима с удобством.
Дальше сессия продлевается сама — обменом refresh-токена перед каждым запросом, за
120 секунд до истечения. Поэтому `токен_живёт_ещё_секунд: 0` в `myt_status()` — это
не «сессия умерла», а «прошёл час с прошлого вызова». `myt_refresh_session()`
запускает обмен принудительно: он нужен, чтобы проверить именно refresh-токен
(access бывает жив, когда refresh уже отозван). Здешний refresh-токен при обмене не
ротируется, так что звать его можно свободно.
Полный вход нужен только когда тул сказал `MYT SESSION EXPIRED`. Работающий
MCP-сервер подхватит новую сессию сам, со следующего же вызова: он сверяет файл
перед каждым обращением. Перезапускать его или переподключать MCP не нужно —
раньше было нужно, и об этом никто не догадывался, потому что тул продолжал
советовать вход, который человек только что сделал.
## Часовой пояс
Kairos отдаёт время встреч в UTC, а тулы приводят его к поясу **сотрудника** и
подписывают в шапке. Пояс не угадывается: в workplacer 66 зданий в восьми поясах, от
`+02:00` до `+10:00`, поэтому он берётся из офиса самого сотрудника.
`MYT_TZ` переопределяет — `+05:00` или `Asia/Yekaterinburg`. Нужен тому, кто уехал,
работает удалённо или сидит не в том офисе, что записан в кадрах. Если офис
определить не удалось, тулы возьмут Москву и **скажут об этом в шапке**, а не
покажут молча неверный час.
## Доверие сертификатам
`kairos.tbank.ru` и `workplacer.tbank.ru` проходят на системных CA. А вот
`magentbep.tcsbank.ru` — хост выдачи и обмена токена — **нет**: он отдаёт цепочку с
Russian Trusted Root CA (Минцифры), которого не везёт ни один системный стор. Без
него не работает ни вход, ни продление, то есть ничего.
Корень приколот по SHA-256 и лежит в `ca/roots/`. Сертификаты никогда не берутся из
сети — подробности в шапке `src/tls.py`.
## Переменные окружения
| Переменная | Что делает |
|---|---|
| `MYT_SESSION` | Путь к файлу сессии (по умолчанию `~/.local/share/tbank-myt/session.json`) |
| `MYT_PASSWORD` | Пароль для `login_cli.py` вместо ввода с клавиатуры |
| `MYT_TZ` | Часовой пояс сотрудника: `+05:00` или `Asia/Yekaterinburg` |
| `MYT_TRACE` | `0` выключает трассировку вызовов |
| `MYT_TRACE_FILE` | Куда писать трассировку вызовов |
| `MYT_EXTRA_CA` | Дополнительный PEM в хранилище доверия |
## Тесты
```bash
.venv/bin/python tests/run_all.py
```
Тесты — обычные скрипты, без pytest; каждый запускается в своём процессе, а логи
уводятся во временный каталог, чтобы прогон не дописывал ничего в реальные файлы.
Один из них, `test_installed_package.py`, проверяет не код, а **установку**: он
поднимает сервер подпроцессом из постороннего каталога и говорит с ним по MCP —
ровно как клиент, который стартует его из домашней директории. Остальные тесты
кладут корень репозитория в `sys.path` и по построению не замечают, что
`pip install -e .` устарел; а видно это только как `Failed to connect` в клиенте,
без объяснения причины. Если этот тест упал — переустанови пакет.
Форма каждого запроса сверена с реальным трафиком клиента и закреплена фикстурой
`tests/fixtures/myt.json`: структура настоящая, а люди, темы встреч, названия офисов
и машина — синтетические. Отдельно проверяется то, что дороже всего стоило понять:
`floorId=nil` уходит литеральной строкой, ответ на встречу пускают не чаще раза в
5 секунд, а `200` с пустым телом на брони и отмене **ничего не значит** — поэтому
тулы перечитывают состояние и печатают его, а не факт успешного вызова.
## Безопасность
- Пароль не проходит через агента: только вход отдельным процессом и `getpass`.
- Файл сессии — `0600`; запись атомарная, и **провал записи не выдаётся за успех**.
- Номер машины и корпоративный логин не попадают в трассировку дословно.
- Захваты трафика и файл сессии — в `.gitignore`.
## Дисклеймер
Неофициальный клиент, сделанный по наблюдаемому поведению приложения. Работает с
рабочим аккаунтом живого человека: пишущие тулы видят коллеги — ответ на встречу
приходит организатору, отмена уведомляет всех участников, а занятое машиноместо
снимается у кого-то другого.
TDQS
Scored across 9 tools
Each tool targets a distinct resource and action: session status vs refresh, calendar schedule/event/respond/cancel, parking availability/booking, and office bookings listing. The descriptions clearly differentiate the two session tools and the calendar action verbs, leaving no ambiguity.
Tool names follow a consistent domain-prefix pattern: myt_ for session, calendar_ for calendar, parking_ for parking, office_ for office bookings. Within each domain, names mix nouns (status, schedule, places) and verbs (refresh, respond, cancel, book), but the overall structure is predictable and readable.
9 tools is well-scoped for the stated purpose: the server covers session management, calendar operations, and parking/office bookings without bloat. Each tool serves a distinct need and earns its place.
The set covers core workflows well: calendar has list/detail/respond/cancel, parking has availability and booking, and office bookings offers a read-only summary. However, obvious gaps exist: parking bookings cannot be canceled, and there is no tool to create or update calendar events, leaving some operations as dead ends.