{"_id":"@aimuzov/thinks-mcp","_rev":"2-15d3899524abfa6a90be8af4d21e8d14","name":"@aimuzov/thinks-mcp","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.1":{"name":"@aimuzov/thinks-mcp","version":"0.1.1","keywords":["mcp","model-context-protocol","mcp-server","claude","claude-code","claude-desktop","cowork","ai","llm","stdio","telegram","writing-style","personal-voice","style-transfer","russian"],"author":{"name":"Aleksey Imuzov"},"license":"MIT","_id":"@aimuzov/thinks-mcp@0.1.1","maintainers":[{"name":"aimuzov","email":"npmjs@aimuzov.online"}],"homepage":"https://github.com/aimuzov/thinks-mcp#readme","bugs":{"url":"https://github.com/aimuzov/thinks-mcp/issues"},"bin":{"thinks-mcp":"build/index.js"},"dist":{"shasum":"79a9ea9eafc18947a632b72d24590a7e430cd7bd","tarball":"https://registry.npmjs.org/@aimuzov/thinks-mcp/-/thinks-mcp-0.1.1.tgz","fileCount":40,"integrity":"sha512-0EPhWKeXK419YDnbtrjtkds/tPHsWuGKNq4lHTW4uWVg9VsYKmFhkLQHbqPS2YCGV6yw61RvBCcyk54GYfTn8w==","signatures":[{"sig":"MEQCIG5kEgKPYQ+PgAVoC7VU7q6ddKAzsP16c3HgitieWHA2AiAT0lFO35s/gaLw2pQBzM2hZW4G1vQJf7WNg/U0ksEczA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":188125},"type":"module","engines":{"node":">=24.0.0"},"gitHead":"8e92a799216a53af0495ee0d5bde3ac88d2133a3","scripts":{"dev":"tsc --watch","test":"vitest run","build":"tsc && chmod +x build/index.js","start":"node build/index.js","where":"node build/index.js where","corpus":"node --max-old-space-size=8192 build/index.js build","format":"prettier --write \"src/**/*.ts\"","profile":"node build/index.js profile","typecheck":"tsc --noEmit && tsc -p tsconfig.test.json","test:watch":"vitest","format:check":"prettier --check \"src/**/*.ts\"","prepublishOnly":"npm run typecheck && npm run format:check && npm run test && npm run build"},"_npmUser":{"name":"aimuzov","email":"npmjs@aimuzov.online"},"repository":{"url":"git+https://github.com/aimuzov/thinks-mcp.git","type":"git"},"_npmVersion":"11.19.0","description":"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.","directories":{},"_nodeVersion":"24.20.0","dependencies":{"zod":"4.4.3","@modelcontextprotocol/sdk":"1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@11.5.1","devDependencies":{"vitest":"4.1.8","prettier":"3.8.4","typescript":"5.9.3","@types/node":"24.13.2"},"_npmOperationalInternal":{"tmp":"tmp/thinks-mcp_0.1.1_1788710298607_0.8543944167509951","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aimuzov/thinks-mcp","version":"0.2.0","description":"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.","keywords":["mcp","model-context-protocol","mcp-server","claude","claude-code","claude-desktop","cowork","ai","llm","stdio","telegram","writing-style","personal-voice","style-transfer","russian"],"homepage":"https://github.com/aimuzov/thinks-mcp#readme","bugs":{"url":"https://github.com/aimuzov/thinks-mcp/issues"},"repository":{"type":"git","url":"git+https://github.com/aimuzov/thinks-mcp.git"},"license":"MIT","author":{"name":"Aleksey Imuzov"},"type":"module","engines":{"node":">=24.0.0"},"bin":{"thinks-mcp":"build/index.js"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc && chmod +x build/index.js","dev":"tsc --watch","start":"node build/index.js","corpus":"node --max-old-space-size=8192 build/index.js build","profile":"node build/index.js profile","where":"node build/index.js where","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit && tsc -p tsconfig.test.json","format":"prettier --write \"src/**/*.ts\"","format:check":"prettier --check \"src/**/*.ts\"","prepublishOnly":"npm run typecheck && npm run format:check && npm run test && npm run build"},"dependencies":{"@modelcontextprotocol/sdk":"1.29.0","zod":"4.4.3"},"devDependencies":{"@types/node":"24.13.2","prettier":"3.8.4","typescript":"5.9.3","vitest":"4.1.8"},"packageManager":"pnpm@11.5.1","gitHead":"c4fb493b0799e10763d504e73ecbe3d5f76f37f8","_id":"@aimuzov/thinks-mcp@0.2.0","_nodeVersion":"24.20.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-VNtf85kX6BKBkUlmolWlxGcmn1rUHtiI9RXfF8NAvxbT7YoNtoA9NVnlT5mjTsHycoJi+40nCLyCN1EGNa3jew==","shasum":"f0274ec4f10dbd08de7e1d91b1aee8d7041ed236","tarball":"https://registry.npmjs.org/@aimuzov/thinks-mcp/-/thinks-mcp-0.2.0.tgz","fileCount":40,"unpackedSize":201093,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCDSz5k5sYT0B4xfUD1k5QR6WeOpbTmc++23Mec6ex82QIgJgqxvKSurtiHyUlbh+dzPHok4KPqtxoIFL2cmzdd8os="}]},"_npmUser":{"name":"aimuzov","email":"npmjs@aimuzov.online"},"directories":{},"maintainers":[{"name":"aimuzov","email":"npmjs@aimuzov.online"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/thinks-mcp_0.2.0_1788868885157_0.8644346363885846"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-06T15:58:18.434Z","modified":"2026-09-08T12:01:25.617Z","0.1.1":"2026-09-06T15:58:18.763Z","0.2.0":"2026-09-08T12:01:25.324Z"},"bugs":{"url":"https://github.com/aimuzov/thinks-mcp/issues"},"author":{"name":"Aleksey Imuzov"},"license":"MIT","homepage":"https://github.com/aimuzov/thinks-mcp#readme","keywords":["mcp","model-context-protocol","mcp-server","claude","claude-code","claude-desktop","cowork","ai","llm","stdio","telegram","writing-style","personal-voice","style-transfer","russian"],"repository":{"type":"git","url":"git+https://github.com/aimuzov/thinks-mcp.git"},"description":"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.","maintainers":[{"name":"aimuzov","email":"npmjs@aimuzov.online"}],"readme":"# thinks-mcp\n\n[English version](README.md)\n\nMCP-сервер, который пишет, отвечает и переформулирует текст твоим голосом —\nвыученным по выгрузке Telegram и истории твоих репозиториев.\n\nСервер ничего не генерирует сам и не ходит ни в какие API. Он отдаёт вызывающей\nмодели **бриф**: измеренный стиль-профиль, настоящие твои сообщения, подобранные\nпод конкретный запрос, числовые ограничения и формат ответа. Пишет по этому\nбрифу сама модель — Claude Code, Claude Desktop, что угодно.\n\nСервер говорит по-русски: описания инструментов, брифы, профиль и CLI — всё на\nрусском, а стилевые пробы (канцелярит, стоп-слова, плейсхолдеры при чистке)\nрассчитаны на русскоязычный архив. Английский понимают поиск и кодовые\nрегистры, где встречаются оба языка.\n\n## Как это работает\n\nДве фазы, разделённые файлом SQLite.\n\n**Сборка** (руками, редко): выгрузка → фильтрация → чистка персональных данных →\nсклейка сообщений в ходы → метрики → индексы.\n\n**Выдача** (MCP-сервер): запрос инструмента → BM25-поиск по архиву → бриф.\n\nЕдиница корпуса — не сообщение, а **ход**: серия сообщений подряд в пределах\n90 секунд. Так и выглядит живая переписка: заметная доля сообщений идёт\nочередью, мысль разбивается на несколько коротких реплик вместо абзаца.\nИндексируй сервер отдельные сообщения — он учил бы обратному.\n\n## Установка\n\nНужен Node 24 или новее: корпус и поиск построены на `node:sqlite` с FTS5,\nкоторый стабилен начиная с этой версии.\n\n### Через mise\n\n```bash\nmise use -g npm:@aimuzov/thinks-mcp\n```\n\n### Локально, из исходников\n\n```bash\npnpm i && pnpm build && npm pack && npm i -g ./aimuzov-thinks-mcp-*.tgz\n```\n\nУчти: `npm i -g` ставит бинарник в ту версию Node, которая активна в этот\nмомент. Если mise переключит версию, `thinks-mcp` пропадёт из `PATH` — поэтому\nв конфиге MCP-хоста лучше запускать через `mise exec`, а не полагаться на голое\nимя команды.\n\n## Сборка чат-корпуса\n\nВыгрузи архив в Telegram: Settings → Advanced → Export Telegram data, формат\nJSON, снять галочки со всех медиа (нужен только текст). Затем:\n\n```bash\nthinks-mcp build ~/Downloads/Telegram\\ Desktop/DataExport/result.json\n```\n\nПорядок величины: несколько сотен тысяч сообщений собираются примерно за 15\nсекунд. Выгрузка парсится целиком в память, и пик примерно вшестеро больше\nфайла — на экспорте в 400 МБ это около 2.5 ГБ. Если Node не хватит кучи,\nподними её: `NODE_OPTIONS=--max-old-space-size=8192 thinks-mcp build ...`\n\nГде что лежит и собрано ли:\n\n```bash\nthinks-mcp where\nthinks-mcp profile\n```\n\n## Сборка корпуса кода\n\nВторой, независимый корпус — комментарии из твоих репозиториев. Нужен для того,\nдля чего чат-корпус не годится: писать комментарии в коде.\n\n```bash\nTHINKS_CODE_EMAILS=\"me@personal,me@work\" thinks-mcp code ~/Projects/*/ ~/work/repo\n```\n\nАвторство определяется через `git blame`: блок попадает в корпус, только если\nбольше половины его строк написаны с указанных адресов. Чужие комментарии,\nстроки-разделители, закомментированный код и директивы инструментов\n(`eslint-disable`, `shellcheck source=`) отсеиваются.\n\nДаёт два регистра: `code` — инлайн, `jsdoc` — докблоки. Замеряются они\nраздельно, потому что это разные жанры: инлайн обычно однострочный, а докблок\nначинается с итоговой фразы и продолжается.\n\nПовторная сборка переиспользует результаты `git blame` для файлов, которые не\nменялись — ключ по blob-хешу. На десятке репозиториев это разница между\nполуминутой и парой секунд.\n\nДва корпуса живут в одной базе и не мешают друг другу: `build` пересобирает\nтолько чат-регистры, `code` — только кодовые.\n\nКорпус хранится в `~/.config/thinks-mcp/style.db` — рядом с настройками, а не\nрядом с кодом. Иначе при обновлении пакета он потерялся бы вместе со старой\nверсией.\n\n## Подключение\n\nСкопируй `.mcp.json.example` в `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"thinks\": {\n      \"command\": \"/opt/homebrew/bin/mise\",\n      \"args\": [\"exec\", \"npm:@aimuzov/thinks-mcp\", \"--\", \"thinks-mcp\"]\n    }\n  }\n}\n```\n\nЕсли ставил из исходников через `npm i -g` — запускай через ту версию Node,\nв которую лёг бинарник:\n\n```json\n{\n  \"mcpServers\": {\n    \"thinks\": {\n      \"command\": \"/opt/homebrew/bin/mise\",\n      \"args\": [\"x\", \"node@24\", \"--\", \"thinks-mcp\"]\n    }\n  }\n}\n```\n\nСервер стартует и без собранного корпуса и объясняет, что делать, — машина, где\nархив ещё не импортирован, получит рабочий сервер, а не упавший.\n\n## Инструменты\n\n| Инструмент | Что делает |\n|---|---|\n| `write_as_me` | бриф для текста с нуля по заданию |\n| `reply_as_me` | бриф для ответа на входящее сообщение |\n| `rephrase_as_me` | бриф для переписывания готового текста |\n| `check_as_me` | детерминированная оценка 0–100 и список отклонений |\n| `find_my_messages` | поиск по архиву: «как я обычно отказываю» |\n\nУ каждого есть параметр `register`: `dm` — личка, `group` — групповой чат,\n`longform` — длинный авторский текст, `code` — инлайн-комментарий, `jsdoc` —\nдокблок. Стиль в них разный, поэтому профиль, ограничения и поиск разведены по\nрегистрам, а `check_as_me` для кода проверяет своё: ширину строки, маркеры,\nводу вместо факта, типы в фигурных скобках.\n\nДля комментариев в коде брать только `code` и `jsdoc`. Чат-регистры замерены по\nпереписке — короткие реплики, разговорные формы, эмодзи — и в коде дают чужой\nголос.\n\nУ `write_as_me` и `find_my_messages` есть ещё `lang` (`ru`/`en`) — осмысленно\nдля кода, где используются оба языка.\n\nОстальные параметры:\n\n- `examples` (4–40, по умолчанию 18) у трёх инструментов-брифов — сколько\n  примеров из архива подложить в бриф;\n- `length` (`short`/`normal`/`long`) у `write_as_me` — относительно обычного\n  для регистра объёма;\n- `hint` у `reply_as_me` — что должно быть сказано в ответе;\n- `limit`, `yearFrom` и `matchIncoming` у `find_my_messages`; последний ищет\n  по сообщениям собеседников, а не по твоим.\n\nУ `check_as_me` есть необязательный параметр `code` — строки, над которыми стоит\nкомментарий. С ним проверка ловит и пересказ кода. Ограничение честное: она\nсравнивает слова, поэтому русский комментарий над английским кодом оценить не\nможет и промолчит.\n\nРесурсы: `style://profile` и `style://profile/{register}` — профиль как\nmarkdown. Промпты: `as-me`, `reply-as-me` и `comment-as-me`.\n\nРабочий цикл, который задают промпты: получить бриф → написать →\n`check_as_me` → переписать по замечаниям, пока оценка не станет высокой.\n\n## Чего стоит пример ответа\n\n`reply_as_me` строит примеры из пар, а пары бывают двух сортов.\n\nСообщение с `reply_to_message_id` — это факт: автор сам выбрал, на что\nотвечает. Всё остальное выведено из порядка сообщений, то есть «что написали\nперед этим». Такая догадка ломается там, где переписка обычнее всего: тебе\nпишут «Извини», ответ совсем про другое, и пара учит модель отвечать не по теме.\n\nПоэтому пары с явной цитатой ранжируются выше выведенных, а выведенная пара, на\nкоторую автор потратил больше получаса, — ещё ниже. У каждого примера подписано,\nкакой он.\n\nВ некоторых регистрах пар почти нет: выгрузка Telegram для супергруппы почти не\nсодержит сообщений собеседников. Там бриф прямо это говорит, а не выдаёт\nподходящие по теме ходы за ответы.\n\n## Свежесть\n\nЗа десять лет переписки привычки заметно смещаются — пунктуация, длина реплики,\nритм. Профиль, усреднённый по всему архиву, не описывает ни сегодняшнего\nчеловека, ни его же десятилетней давности. Поэтому:\n\n- профиль и ограничения считаются по последним годам (`THINKS_RECENT_YEARS`), а\n  цифры за всё время показываются справочно;\n- выдача поиска взвешивается по году — свежий пример при прочих равных\n  выигрывает. Вес подобран так, чтобы десятилетие возраста стоило примерно треть\n  типичного разброса BM25 в выдаче: свежесть влияет, но нерелевантное новое не\n  обгоняет релевантное старое.\n\n## Знаки препинания\n\nКавычки-ёлочки, длинное и короткое тире, лапки — типографика печатной книги, и\nмодель ставит её по умолчанию. Ставит ли её владелец архива, решает замер: доля\nсообщений с этим знаком считается отдельно для чата и для каждого кодового\nжанра. Ниже 2% знак попадает в профиль как чужой, `check_as_me` за него\nштрафует, а бриф просит вместо тире дефис и простые кавычки. Порог здесь свой,\nне общий для антипаттернов: дефис в 1% сообщений уже выдаёт текст, а вот\nканцелярское слово при такой доле — ещё нет.\n\nДвойной дефис измеряется, но никогда не штрафуется. Это не чужой знак, а\nASCII-замена тире, и она разная по регистрам: в переписке её нет, в\nкомментариях она может быть нормой. Там, где её доля от 1%, бриф прямо просит\nписать тире двумя дефисами.\n\nОтдельная беда — комментарии, написанные с ассистентом. `git blame` считает их\nтвоими, а знаки в них его: в моём корпусе за 2026 год доля ёлочек в докблоках\nподскочила с нуля до 6%. Год, начиная с которого комментарии писались уже не\nвручную, задаётся в `THINKS_CODE_HANDWRITTEN_UNTIL`. Всё, что позже, остаётся\nв индексе и в поиске, но в замер знаков не идёт. Если после отсечки в жанре\nосталось меньше 200 строк, знаки для него не считаются вовсе: уверенный ноль на\nдвадцати строках хуже, чем честное отсутствие цифры.\n\n## Приватность\n\nЭто архив личной переписки, поэтому:\n\n- выгрузка и собранный индекс не коммитятся никогда — каталог с данными лежит\n  вообще вне репозитория;\n- телефоны, почта и номера карт вырезаются по разметке Telegram — выгрузка\n  гарантирует, что разбиение на сущности покрывает текст сообщения целиком, —\n  плюс страховочные регулярки для того, что Telegram не разметил;\n- чаты, отправители и репозитории хранятся под псевдонимами, настоящие имена в\n  базу не попадают;\n- фамилии вырезаются из текста сообщений, имена — нет: имя никого не\n  идентифицирует, а без них примеры выглядели бы как документ с вымарками;\n- из стиль-профиля имена собственные исключаются отдельно.\n\nИсключить чаты целиком: `THINKS_CHAT_STOPLIST=\"Чат один,Чат два\"`.\n\n## Команды\n\n```bash\nthinks-mcp build <dump.json>    # собрать чат-корпус\nthinks-mcp code <репозитории>   # собрать корпус комментариев\nthinks-mcp profile jsdoc        # профиль по регистру\nthinks-mcp where                # где лежит индекс\nthinks-mcp holdout --answers    # слепая проверка качества\nthinks-mcp serve                # то же, что без аргументов\nthinks-mcp --help\n```\n\nВ самом репозитории:\n\n```bash\nmise run check           # типы, форматирование, тесты\npnpm test\npnpm build\n```\n\n`holdout` — слепая проверка качества: при сборке 20 реальных пар\n«входящее → ответ» откладываются и не попадают в индекс. Сначала смотришь\nтолько входящие, отвечаешь через `reply_as_me`, потом сверяешь с тем, что было\nотвечено на самом деле.\n\n## Переменные окружения\n\n| Переменная | По умолчанию | Зачем |\n|---|---|---|\n| `THINKS_DATA_DIR` | `$XDG_CONFIG_HOME/thinks-mcp` или `~/.config/thinks-mcp` | каталог с индексом |\n| `THINKS_DUMP` | `<data-dir>/dump.json` | выгрузка, если не передана аргументом |\n| `THINKS_DB` | `<data-dir>/style.db` | путь к файлу индекса |\n| `THINKS_OWNER_ID` | автоопределение | если автоопределение ошиблось |\n| `THINKS_CHAT_STOPLIST` | пусто | чаты через запятую, которые не индексируются |\n| `THINKS_CODE_EMAILS` | `git config --global user.email` | git-адреса автора через запятую |\n| `THINKS_RECENT_YEARS` | `3` | окно «как я пишу сейчас» для профиля |\n| `THINKS_CODE_HANDWRITTEN_UNTIL` | нет | год, с которого комментарии писались не вручную: замер знаков их не берёт |\n| `THINKS_BURST_WINDOW` | `90` | окно склейки сообщений в ход, секунды |\n| `THINKS_LONGFORM_MIN` | `300` | порог longform-регистра, символы |\n| `THINKS_HOLDOUT` | `20` | сколько пар отложить на слепую проверку |\n\n## Зависимости\n\n`@modelcontextprotocol/sdk` и `zod` — и всё. Полнотекстовый поиск — FTS5 из\nвстроенного в Node `node:sqlite`, стеммеры русского и английского написаны\nздесь же (`src/search/stem.ts`), потому что FTS5 токенизирует оба алфавита, но\nне знает морфологии.\n\n## Лицензия\n\nMIT\n","readmeFilename":"README.ru.md"}