@Eugene Sukhodolskiy Eugene Sukhodolskiy authored 2 hours ago
backend tgclient: подключён webhook_router — вебхуки gnexus-auth раньше не обслуживали POST (405 от SPA catch-all, блокировка юзера не отключала бы ключи) 2 hours ago
frontend tgclient: лого под тему Tokyo Night (синий→фиолетовый самолётик на тёмной подложке кита) 2 hours ago
.env.example tgclient: upload_* (voice/file/round) принимают file_path — файл с диска сервера из TGCLIENT_UPLOAD_DIR (только имя файла, пути запрещены — гард против чтения произвольных файлов MCP-ключом); по умолчанию выкл. 3 hours ago
.gitignore tgclient: backend M1-M5 — FastAPI + Telethon + MCP (core, media, voice/rounds, login-тулы) 4 hours ago
README.md tgclient: upload_* (voice/file/round) принимают file_path — файл с диска сервера из TGCLIENT_UPLOAD_DIR (только имя файла, пути запрещены — гард против чтения произвольных файлов MCP-ключом); по умолчанию выкл. 3 hours ago
docker-compose.yml tgclient: backend M1-M5 — FastAPI + Telethon + MCP (core, media, voice/rounds, login-тулы) 4 hours ago
README.md

tgclient-mcp

Telegram-клиент формата MCP для экосистемы Gnexus (конвенции gnexus-handbook): мульти-аккаунтный MTProto юзер-клиент (Telethon), MCP-сервер для ИИ-агентов (/mcp-protocol/, alias /mcp) и SPA-админка, закрытая SSO gnexus-auth, с персональными MCP-токенами.

Возможности

  • Мульти-аккаунтность: каждый SSO-пользователь логинит свои Telegram-аккаунты (phone → код → 2FA в SPA-визарде или текстом через MCP-тулы) и работает только со своими; админ видит все.
  • MCP-каталог тулов (/mcp-protocol/, streamable HTTP + Bearer mcp_*):
    • ядро: accounts_list, me_get, dialogs_list, messages_history, messages_read, messages_search, message_send/reply/edit/delete;
    • медиа: download_media (base64), upload_file; голосовые (upload_voice ogg-opus + waveform 63×5 бит) и кружки (upload_round, mp4-квадрат ≤60 с); каждый upload-тул принимает и base64, и file_path — имя файла внутри каталога сервера TGCLIENT_UPLOAD_DIR (пути запрещены, по умолчанию выкл.);
    • звонки: call_start/call_status/call_discard — сигналинг MTProto (phone.requestCall → ринг у абонента, DH + confirm по voice-calls-спеке); голос не передаётся (Telethon без tgcalls): принявший вызов получает моментальный hangup; статус в RAM (после рестарта сервиса — потерян);
    • справочники: contacts_list, chat_info, chat_participants;
    • текстовый логин: account_login_start/code/password/status/cancel.
  • Ошибки — данные ({"error": code, "detail": ...}), перс. ключи по канону mcp.md (≤10 на юзера, снейпшот роли, plaintext один раз, ревок вместо архива), блокировка юзера гасит ключи тем же 401, rate-limit мутаций и логинов, Synapse-уведомления (логин/логаут/auth-lost/флуд).

Стек

FastAPI + uvicorn, aiosqlite (WAL, одна БД SQLite в /data), Telethon (только StringSession), mcp python-sdk (FastMCP), gnexus-gauth SDK (OAuth PKCE + cookie-сессии + вебхуки), gnexus-synapse (нотификации), Vue 3 + Vite + gnexus-ui-kit (SPA).

Запуск

cp .env.example .env && chmod 600 .env
# заполнить: TGCLIENT_API_ID/_API_HASH (my.telegram.org), TGCLIENT_ADMIN_TOKEN,
# TGCLIENT_AUTH_* ( gnexus-auth приложение), TGCLIENT_SYNAPSE_* (опционально)
docker compose up -d --build
# http://<host>:TGCLIENT_PORT — SPA; MCP: http://<host>:TGCLIENT_PORT/mcp-protocol/

Подключение агента

claude mcp add --transport http tgclient https://<host>/mcp-protocol/ \
  --header "Authorization: Bearer mcp_<персональный ключ>"

Ключ выпускается на странице «MCP-ключи» сервиса; показывается один раз. Статический TGCLIENT_ADMIN_TOKEN — супер-бэкдор для скриптов (роль superadmin).

События в Synapse (10-platform/notifications.md)

Конверты v1, fire-and-forget, клиент gnexus-synapse; конфиг — TGCLIENT_SYNAPSE_URL/_API_KEY (+ _DEFAULT_SOURCE=tgclient-mcp). Кому и куда доставить — решает маршрутизация Synapse; в конверте только факт. payload.user_id — sub gnexus-auth.

событие subject/action prio dedup когда
tg_account_logged_in account/logged_in high день+аккаунт Telegram-сессия добавлена (новая или повторная)
tg_account_logged_out account/logged_out normal — логаут аккаунта из SPA
tg_account_auth_lost account/auth_lost high день+аккаунт MTProto-ключ умер (сессия отозвана в ТГ)
tg_login_failed account/login_failed normal день+логин 3 неверных кода / 2 пароля — логин отменён
tg_flood_wait api/flood_wait high день+аккаунт длинный FloodWait (>60 с) в Telethon
mcp_token_issued token/issued normal — выпуск персонального MCP-ключа
mcp_token_revoked token/revoked normal — ревок ключа (свой или админский)
tg_message_received message/received normal — новое сообщение (text + media-мета: voice/round/duration/waveform)
tg_message_edited message/edited normal — правка сообщения
tg_message_deleted message/deleted low — удаление (ids; ttl 1 ч)

Пуш-события сообщений — то, на что реагирует ИИ-агент/скрипт: подписка через Synapse Target (webhook) — MCP подписок не даёт (stateless request/response). Включается env TGCLIENT_NOTIFY_MESSAGES=1 (payload несёт личные переписки — по умолчанию выключено). Plaintext MCP-ключа в конверты не уходит.

Разработка (без docker)

cd backend && python3 -m venv .venv && .venv/bin/pip install -e .
TGCLIENT_DB_PATH=./data/tgclient.db .venv/bin/uvicorn app.main:app --reload
# SPA: cd frontend && npm ci && npm run dev (vite proxy /api,/auth → 8710)
  • TGCLIENT_AUTH_CLIENT_ID пуст → auth-off: всё принадлежит служебному юзеру local, MCP открыт без ключей (только для разработки!).
  • /api/v1/health → {status, version, accounts:{active,connected,pending_logins}, db}.

Структура

backend/app/
  config.py db.py schema.sql security.py auth.py locales.py synapse_report.py errors.py
  main.py                       # lifespan (telethon-пул, GC, persist), роуты, /mcp, SPA
  api/   auth_routes accounts mcp_tokens admin
  tg/    manager login_flow limits waveform
  mcp/   server context tools serializers
frontend/src/  App.vue router.js api.js i18n/ pages/

Pitfalls (из плана)

  1. Telethon-сессии — только StringSession в БД (SQLiteSession конфликтует с aiosqlite).
  2. TL-объекты сериализуются только через mcp/serializers.py, никогда to_dict(); наружу — id в bot-API-маркировке (юзер >0, группа -id, канал -100id).
  3. Кружок требует mp4 H.264/AAC квадрат ≤60 с, иначе уйдёт как обычное видео; waveform голосового считаем сами (63 семпла × 5 бит).
  4. base64 в JSON ≈ +33%: cap TGCLIENT_MEDIA_MAX_BYTES (по умолчанию 20 MB).
  5. Повторный send_code_request на живую pending-сессию не дёргается; код/пароль не логируются и не хранятся.
  6. FloodWait ≤60 с Telethon спит сам; длинный — 429 как данные + Synapse-репорт.