npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aimuzov/thinks-mcp

v0.3.0

Published

MCP server that writes, replies and rephrases in your own voice, learned from a Telegram export. Hands the calling model a measured style profile and your real messages — no API keys, no generation of its own.

Readme

thinks-mcp

English version

MCP-сервер, который пишет, отвечает и переформулирует текст твоим голосом — выученным по выгрузке Telegram и истории твоих репозиториев.

Сервер ничего не генерирует сам и не ходит ни в какие API. Он отдаёт вызывающей модели бриф: измеренный стиль-профиль, настоящие твои сообщения, подобранные под конкретный запрос, числовые ограничения и формат ответа. Пишет по этому брифу сама модель — Claude Code, Claude Desktop, что угодно.

Сервер говорит по-русски: описания инструментов, брифы, профиль и CLI — всё на русском, а стилевые пробы (канцелярит, стоп-слова, плейсхолдеры при чистке) рассчитаны на русскоязычный архив. Английский понимают поиск и кодовые регистры, где встречаются оба языка.

Как это работает

Две фазы, разделённые файлом SQLite.

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

Выдача (MCP-сервер): запрос инструмента → BM25-поиск по архиву → бриф.

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

Установка

Нужен Node 24 или новее: корпус и поиск построены на node:sqlite с FTS5, который стабилен начиная с этой версии.

Через mise

mise use -g npm:@aimuzov/thinks-mcp

Локально, из исходников

pnpm i && pnpm build && npm pack && npm i -g ./aimuzov-thinks-mcp-*.tgz

Учти: npm i -g ставит бинарник в ту версию Node, которая активна в этот момент. Если mise переключит версию, thinks-mcp пропадёт из PATH — поэтому в конфиге MCP-хоста лучше запускать через mise exec, а не полагаться на голое имя команды.

Сборка чат-корпуса

Выгрузи архив в Telegram: Settings → Advanced → Export Telegram data, формат JSON, снять галочки со всех медиа (нужен только текст). Затем:

thinks-mcp build ~/Downloads/Telegram\ Desktop/DataExport/result.json

Порядок величины: несколько сотен тысяч сообщений собираются примерно за 15 секунд. Выгрузка парсится целиком в память, и пик примерно вшестеро больше файла — на экспорте в 400 МБ это около 2.5 ГБ. Если Node не хватит кучи, подними её: NODE_OPTIONS=--max-old-space-size=8192 thinks-mcp build ...

Где что лежит и собрано ли:

thinks-mcp where
thinks-mcp profile

Сборка корпуса кода

Второй, независимый корпус — комментарии из твоих репозиториев. Нужен для того, для чего чат-корпус не годится: писать комментарии в коде.

THINKS_CODE_EMAILS="me@personal,me@work" thinks-mcp code ~/Projects/*/ ~/work/repo

Авторство определяется через git blame: блок попадает в корпус, только если больше половины его строк написаны с указанных адресов. Чужие комментарии, строки-разделители, закомментированный код и директивы инструментов (eslint-disable, shellcheck source=) отсеиваются.

Даёт два регистра: code — инлайн, jsdoc — докблоки. Замеряются они раздельно, потому что это разные жанры: инлайн обычно однострочный, а докблок начинается с итоговой фразы и продолжается.

Повторная сборка переиспользует результаты git blame для файлов, которые не менялись — ключ по blob-хешу. На десятке репозиториев это разница между полуминутой и парой секунд.

Два корпуса живут в одной базе и не мешают друг другу: build пересобирает только чат-регистры, code — только кодовые.

Корпус хранится в ~/.config/thinks-mcp/style.db — рядом с настройками, а не рядом с кодом. Иначе при обновлении пакета он потерялся бы вместе со старой версией.

Подключение

Скопируй .mcp.json.example в .mcp.json:

{
  "mcpServers": {
    "thinks": {
      "command": "/opt/homebrew/bin/mise",
      "args": ["exec", "npm:@aimuzov/thinks-mcp", "--", "thinks-mcp"]
    }
  }
}

Если ставил из исходников через npm i -g — запускай через ту версию Node, в которую лёг бинарник:

{
  "mcpServers": {
    "thinks": {
      "command": "/opt/homebrew/bin/mise",
      "args": ["x", "node@24", "--", "thinks-mcp"]
    }
  }
}

Сервер стартует и без собранного корпуса и объясняет, что делать, — машина, где архив ещё не импортирован, получит рабочий сервер, а не упавший.

Инструменты

| Инструмент | Что делает | |---|---| | write_as_me | бриф для текста с нуля по заданию | | reply_as_me | бриф для ответа на входящее сообщение | | rephrase_as_me | бриф для переписывания готового текста | | check_as_me | детерминированная оценка 0–100 и список отклонений | | find_my_messages | поиск по архиву: «как я обычно отказываю» |

У каждого есть параметр register: dm — личка, group — групповой чат, longform — длинный авторский текст, code — инлайн-комментарий, jsdoc — докблок. Стиль в них разный, поэтому профиль, ограничения и поиск разведены по регистрам, а check_as_me для кода проверяет своё: ширину строки, маркеры, воду вместо факта, типы в фигурных скобках.

Для комментариев в коде брать только code и jsdoc. Чат-регистры замерены по переписке — короткие реплики, разговорные формы, эмодзи — и в коде дают чужой голос.

У write_as_me и find_my_messages есть ещё lang (ru/en) — осмысленно для кода, где используются оба языка.

Остальные параметры:

  • examples (4–40, по умолчанию 18) у трёх инструментов-брифов — сколько примеров из архива подложить в бриф;
  • length (short/normal/long) у write_as_me — относительно обычного для регистра объёма;
  • hint у reply_as_me — что должно быть сказано в ответе;
  • limit, yearFrom и matchIncoming у find_my_messages; последний ищет по сообщениям собеседников, а не по твоим.

У check_as_me есть необязательный параметр code — строки, над которыми стоит комментарий. С ним проверка ловит и пересказ кода. Ограничение честное: она сравнивает слова, поэтому русский комментарий над английским кодом оценить не может и промолчит.

Ресурсы: style://profile и style://profile/{register} — профиль как markdown. Промпты: as-me, reply-as-me и comment-as-me.

Рабочий цикл, который задают промпты: получить бриф → написать → check_as_me → переписать по замечаниям, пока оценка не станет высокой.

Чего стоит пример ответа

reply_as_me строит примеры из пар, а пары бывают двух сортов.

Сообщение с reply_to_message_id — это факт: автор сам выбрал, на что отвечает. Всё остальное выведено из порядка сообщений, то есть «что написали перед этим». Такая догадка ломается там, где переписка обычнее всего: тебе пишут «Извини», ответ совсем про другое, и пара учит модель отвечать не по теме.

Поэтому пары с явной цитатой ранжируются выше выведенных, а выведенная пара, на которую автор потратил больше получаса, — ещё ниже. У каждого примера подписано, какой он.

В некоторых регистрах пар почти нет: выгрузка Telegram для супергруппы почти не содержит сообщений собеседников. Там бриф прямо это говорит, а не выдаёт подходящие по теме ходы за ответы.

Свежесть

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

  • профиль и ограничения считаются по последним годам (THINKS_RECENT_YEARS), а цифры за всё время показываются справочно;
  • выдача поиска взвешивается по году — свежий пример при прочих равных выигрывает. Вес подобран так, чтобы десятилетие возраста стоило примерно треть типичного разброса BM25 в выдаче: свежесть влияет, но нерелевантное новое не обгоняет релевантное старое.

Знаки препинания

Кавычки-ёлочки, длинное и короткое тире, лапки — типографика печатной книги, и модель ставит её по умолчанию. Ставит ли её владелец архива, решает замер: доля сообщений с этим знаком считается отдельно для чата и для каждого кодового жанра. Ниже 2% знак попадает в профиль как чужой, check_as_me за него штрафует, а бриф просит вместо тире дефис и простые кавычки. Порог здесь свой, не общий для антипаттернов: дефис в 1% сообщений уже выдаёт текст, а вот канцелярское слово при такой доле — ещё нет.

Двойной дефис измеряется, но никогда не штрафуется. Это не чужой знак, а ASCII-замена тире, и она разная по регистрам: в переписке её нет, в комментариях она может быть нормой. Там, где её доля от 1%, бриф прямо просит писать тире двумя дефисами.

Отдельная беда — комментарии, написанные с ассистентом. git blame считает их твоими, а знаки в них его: в моём корпусе за 2026 год доля ёлочек в докблоках подскочила с нуля до 6%. Год, начиная с которого комментарии писались уже не вручную, задаётся в THINKS_CODE_HANDWRITTEN_UNTIL. Всё, что позже, остаётся в индексе и в поиске, но в замер знаков не идёт. Если после отсечки в жанре осталось меньше 200 строк, знаки для него не считаются вовсе: уверенный ноль на двадцати строках хуже, чем честное отсутствие цифры.

Так же замеряются многоточие одним знаком … и стрелка →.

Некоторые знаки порогом не решить. Автозамена или раскладка могли годами ставить — в рукописные комментарии, и архив считает это привычкой.

THINKS_NEVER_MARKS -- единственное правило, которое объявляется, а не замеряется: —:--,…:...,→:=>,«:",»:" перечисляет знаки и то, что пишешь вместо них. Объявленный знак штрафуется во всех регистрах при любой замеренной доле, замечание цитирует его с соседними словами и называет замену, а бриф просит писать замену.

Приватность

Это архив личной переписки, поэтому:

  • выгрузка и собранный индекс не коммитятся никогда — каталог с данными лежит вообще вне репозитория;
  • телефоны, почта и номера карт вырезаются по разметке Telegram — выгрузка гарантирует, что разбиение на сущности покрывает текст сообщения целиком, — плюс страховочные регулярки для того, что Telegram не разметил;
  • чаты, отправители и репозитории хранятся под псевдонимами, настоящие имена в базу не попадают;
  • фамилии вырезаются из текста сообщений, имена — нет: имя никого не идентифицирует, а без них примеры выглядели бы как документ с вымарками;
  • из стиль-профиля имена собственные исключаются отдельно.

Исключить чаты целиком: THINKS_CHAT_STOPLIST="Чат один,Чат два".

Команды

thinks-mcp build <dump.json>    # собрать чат-корпус
thinks-mcp code <репозитории>   # собрать корпус комментариев
thinks-mcp profile jsdoc        # профиль по регистру
thinks-mcp where                # где лежит индекс
thinks-mcp holdout --answers    # слепая проверка качества
thinks-mcp serve                # то же, что без аргументов
thinks-mcp --help

В самом репозитории:

mise run check           # типы, форматирование, тесты
pnpm test
pnpm build

holdout — слепая проверка качества: при сборке 20 реальных пар «входящее → ответ» откладываются и не попадают в индекс. Сначала смотришь только входящие, отвечаешь через reply_as_me, потом сверяешь с тем, что было отвечено на самом деле.

Переменные окружения

| Переменная | По умолчанию | Зачем | |---|---|---| | THINKS_DATA_DIR | $XDG_CONFIG_HOME/thinks-mcp или ~/.config/thinks-mcp | каталог с индексом | | THINKS_DUMP | <data-dir>/dump.json | выгрузка, если не передана аргументом | | THINKS_DB | <data-dir>/style.db | путь к файлу индекса | | THINKS_OWNER_ID | автоопределение | если автоопределение ошиблось | | THINKS_CHAT_STOPLIST | пусто | чаты через запятую, которые не индексируются | | THINKS_CODE_EMAILS | git config --global user.email | git-адреса автора через запятую | | THINKS_RECENT_YEARS | 3 | окно «как я пишу сейчас» для профиля | | THINKS_CODE_HANDWRITTEN_UNTIL | нет | год, с которого комментарии писались не вручную: замер знаков их не берёт | | THINKS_NEVER_MARKS | пусто | знаки, которые не набираешь, и замена, например —:--,…:...,→:=>,«:",»:" | | THINKS_BURST_WINDOW | 90 | окно склейки сообщений в ход, секунды | | THINKS_LONGFORM_MIN | 300 | порог longform-регистра, символы | | THINKS_HOLDOUT | 20 | сколько пар отложить на слепую проверку |

Зависимости

@modelcontextprotocol/sdk и zod — и всё. Полнотекстовый поиск — FTS5 из встроенного в Node node:sqlite, стеммеры русского и английского написаны здесь же (src/search/stem.ts), потому что FTS5 токенизирует оба алфавита, но не знает морфологии.

Лицензия

MIT