Skip to main content
Glama
WhiteBite

tg-agent (Telegram → SQLite → MCP)

by WhiteBite

Telegram-группы и темы для агента в opencode

tests

Read-only связка: Telethon (твой аккаунт) → локальный SQLite → MCP-сервер → агент.

Главная идея — агент видит не всё подряд, а только то, что ты отметил сам. Тем может быть десятки, в каналах — годы переписки; вываливать это в контекст бессмысленно. Поэтому:

  1. ты выделяешь нужное (папка в Telegram или точечно темы через watch.py),

  2. это синхронизируется в локальный индекс (инкрементально, старая история — чанками),

  3. агент получает 13 read-only инструментов, которые знают твои алиасы и «что нового с прошлого раза».



Быстрый старт: одна команда

python3 start.py

Она сама решает, что нужно: нет ключей — спросит и запомнит; не залогинен — покажет QR; дальше открывает визуал в браузере, где всё остальное мышкой: 「+ из Telegram」 — список чатов прямо из Telegram (форум/группа/канал, пометка «в индексе»), фильтр, окно дней и кнопка «Слить»; ★ — отдать тему агенту. Ни флагов, ни имён чатов, ни запоминания команд.

Всё описанное ниже — те же шаги, но вручную, для тех, кому нужен точный контроль (в терминале есть и запасной выбор чатов: python3 start.py --cli).

Related MCP server: telegram-user-mcp

Как «визуально выделить нужное»

Вариант 0 — веб-визуал «как в тг» (рекомендую)

python3 ui.py                 # http://127.0.0.1:8899

Три колонки в тёмной теме Telegram Desktop: чаты → темы → сообщения. Рейка слева фильтрует: все / ★ мои / форумы / каналы. Что умеет:

  • звёздочка на теме — отметить для агента (или «★ отметить все темы» на чат);

  • алиас на отмеченную тему — имя, которое агент увидит в tg_watch;

  • синие бейджи — непрочитанное с момента «✓ прочитано»;

  • поиск по чатам и FTS-поиск по всей истории прямо в панели сообщений;

  • 📁 рейка показывает твои папки Telegram — фильтр по составу папки;

  • 📌 баннер закреплённых сообщений темы («readme») над лентой;

  • 📝 баннер дайджеста, если он есть для темы (см. «Дайджесты»);

  • поле «к дате» — прыжок к истории за любое число (полезно для летних архивов);

  • выделение сообщений → «копировать для агента» — вставляет в opencode разом;

  • live: при запущенном live.py лента сама догоняет новые сообщения;

  • кнопка ⟳ — sync.py --watched в фоне (нужны креды; статус виден под поиском).

Выбор пишется в ту же таблицу watch, которую читает MCP-сервер, — агент в opencode видит отметки сразу, без перегенерации конфигов. watch.py остаётся для терминала и скриптов.

Посмотреть без Telegram: python3 seed_demo.py demo.sqlite && TG_DB=demo.sqlite python3 ui.py.

Вариант A — папка в Telegram

В приложении: Settings → Folders → New folder, назови например AI Agent, положи туда нужные каналы и группы. Отмечать в коде ничего не нужно — что в папке, то и читает агент:

python3 sync.py --folder <id папки>     # id видно в веб-версии Telegram в URL папки
sync.py --dry-run --folder 7            # сначала посмотреть, что попадёт

Каналы-вещания из папки берутся автоматически (раз положил — значит нужно). Пополнять папку можно и без приложения — агенту достаточно попросить:

python3 folders.py list                                  # папки и их состав
python3 folders.py add --folder 15 --chat @ai_channel    # доложить чат в папку
python3 folders.py remove --folder 15 --chat @ai_channel

Вариант B — точечно темы внутри одной группы

Когда группа одна (твоя AI), а тем в ней десятки:

python3 watch.py topics --chat @mygroup      # темы с цифрами: сколько сообщений, закрыта ли
python3 watch.py add --chat @mygroup --topic 123456 --alias "ML-новости" --pin
python3 watch.py add --chat @mygroup --topic 789   --alias "Релизы"
python3 watch.py list                        # что отмечено и сколько там непрочитанного
python3 watch.py suggest                     # топ-тем по активности + готовые команды add
python3 sync.py --watched                    # синхронизировать только отмеченное

Алиас — это то, что увидит агент вместо chat_id=-1001234567890 / topic_id=789.

Оба варианта совмещаются: папка даёт каналы, watch.py — темы внутри них.


Инструменты агента (все read-only)

Инструмент

Что делает

tg_watch

Первым делом. Список отмеченного: алиас, название темы, сколько всего сообщений, сколько новых, дата последнего

tg_new

Новые сообщения темы с момента прошлого прочтения. Основной для «что там нового»

tg_read

История темы: date_from/date_to (архивы), before_id+next_before (листание), budget_chars (защита контекста)

tg_find

Полнотекстовый поиск по всей истории, включая посты двухлетней давности (SQLite FTS5, релевантность через bm25)

tg_stats

Активность: разбивка по темам, топ авторов, динамика по дням, медиа

tg_mark_read

Отметить тему прочитанной (агент зовёт сам только после того, как показал разбор)

tg_index_stats

Что лежит в индексе и за какой период

tg_links

Что шерили: топ @упоминаний, t.me, инвайтов, URL и источников пересылок

tg_topics

Структура индекса: чаты, а по chat_id — его темы со счётчиками

tg_digests

Готовые выжимки новых сообщений (создаёт digest.py)

tg_entities

Извлечённые сущности (задачи, решения, обещания, вопросы): project, kind, status, owner, due_ts; фильтры pack/kind/status/project/chat_id и query — подстрока в title/body

tg_entity

Одна сущность по id: все поля и история событий — какое поле менялось, когда и с каким доказательством из сообщения

tg_overdue

Открытые сущности со сроком: просроченные (days_ahead=0) или на горизонте N дней, по возрастанию due_ts

Отправки нет вообще: в server.py не существует инструмента, который что-то пишет в Telegram. Пишущих модуля два, оба вне MCP-сервера: notify.py (сообщения себе, за гейтом TG_ALLOW_SEND=1) и folders.py (состав папок — add/remove чатов; контент не пишет). tg_sql (произвольный read-only SQL) скрыт по умолчанию, включается TG_EXPOSE_SQL=1.


Диагностика

python3 doctor.py          # офлайн: библиотеки, креды, сессия, БД, FTS5, self-test маскировки
python3 doctor.py --live   # + реальное подключение к Telegram

Гоняй перед первым sync.py и после любых «почему не работает».

Установка

python3 -m venv ~/.venvs/tg-agent
~/.venvs/tg-agent/bin/pip install -r requirements.txt   # telethon, mcp>=2, cryptg, qrcode

api_id / api_hash: https://my.telegram.org/apps (бесплатно).

Больше ничего руками делать не надо: login.py при первом запуске сам спросит ключи и запомнит их в .env (файл в .gitignore). Хочешь заранее — скопируй .env.example в .env и заполни: все скрипты (sync / login / live / watch / server / ui / digest / doctor) читают .env сами, а переменные окружения, если заданы, имеют приоритет. Кодировка файла не важна: UTF-8, UTF-8 с BOM (notepad) и UTF-16 (Out-File в Windows PowerShell) читаются одинаково.

Первый логин: один скан QR

python3 login.py

В терминале появится QR: Telegram → Настройки → Устройства → Подключить устройство — наведи камеру. Телефон и код вводить не нужно (код из Telegram приходит только тебе, обойти это нельзя — а QR убирает ввод целиком). Если на аккаунте включена двухэтапная проверка — спросит облачный пароль (до трёх попыток, ввод не отображается — следи за раскладкой); повторный запуск не требует нового скана: QR уже принят, спросит только пароль. Сессия ляжет в tg_agent_session.session и переиспользуется навсегда. Альтернативы: login.py --phone (телефон+код), login.py --export (напечатает TELEGRAM_SESSION_STRING для контейнеров).

Переменная TG_ENV_FILE управляет тем, что читается вместо .env (пустое значение — не читать .env вовсе; так тесты и CI остаются герметичными).

Синхронизация

python3 sync.py --dry-run                    # что найдётся, ничего не записывая
python3 sync.py --folder 7                   # всё из папки
python3 sync.py --watched                    # только watch-лист
python3 sync.py --chat @mygroup --days 30    # конкретная группа, окно 30 дней

Первый запуск попросит QR уже не должен — логинься заранее login.py; но и sync.py сам спросит телефон + код (или QR), если сессии нет. Сессия — в tg_agent_session.session: это полный доступ к аккаунту, chmod 600, не коммитить.

Каналы с историей в годы

Обычная синхронизация идёт сверху вниз и обрывается на первом известном сообщении — до конца архива так не дойти. Для архива:

python3 sync.py --chat @old_channel --backfill 3000

Бэкфилл идёт снизу вверх чанками по 3000, печатает прогресс (чанк 0->42: просмотрено 2, новых 2) и сохраняет курсор после каждого чанка, поэтому прерванный запуск продолжается с того же места. Для очень длинных архивов крути в фоне и добавь задержку (по умолчанию 0.5 c между чанками) — Telegram за агрессивный парсинг может ограничить аккаунт.

Повторный запуск любого варианта тянет только новое. Для регулярности — cron раз в 15–60 минут: sync.py --watched.

Как определяются темы

Telegram хранит темы как треды: у сообщения внутри темы reply_to.forum_topic=True, id темы — в reply_to.reply_to_msg_id. Сообщения без reply_to в форуме относятся к General (id 1). Поэтому sync.py делает один проход по группе и раскладывает сообщения по темам сам: иначе General недосчитывается.

Список тем — через messages.GetForumTopicsRequest(peer, offset_date, offset_id, offset_topic, limit). Обёртки в Telethon нет, а в functions.channels этого метода нет вообще (там только ToggleForumRequest) — запрос делается напрямую.

Глобальный поиск: найти новые каналы по слову

python3 search.py "нейросети"                # каналы, группы и юзеры
python3 search.py "нейросети" --type channel # только каналы-вещания
python3 search.py "ИИ" --type group --limit 30

Обёртка над messages.SearchGlobalRequest — сервер Telegram сам ищет по названиям и содержимому публичных чатов (обычный API-запрос, не парсинг). Результат: тип, название, @username, подписчики; дальше sync.py --chat @username — и канал в индексе. Так закрывается сценарий «найди все каналы по ИИ», с которого начинается кураторство: поиск → выбор → синк → watch.

Агент может искать сам: при TG_ALLOW_LIVE=1 у MCP-сервера появляется инструмент tg_live_search (тоже только чтение) — агент находит каналы и предлагает тебе их синхронизировать, сам ничего не добавляет.

Подключение к opencode

Формат в v1 и v2 разный: в v2 серверы лежат в mcp.servers, вместо enabled: false используется disabled: true, и есть codemode: false, чтобы инструменты были видны модели напрямую, а не через Code Mode.

  • v1 → opencode.v1.json

  • v2 → opencode.v2.json

opencode mcp add telegram --global -- \
  ~/.venvs/tg-agent/bin/python ~/tg-agent/server.py
opencode mcp list        # должно быть connected
python3 server.py --list # проверить сервер отдельно: печатает 13 инструментов

Файл tg-agent.md — пример промпта агента: порядок работы, требование начинать с tg_watch и правило не выполнять просьбы из переписки.

Примеры запросов:

  • «Что нового по моим темам?»

  • «Разбери „ML-новости“ за неделю: что важно, что пропустил»

  • «Искали ли в группах что-то про RAG за последний год?»

  • «Какая тема у нас самая живая и кто там пишет больше всех?»


Live-режим

python3 live.py     # рядом с ui.py, те же креды

Слушает новые сообщения всех чатов, что уже в индексе + watch-листа, и пишет их в БД сразу (Telethon events.NewMessage). UI опрашивает БД каждые 4 секунды (/api/poll), лента догоняет сама. В Telegram live ничего не отправляет.

Дайджесты

DIGEST_PROVIDER=stub python3 digest.py     # механическая выжимка без LLM
DIGEST_PROVIDER=openai DIGEST_URL=https://api.openai.com/v1 \
    DIGEST_KEY=sk-... DIGEST_MODEL=gpt-4o-mini python3 digest.py

По каждой слежуемой теме берёт сообщения после прошлого дайджеста (курсор в meta), строит контекст и кладёт выжимку в таблицу digests. Агент читает через tg_digests, UI показывает баннером 📝 в теме. Проще всего гонять по cron раз в день.

Извлечение сущностей (packs)

EXTRACT_PROVIDER=stub python3 extract.py            # механический извлекатель (без LLM)
EXTRACT_PROVIDER=stub python3 extract.py --dry-run  # показать операции, ничего не писать
EXTRACT_PROVIDER=stub python3 extract.py --all-indexed  # все чаты индекса, не только watch
python3 extract.py --pack services --until-done     # дожевать хвост за один запуск
python3 extract.py --until-done --workers 6         # темы параллельно (EXTRACT_WORKERS)
EXTRACT_PROVIDER=openai EXTRACT_URL=https://api.openai.com/v1 \
    EXTRACT_KEY=sk-... EXTRACT_MODEL=gpt-4o-mini python3 extract.py --pack construction

Сообщения остаются неизменяемым источником истины; сущности — пересчитываемая проекция: удалил и перегенерил заново, ничего не потеряно. Движок extract.py generic, предметная область описана декларативным пакетом в packs/; пакет задаёт kinds, prompt-шаблон, окно первого прогона (backfill_days) и scope. Пакеты: construction (PM для стройки: decision / task / promise / question) и services (сервисы и триалы: service / trial / promo; ключ дедупа — dedup_hint: домен или слаг сервиса, поэтому «Триал 30 дней на Acme» и «Acme Cloud trial» схлопываются в одну сущность). Дедуп глобальный между чатами: один сервис из разных чатов — одна сущность с несколькими источниками (entity_sources); слияние пишется в entity_events (op='merge'), редирект старого id — в entity_aliases, tg_entity резолвит его автоматически. services прицелен в темы watch-листа с проектом «сервисы» (watch.py add --project сервисы) — не шумит в код-чатах; scope меняется в packs/services.py. --until-done крутит раунды (по 200 сообщений на тему), пока хвост истории не кончится; --workers N (или EXTRACT_WORKERS) дожёвывает темы параллельно — разовая накачка базы на десятки тысяч сообщений вместо часов занимает минуты-десятки минут, а каждый скоуп коммитится независимо. Один огромный скоуп делится на окна msg_id и жуётся тем же пулом параллельно (EXTRACT_WINDOW_MIN, дефолт 2000 сообщений; --windows N): план и sub-курсоры окон живут в meta, главный курсор продвигается одной транзакцией, когда все окна дожёваны, — прерванный прогон продолжается с того же места.

По умолчанию область прогона — watch-лист; --all-indexed расширяет на все пары (chat, topic) индекса — для массовой обработки «кучи групп», без курирования. По каждой паре (пакет, тема) курсор extract:<chat>:<topic>:<pack> в meta; за один прогон не больше 200 сообщений, остаток подберёт следующий. Провайдер отвечает JSON-операциями new/update/close/reopen; каждая обязана нести evidence — цитаты сообщений. Цитата проверяется как подстрока текста сохранённого сообщения (плюс транскрипты медиа); операция без валидного evidence отклоняется. Операции применяются одной транзакцией (entities, entity_events, extraction_runs), сбой провайдера курсор не двигает. Валидных операций больше 50 за прогон: применяются первые по позиции evidence, остаток фиксируется как ops_overflow (status=partial), а курсор остаётся до первой отброшенной — следующий прогон дочитает без потерь. Батч, отклонённый контент-фильтром провайдера (data_inspection_failed), бисектится до сообщения-триггера; триггер пропускается с фиксацией run status='filtered', остальные сообщения обрабатываются штатно. Сетевой сбой ретраит батч до 3 раз (интервал EXTRACT_NET_RETRY_DELAY, по умолчанию 15с); сломанная область завершает прогон ненулевым кодом — незавершённый хвост виден сразу, курсоры позволяют перезапустить с места.

Теги проектов: python3 watch.py add ... --project "Объекд" или python3 watch.py project --chat @mygroup --topic 789 --project "Объекд". Проект у строки watch — сущности наследуют его от темы.

Автономный конвейер

python3 pipeline.py                  # sync -> media -> extract -> fuzzy -> export -> digest -> monitor
python3 pipeline.py --pack services  # один пакет извлечения
python3 pipeline.py --skip sync      # офлайн-шаги, без Telegram (--skip media/fuzzy/export тоже можно)
python3 pipeline.py --days-ahead 7 --send
python3 pipeline.py --loop 30 --send # демон: полный цикл каждые 30 минут

Одна команда для автономного режима: шаги независимы, сбой одного не останавливает остальные (упавший шаг — в отчёте и в exit code 1); extract дожёвывает хвост сообщений до конца. После извлечения fuzzy сливает дубли сущностей по нормализованному title (FUZZY_MAX_MERGES, дефолт 500), а export дописывает JSONL-фид для внешних потребителей (по умолчанию export.jsonl рядом с базой, путь — TG_EXPORT_PATH; формат строки {"entity", "events", "exported_ts"}, курсор в integrations). Регулярность — --loop N (демон, cron не нужен) или cron; live.py остаётся отдельным демоном для мгновенной подхватки новых сообщений.

Автономка (Windows)

На машине настроены три компонента:

Компонент

Роль

Запуск

pipeline.py --loop 30

полный цикл каждые 30 минут (sync → media → extract → fuzzy → export → digest → monitor)

при входе в систему: tg-agent.cmd в папке Startup

live.py

мгновенная подхватка новых сообщений в индекс

тот же .cmd

задача tg-agent-morning (09:00)

утренняя сводка в Saved Messages (pipeline.py --skip sync --skip media --send)

Task Scheduler

Каждый демон получает собственную копию Telethon-сессии (tg_agent_session_cli, tg_agent_session_live, tg_agent_session_morning): два клиента на одном session-файле конфликтуют через его SQLite (database is locked). Копии делаются обычным копированием файла авторизованной сессии — auth-ключ один, локальные базы разные.

Двойной старт блокирует proclock (байтовая блокировка <db>.pipeline.lock / <db>.live.lock; при смерти процесса ОС снимает её сама — протухших файлов не остаётся). Логи дописываются в pipeline.log / live.log / morning.log (в gitignore). Утренней сводке sync не нужен — loop-демон и так держит данные свежими, а Telegram-API не конкурирует. Остановка: снять задачи Get-CimInstance Win32_Process -Filter "Name='python.exe'" и убить PID'ы демонов; для отключения автозапуска — удалить tg-agent.cmd из Startup и schtasks /delete /tn tg-agent-morning /f.

Мониторинг и уведомления

python3 monitor.py                       # dry-run: сводка по проектам и дедлайны
python3 monitor.py --days-ahead 7        # добавить задачи на неделю вперёд
python3 monitor.py --project "Объекд"    # один проект
python3 monitor.py --news-hours 24       # окно блока «новое» (по умолчанию сутки)
python3 monitor.py --send                # отправить сводку в Saved Messages

Считает по entities: просроченные — открытые сущности со сроком, чей due_ts в прошлом; --days-ahead N дополнительно достаёт задачи до N дней включительно. Сводка начинается с блока «новое за Nч» — свежесозданные сущности всех пакетов и кросс-чатные повторы (события merge/dedup — сервис снова всплыл в другом чате) со временем появления: утренний pipeline.py --loop 1440 --send превращается в ежедневную сводку о триалах. --send — это notify.py:

python3 notify.py "текст"                # dry-run: показать, что ушло бы
TG_ALLOW_SEND=1 python3 notify.py --send "текст"
echo "текст" | TG_ALLOW_SEND=1 python3 notify.py --send   # из stdin, построчно

notify.py — единственный модуль репозитория, который умеет писать в Telegram (в свои Saved Messages). Реальная отправка только при TG_ALLOW_SEND=1, по умолчанию везде dry-run; TG_NOTIFY_CAP (по умолчанию 10) ограничивает число сообщений за вызов. MCP-сервер notify.py не импортирует — агент отправки по-прежнему не видит.

Медиа-транскрипция

MEDIA_PROVIDER=stub python3 media.py                    # заглушка без сети
MEDIA_PROVIDER=openai python3 media.py --limit 50       # скачивание + транскрипция

sync.py и live.py ставят голосовые и документы в очередь (таблица media_assets, status=pending); воркер рефетчит сообщение из Telegram по id (файловая ссылка живёт только на свежем объекте), классифицирует аудио по атрибутам (DocumentAttributeVoice/Audio, mime audio/* как фолбэк) и до скачивания отсекает не-аудио, файлы крупнее 10MB и длиннее 5 минут статусом skipped; удалённые сообщения — missing, сбой провайдера — failed. openai-провайдер — DashScope qwen3-asr-flash через chat/completions: аудио инлайнится base64 data-URL, asr_options идёт верхним уровнем (MEDIA_URL/MEDIA_KEY/MEDIA_MODEL/MEDIA_TIMEOUT; ключ — список через запятую с ротацией на 401/403). MEDIA_LIMIT (дефолт 25) ограничивает ассеты за прогон, --limit/--kinds — то же в CLI. Готовые транскрипции extract.py подмешивает в промпт извлечения (и evidence-проверка учитывает их текст). pipeline.py гоняет шаг автоматически после sync.

Экспорт

python3 export.py                                  # JSONL рядом с базой
python3 export.py --system file --out /tmp/export.jsonl
python3 export.py --system notion                  # структурированная ошибка: нет API-доступа

Выгружает сущности и их события во внешние системы. Путь file-провайдера: --out, иначе TG_EXPORT_PATH, иначе export.jsonl рядом с базой. Курсор каждого провайдера живёт в таблице integrations (entity_id, system): выгружаются только сущности с событиями свежее synced_ts; сущности без событий не выгружаются никогда. 101 и notion не реализованы — отвечают словарём-ошибкой, исключений не бросают (у 101 есть публичный REST API: JWT, swagger на app.101-group.ru — ждёт кредов).

Безопасность

  • Отправить сообщение агент не может — таких инструментов нет. Пишущие модули живут вне MCP-сервера: notify.py (сообщения в Saved Messages, гейт TG_ALLOW_SEND=1) и folders.py (состав папок — только membership, без контента; правка = явный запуск команды).

  • Сообщения — неизменяемый источник истины. Извлечение сущностей — пересчитываемая проекция: каждая сущность обязана цитатами из сохранённых сообщений (проверка на подстроку), без валидного evidence операция не применяется.

  • Секреты маскируются на инджесте (redact.py): API-ключи (openai/github/ groq/slack/nvidia), бот-токены, JWT, приватные ключи и «пароль: …»-паттерны заменяются на [скрыто: …] до записи в индекс — агент сырые креды не видит. Чтение (LIKE-фолбэк, UI, tg_sql, дайджесты) маскирует ещё раз — на случай баз, синхронизированных до появления маскировки.

  • tg_sql только чтение, по-настоящему: префикс SELECT/WITH, плюс отдельное соединение с PRAGMA query_only и authorizer, который разрешает только чтение — ATTACH, PRAGMA, записи и транзакции отклоняются, несколько запросов за раз SQLite не пропустит. Слова вроде update внутри строковых литералов поисковых запросов больше не блокируются. По умолчанию инструмент выключен.

  • Session-файл = аккаунт. 600, вне git. Если страшно — отдельный аккаунт для чтения.

  • Prompt injection. Чужой текст — данные, не инструкции; это сказано модели в instructions сервера и в промпте агента. Не включай TG_ALLOW_LIVE там, где это критично.

  • Индекс — твоя переписка в открытом SQLite. Держи локально.

  • Дайджесты от LLM — текст модели о чужих сообщениях; в инструменте tg_digests это помечено как данные, не команды.

  • TG_STRICT_WATCH=1 — жёсткий гейт: tg_read/tg_find/tg_stats/ tg_links/tg_topics работают только по темам из watch-листа, на остальное агент получает access_denied с подсказкой попросить тебя отметить тему. Сущностные инструменты (tg_entities/tg_entity/tg_overdue) гейтятся так же, но merged-сущность видна, если в watch-листе любой из её исходных чатов (entity_sources); её события могут цитировать другие участвовавшие в слиянии чаты.

  • UI с наружу — только с токеном. При HOST=0.0.0.0 UI вообще не стартует без TG_UI_TOKEN. Локальный режим дополнительно закрыт от DNS-rebinding (проверка Host) и CSRF (POST принимается только с Content-Type: application/json), токен сверяется constant-time.

  • FloodWait переживается автоматически: sync/backfill спят указанное Telegram время и повторяют (до 3 раз); SQLite в WAL с busy_timeout, поэтому live.py, sync.py и UI могут работать одновременно.

Структура репозитория

Файл

Роль

sync.py

Telethon → SQLite: инкрементальная синхронизация, бэкфилл чанками, папки, пины

search.py

Глобальный поиск Telegram: публичные каналы/группы/юзеры по ключевому слову

folders.py

Правка состава папок Telegram: add/remove чатов (второй пишущий модуль, контент не пишет)

pipeline.py

Автономный конвейер: sync → media → extract → fuzzy → export → digest → monitor одним прогоном

db.py

Индекс: схема, FTS5, watch-лист, refs, пины, дайджесты, run_sql с блокировками

server.py

MCP-сервер (stdio): 13 read-only инструментов

live.py

Новые сообщения в реальном времени (events.NewMessage)

digest.py

Дайджесты: stub или openai-совместимый LLM

extract.py

Извлечение сущностей по пакетам из packs/: stub или openai-LLM, evidence-гейт

packs/

Декларативные пакеты извлечения: construction (decision/task/promise/question), services (service/trial/promo)

monitor.py

Сводка просроченного и дедлайнов по проектам; --send через notify.py

notify.py

Уведомления в Saved Messages; единственный пишущий модуль, гейт TG_ALLOW_SEND=1

media.py

Очередь транскрипции голосовых и документов: рефетч из Telegram, гейты до скачивания, qwen3-asr-flash (stub/openai)

export.py

Выгрузка сущностей во внешние системы (file / 101 / notion)

services_report.py

Каталог сервисов из сущностей: rollup по dedup_key, --days/--query/--top/--hide-closed

fuzzy_dedup.py

Maintenance-проход: слияние дублей сущностей по нормализованному title (richer-keeper, cap, идемпотентный)

proclock.py

Lockfile одиночного инстанса для демонов (pipeline --loop, live.py)

ui.py + ui.html

Веб-визуал «как в TG Desktop» (кураторство watch-листа)

watch.py

То же в терминале; --project и подкоманда project — тег проекта у темы

login.py

Логин QR-кодом (или телефон+код), --export — строка сессии

env.py

Чтение .env всеми скриптами (env-переменные приоритетнее)

redact.py / sanitize.py

Маскировка секретов / чистка bidi-инъекций (sanitize — из chigwell, Apache-2.0)

doctor.py

Диагностика окружения

seed_demo.py

Демо-база без Telegram: python3 seed_demo.py demo.sqlite

test_offline.py

475 офлайн-проверок (без аккаунта Telegram), гоняются в CI

opencode.v1.json / opencode.v2.json

Готовые конфиги подключения к opencode

tg-agent.md

Памятка-промпт для агента

Лицензия — MIT (LICENSE); атрибуция заимствований — там же и в шапках файлов. *.sqlite, *.session* и .env в .gitignore — в репозиторий не попадают.

Альтернативы: готовые MCP

Если не хочется своего кода:

Проект

Темы

Комментарий

chigwell/telegram-mcp (~1.8k★)

✅ list_topics, get_history(topic_id=...)

Самый полный: мультиаккаунт, Docker, TELEGRAM_EXPOSED_TOOLS=read-only, транскрипция голосовых. Минус — 100+ инструментов в контексте и нет watch-листа

m0n0x41d/telegram-mcp

❌

7 инструментов, read_messages_since для мониторинга, uvx

DmitryKhali/telegram-mcp

❌

«суммаризируй чат», отправка только через confirm=true

⚠️ Не ставь pip install telegram-mcp / uvx telegram-mcp — в PyPI это другой проект; передав ему API_HASH и session-string, отдашь доступ к аккаунту постороннему коду. Только из git по явному URL.

Про клиентов

Под капотом всех MCP — обычные user-клиенты MTProto: Telethon (Python, здесь), GramJS (JS/TS), TDLib (C++, удобнее для очень больших дампов), opentele (конвертация tdata Telegram Desktop в Telethon-сессию). Разница только в языке: данные MTProto отдаёт одни и те же.


Майнинг готовых MCP: что взято

Склонированы и прочитаны четыре репы; взято только то, что ложится в нашу архитектуру без чужих рантайм-зависимостей:

Откуда

Что

Статус

chigwell/telegram-mcp

sanitize.py целиком (prompt-injection защита выдачи)

✅ взято как есть, применяется ко всей выдаче сервера и UI

chigwell/telegram-mcp

паттерн пинов (get_messages(filter=InputMessagesFilterPinned) + фолбэк)

✅ портирован в sync.fetch_pins, таблица pins, баннер в UI и pinned в tg_watch

0xMRK95/telegram-mcp-server

regexp extract_refs (@упоминания, t.me, инвайты, форварды)

✅ портирован: таблица refs на инджесте + инструмент tg_links

chigwell/telegram-mcp

transcription.py (голосовые: groq/openai/whisper + SQLite-кэш)

⛔ не берём: намертво привязан к их runtime.py; если нужны голосовые — проще вторым MCP поднять их сервер с TELEGRAM_EXPOSED_TOOLS=read-only

chigwell/telegram-mcp

TELEGRAM_EXPOSED_TOOLS (read-only поверхность)

💡 идея учтена: у нас инструментов отправки нет вообще, tg_sql скрыт за TG_EXPOSE_SQL=1

m0n0x41d/telegram-mcp

scan-state с курсорами и TRUNCATED-семантикой

≈ не берём: наши курсоры в meta делают то же; их doctor (диагностика сессии) — кандидат на потом

m0n0x41d/telegram-mcp

auth-визард с 2FA и лечением протухшей сессии

кандидат на потом (у нас пока client.start())

andvseti/telegram-mcp-server

get_recent_media/download_media в каталог

кандидат для медиа-превью (слой 4)

chigwell/telegram-mcp

Docker + HTTP-транспорт (общий демон на несколько клиентов)

кандидат, если визуал и opencode будут на разных машинах

Проверено / не проверено

python3 test_offline.py — 475 проверок, все зелёные (работают и на Linux, и на Windows). Реально исполняются:

  • sync.run() на подставном клиенте: определение групп/каналов, --pattern, --chat @канал, --folder, --watched, --dry-run, раскладка по темам (включая General), инкрементальность по курсорам

  • sync.backfill(): досасывание истории чанками снизу вверх, идемпотентность, сохранение курсора, resume прерванного прогона (meta:backfill)

  • id-формы Telethon: entity.id в marked-форме (-100…) на границе синка — live.py и watch.py видят одни и те же id

  • db.py: темы, история, окно по времени, FTS5-поиск (включая фильтр по чату), статистика, watch-лист (watch_new / watch_mark_read / удаление), run_sql и его гейты (query_only + authorizer: DELETE, PRAGMA, SELECT; DROP, UPDATE, ATTACH — блокируются; слово в LIKE-литерале — нет)

  • маскировка секретов на инджесте: токен не доезжает до messages.text, raw_json и refs; LIKE-фолбэк и run_sql выдают замаскированное

  • FTS-триггёр удаления: DELETE FROM messages чистит индекс (раньше падал)

  • настоящий server.py как подпроцесс по MCP stdio: initialize → tools/list (13 инструментов, tg_sql скрыт) → tools/call по каждому, включая сценарий «прочитал → отметил → непрочитанного нет»; tg_new честно держит truncated при лимите; отдельный прогон с TG_ALLOW_LIVE=1 — live-инструменты регистрируются, без кредов отвечают JSON-ошибкой, а не зависают

  • папки: GetDialogFiltersRequest на фейках → состав папок в индексе

  • топ по реакциям (stats.top_reactions)

  • live.handle_message: вставка + идемпотентность + refs; collect_ids подхватывает чаты, добавленные после старта

  • настоящий ui.py как подпроцесс: /api/poll отдаёт сообщения по курсору; Host-гейт (403 на чужой Host), POST без application/json → 415, отказ поднимать 0.0.0.0 без токена; /api/messages, /api/poll, /api/state выдают замаскированный текст

  • digest.run: stub-выжимки создаются, повторно не дублируются (курсор), отдаются через MCP tg_digests; сбой LLM-провайдера не двигает курсор

  • гейты: FloodWaitError на фейках (sync дожидается и доделывает; посреди прохода счётчик new не занижается), busy_timeout, before_id/date_to, budget_chars+truncated, tg_topics, live-тема с заголовком, UI-токен (401 без / 200 с)

  • TG_STRICT_WATCH=1: второй сервер-подпроцесс — чужой чат отклонён, слежуемый читается, tg_find/tg_topics вне watch слепы

  • doctor.run_checks(): формат чеков, FTS5 и redact self-test

  • бюджет контекста дайджеста: build_context вписывается в DIGEST_MAX_CHARS и честно помечает обрезку

  • extract.run: курсор по (пакет, тема), окно backfill_days на первом прогоне, лимит 200 сообщений; операции new/update/close/reopen одной транзакцией; evidence-гейт: цитата вне текста сообщения отклоняется, сбой провайдера курсор не двигает; overflow: операции сверх 50 фиксируются в ops_overflow, курсор остаётся до первой отброшенной, следующий прогон дочитывает; --all-indexed покрывает все (chat, topic) индекса; дедуп dedup_hint сильнее title; отдаётся через MCP tg_entities/tg_entity/ tg_overdue

  • monitor.collect: просрочка и горизонт --days-ahead, фильтр по проекту; notify: dry-run по умолчанию, реальная отправка только за TG_ALLOW_SEND=1, cap TG_NOTIFY_CAP; server.py его не импортирует

  • media: sync/live ставят voice/document в очередь, stub-транскрипт попадает в промпт извлечения; openai без байтов — NotImplementedError

  • export.run: file-провайдер пишет JSONL, курсор в integrations (повторный прогон дубликатов не даёт; краш посреди прогона — закоммичено до сущности, повтор выгружает только остаток); 101/notion — словарь-ошибка без исключения

  • topic_id канонически NOT NULL (=1 General): вставка без темы и миграция старых баз с NULL, запросы идут по индексу ix_msg_chat_topic (EXPLAIN PLAN проверяется), watch/history/search/stats видят то же

  • server._db() кэширует соединение на поток — схема/миграции не гоняются на каждый tool-call

  • tg_live_* + tg_live_search: живые запросы регистрируются при TG_ALLOW_LIVE=1; без кредов — JSON-ошибка, не зависание

  • search.search_global: каналы/группы/юзеры с marked-id и kind, фильтр типа уходит флагом в запрос (SearchGlobalRequest на фейк-клиенте)

  • пакет services: триал с due_text через dedup_hint-схему, другая формулировка с тем же hint — дубликат; stub создаёт service/trial/promo

  • pipeline.run: sync→extract→digest→monitor на фейк-клиенте (10 сообщений, 2 дайджеста), второй прогон инкрементален, --skip sync — офлайн-шаги

  • проект у тем watch (watch.py --project, подкоманда project) наследуется сущностями и фильтруется в tg_entities/tg_overdue/monitor

Не проверено: реальные вызовы к Telegram — здесь нет аккаунта. Сигнатура messages.GetForumTopicsRequest снята с установленного Telethon 1.45, но живой ответ API глазами не видел; iter_dialogs(folder=...) проверен только по сигнатуре. Первый sync.py --dry-run --folder <id> — это и есть проверка.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A read-only MCP server that lets AI agents read personal Telegram chats from an allowlist of folders, with no send/edit/delete capability.
    28 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that connects to Telegram as your real user account and exposes read-only tools to read and search messages, list chats and folders, inspect group info, and download media.
    16 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Read-only MCP server for Telegram chats and channels that provides digest summaries, message search, and action items.
    13 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A read-only Telegram MCP server that only exposes a user-defined allowlist of chats, providing tools to list chats, fetch messages, search, and get context while stripping untrusted text and with no write capabilities.
    MIT