bitrix-chat-mcp
by imejaikin
README.md
# bitrix-chat-mcp
**Локальное зеркало истории чатов Bitrix24 с точным поиском, доступное AI-ассистентам через MCP.**
> *A local mirror of your Bitrix24 chat history with fast full-text search, exposed to AI assistants
> over MCP. Read-only against Bitrix: it never posts anything back.*
Решает конкретную боль: важные инженерные договорённости живут **в чатах**, а не в задачах и не в
документации. Найти их через живой API тяжело — приходится выкачивать тысячи строк и грепать.
Здесь они ищутся за миллисекунды и не теряются.
Всё держится на машине: SQLite рядом с кодом, никакого облака, никаких эмбеддингов. Node 22+,
две зависимости (`@modelcontextprotocol/sdk`, `zod`).
**В Bitrix этот сервер не пишет никогда.** Единственная запись — локальные закладки в своей же базе.
## Как это соотносится с `bitrix24-local-mcp`
Инструменты дополняют друг друга, у них разные задачи:
| | `bitrix24-local-mcp` | `bitrix-chat-mcp` (этот) |
|---|---|---|
| Про что | **живое состояние**: задачи, свежие сообщения, пользователи | **память**: накопленная история чатов |
| Пишет в Bitrix | да (создать задачу, отправить сообщение) | **никогда** |
| Поиск по истории | тяжело: постранично через API | быстро, SQLite FTS5 |
| Без сети | не работает | работает |
**Связаны они тремя вещами, без общего кода:**
1. **Общий креденшл.** Читаем `BITRIX24_WEBHOOK_URL` из своего `.env`, а если его нет —
из `../bitrix24-local-mcp/.env` (сосед опционален, без него всё работает). Один секрет
на две тулзы, но процессы независимы:
обновление или падение одного не ломает другой.
2. **Общий словарь идентификаторов.** Зеркало возвращает `dialog_id` и `message_id` — те же,
что понимает живой MCP. Это и есть точка стыковки:
```
chat_search("ai-rules-check") → msg 322265, chat10833
↓ те же идентификаторы
im_chat_messages(chat10833) → свежий контекст
im_send_message(chat10833, …) → ответить
```
3. **Подсказки в описаниях инструментов** — в них прямо сказано, когда переходить в живой MCP.
## Установка
Нужен входящий вебхук Bitrix24 (`Приложения` → `Разработчикам` → `Входящий вебхук`) с правом
**Мессенджер (`im`)** и **Пользователи (`user`)**. URL выглядит так:
`https://ВАШ-ПОРТАЛ.bitrix24.ru/rest/USER_ID/СЕКРЕТНЫЙ_КОД/`.
```bash
make install
cp .env.example .env # вписать BITRIX24_WEBHOOK_URL
make ui # выбрать чаты -> http://127.0.0.1:7625
make sync # выкачать историю
make status # что получилось
```
> ⚠️ Вебхук — это полный доступ к порталу под правами его владельца. Храните его как пароль:
> он лежит в `.env`, который под `.gitignore`.
`config.json` (какие чаты зеркалим) в репозитории **не хранится** — по одному этому списку видно,
с кем человек переписывается. В git лежит только `config.example.json`; свой создаётся через
`make ui`.
Подключение в `~/.mcp.json` (или в конфиге вашего MCP-клиента):
```json
{
"mcpServers": {
"bitrix-chat": { "command": "node", "args": ["/путь/к/bitrix-chat-mcp/src/server.mjs"] }
}
}
```
После правки конфига Claude Code нужно перезапустить.
## Настройка через UI
`make ui` поднимает страницу выбора чатов: командные каналы и личные переписки разделены,
видно объём и глубину уже зеркалируемого. Отметили → «Сохранить» → `make sync`.
Ограничения страницы намеренные:
- слушает **только `127.0.0.1`** — это не сетевой сервис;
- **не показывает и не принимает вебхук** — секрет остаётся в `.env`;
- ничего не пишет в Bitrix, только правит локальный `config.json`.
> **Про личные переписки.** Зеркалирование складывает сообщения на диск в открытом виде.
> В личках нередко передают доступы и ключи — отмечайте их осознанно. По умолчанию
> в конфиге только командные каналы, вложения не выкачиваются вообще.
## Инструменты MCP
| Инструмент | Зачем |
|---|---|
| `chat_search` | найти, где обсуждали решение или замечание; отдаёт `dialog_id` + `message_id` |
| `chat_context` | сообщение с соседними — понять, чем закончилось обсуждение |
| `chat_list` | какие чаты в зеркале, объём и глубина |
| `chat_pin` | отметить сообщение как договорённость с пояснением (локально) |
| `chat_pins` | все отмеченные договорённости |
| `answer_pending` | вопросы ко мне, после которых я в том же чате ничего не написал |
| `my_promises` | мои же «сделаю / отпишу / пришлю» — и писал ли я в чате после |
Закладки (`chat_pin`) — это ответ на «помнить важные замечания»: помеченное не растворяется
в истории и достаётся одним вызовом.
### Что просело между чатами
`answer_pending` и `my_promises` отвечают на два вопроса, которые теряются одинаково —
сообщение прочитано, ответ отложен «на потом», и «потом» не наступает.
Что считать «моей темой», задаётся в `config.json` полем `topics` (например
`["биллинг", "касса", "отчёты"]`): по этим словам вопрос считается адресованным вам даже без
прямого упоминания. Пустой список — только прямые упоминания и личные диалоги.
Обе выборки опираются на один грубый признак: **писал ли я в этом чате после**. Точного
«ответа именно на это» из зеркала не достать, а вечное напоминание про закрытый вопрос
хуже пропуска — поэтому инструменты показывают лишнее охотнее, чем прячут.
Что выяснилось на живых данных (две недели переписки, 372 сообщения):
* **Тему надо искать по началу слова, а не подстрокой.** «бот» внутри «работал» и «чек»
внутри «человек» затаскивали в выдачу чужие ежедневные отчёты: 24 «вопроса» вместо 9.
* **Знак вопроса из ссылки — не вопрос.** `…/compare/a...b?from_project_id=26` держал
в списке сообщение, где вопроса нет вообще.
* **«Сделаю» и «сделал» отличаются одной буквой** и противоположны по смыслу, поэтому
шаблоны обещаний перечислены точно, без основ слов.
`my_promises` не решает, выполнено обещание или нет: «сделаю» закрывается коммитом, а не
сообщением. Он помечает `silent_since` — после этого обещания я в чате молчу.
Свой `user_id` берётся из самого вебхука (`/rest/45/секрет/`) — без обращения к сети
и без вывода секрета наружу.
## Синхронизация
```bash
make sync # инкрементально: тянет только то, что появилось после прошлого раза
```
Первый прогон по чату забирает историю вглубь (ограничение — `maxPagesPerChat` в `config.json`),
дальше докачиваются только новые сообщения.
Вместе с чатами забираются **треды** — комментарии к сообщению. В Bitrix тред это отдельный
скрытый чат, связь с родительским сообщением лежит в `commentInfo`. Ветка перекачивается,
только если выросла: счётчик из `commentInfo` сравнивается с сохранённым. В поиске такое
сообщение показывается как «Родительский чат · тред», а не голым `chat11115`.
Про API: у `im.dialog.messages.get` неочевидная семантика, проверенная экспериментально —
`FIRST_ID` отсутствует → последняя страница; `FIRST_ID: 0` → **самые старые**;
`FIRST_ID: <id>` → сообщения **новее** указанного. Поэтому курсор двигается по максимальному id,
и один код работает и для первой закачки, и для докачки.
## Что не индексируется
Системные события и сообщения без текста (звонки, «X вступил в чат», голые вложения) —
они только зашумляют поиск. Файлы не выкачиваются.
Из-за этого число сохранённых сообщений ветки меньше, чем `messageCount` в ответе Bitrix.
Признак «ветка выросла» строится на самом `messageCount`, а не на подсчёте своих строк —
иначе каждый прогон перекачивает ветки заново.
## Структура
```
src/
server.mjs MCP-сервер (stdio)
sync.mjs инкрементальная синхронизация
ui.mjs локальная страница настройки
status.mjs состояние зеркала
lib/bitrix.mjs клиент REST + чтение креденшла
lib/db.mjs схема, FTS5, помощники
lib/attention.mjs вопросы без ответа и мои обещания (чистые функции)
test/ node --test, запуск: npm test
config.json какие чаты зеркалим (правится через UI)
data/ БД зеркала (в git не хранится)
```
## Тесты
```bash
npm test
```
Чистые функции разбора (`src/lib/attention.mjs`) покрыты тестами без обращения к базе и к сети.
## Лицензия
MIT — см. [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues