Bitrix24 MCP Bridge
README.md
# Bitrix24 MCP Bridge
Мост между Claude (MCP) и Bitrix24 CRM/Задачами. Развёрнут на хостинге
Beget по адресу `mcp-bitrix.karpovpartners-it.ru`.
## 1. Зачем это понадобилось
Изначально пытались подключить Claude к Bitrix24 через встроенный в Bitrix24
коннектор «МСР-подключения» (приложение `aiassistant.bitrix_mcp` /
кнопка «Б24» в маркетплейсе). Оказалось, что эта функция не работает:
эндпоинты `/authorize`, `/.well-known/oauth-authorization-server`,
`/.well-known/oauth-protected-resource` отдают голый nginx 404, хотя все
настройки и подписка в порядке. Это баг/недокат фичи на стороне Bitrix24,
а не ошибка в настройках.
В качестве обходного пути был написан собственный MCP-сервер («мост»),
который:
- принимает MCP-запросы от Claude по протоколу Streamable HTTP;
- транслирует их в вызовы обычного REST API Bitrix24 через **входящий
вебхук** (создан в Bitrix24 с правами только на CRM + Задачи);
- отдаёт результат обратно в Claude в виде MCP tool-ответов.
## 2. Архитектура и файлы
| Файл | Назначение |
|---|---|
| `server.mjs` | Основной код моста (ES-модуль). Поднимает Express-сервер, разбирает MCP-запросы через `@modelcontextprotocol/sdk`, дергает REST API Bitrix24. |
| `app.js` | Тонкая CommonJS-обёртка для запуска `server.mjs`. Нужна из-за особенности Passenger на Beget (см. ниже). |
| `package.json` | Зависимости: `@modelcontextprotocol/sdk`, `express`, `zod`, `undici`. |
| `.htaccess.example` | Шаблон конфигурации Phusion Passenger + переменные окружения. Реальный `.htaccess` с боевыми секретами **не хранится в репозитории** (см. `.gitignore`) — он развёрнут напрямую на сервере и сохранён отдельно у владельца проекта. |
### Какие инструменты (tools) доступны в Claude
- `bitrix24_call` — вызов любого метода `crm.*`, `task.*`, `tasks.*`,
`user.current`, `profile` напрямую (эскейп-люк).
- `bitrix24_list_crm` / `bitrix24_get_crm` / `bitrix24_add_crm` /
`bitrix24_update_crm` — список/чтение/создание/обновление записей CRM
(`lead`, `deal`, `contact`, `company`).
- `bitrix24_list_tasks` / `bitrix24_add_task` / `bitrix24_update_task` /
`bitrix24_complete_task` — работа с задачами.
Сервер жёстко ограничивает вызываемые методы Bitrix24 префиксами `crm.`,
`task.`, `tasks.`, `user.current`, `profile` (см. `ALLOWED_METHOD_PREFIXES`
в `server.mjs`) — это защита на случай, если у вебхука когда-нибудь
появятся более широкие права.
## 3. Аутентификация / безопасность
У кастомных MCP-коннекторов в интерфейсе Claude нет поля для
произвольных HTTP-заголовков — только URL (+ опционально OAuth
Client ID/Secret). Поэтому вместо заголовка `Authorization` секрет
зашит **в путь URL**:
```
https://mcp-bitrix.karpovpartners-it.ru/mcp/<секрет>
```
Секрет и адрес вебхука Bitrix24 хранятся только в боевом `.htaccess`
на сервере и в приватной копии у владельца проекта — **они намеренно не
закоммичены в этот репозиторий** (см. `.gitignore`). Любой, кто узнает
секрет из URL, получит доступ к CRM и задачам Bitrix24 в рамках прав
вебхука.
## 4. Как это работает по шагам
1. Claude открывает MCP-коннектор → POST на `/mcp/<секрет>` с телом
`{"method":"initialize", ...}`.
2. Express-роут в `server.mjs` создаёт новый `McpServer`
(`StreamableHTTPServerTransport`, `sessionIdGenerator: undefined` —
сервер без сохранения сессии, каждый запрос независим).
3. Claude вызывает `tools/list`, затем `tools/call` с конкретным
инструментом (например `bitrix24_list_crm`).
4. `server.mjs` вызывает `bitrixCall(method, params)`, которая делает
`fetch()` на `https://<портал>.bitrix24.ru/rest/<id>/<вебхук>/<метод>.json`.
5. Ответ Bitrix24 оборачивается в MCP-формат и уходит обратно в Claude.
## 5. Развёртывание с нуля
1. **Создать входящий вебхук в Bitrix24**: Настройки → Разработчикам →
Другое → Входящий вебхук. Права — минимум CRM + Задачи.
2. Склонировать репозиторий на сервер, в директорию сайта (`public_html`
вашего домена/поддомена).
3. `npm install` в этой директории (поставит `express`, `zod`,
`@modelcontextprotocol/sdk`, `undici`).
4. Скопировать `.htaccess.example` в `.htaccess` и прописать реальные
`BITRIX_WEBHOOK_URL` и `MCP_PATH_SECRET`.
5. На Beget: `mkdir tmp && touch tmp/restart.txt` — команда Passenger на
перезапуск приложения после любых изменений в коде.
6. В панели Beget: «Сайты» → у нужного сайта → «⋮» → «Прикрепить домен» —
без этого шага Apache даже не пытается достучаться до вашего кода
(см. раздел 6.2 — легко забыть, ошибка неочевидная).
## 6. Проблемы, с которыми столкнулись при разворачивании на Beget, и как их решили
Журнал отладки — пригодится при повторном развёртывании на Beget или
другом shared-хостинге со старым Node.js.
### 6.1. Node.js на Beget — версия 16.20.2, слишком старая
На стороне Beget (Ubuntu 18.04, glibc 2.27) официальные сборки Node 18+ не
запускаются (`GLIBC_2.28' not found`). Пришлось остаться на Node 16.20.2 и
вручную подложить недостающие в Node 16 глобальные объекты, которые нужны
современным зависимостям (`@modelcontextprotocol/sdk`, Express 5):
- `fetch`, `Headers`, `Request`, `Response` — через пакет `undici`.
- `crypto` (Web Crypto API, `crypto.randomUUID()`) — через встроенный
`node:crypto` (`webcrypto`).
- `ReadableStream`, `WritableStream`, `TransformStream` — через встроенный
`node:stream/web`.
- `structuredClone`, `MessageChannel`/`MessagePort` — на всякий случай,
через `node:v8` и `node:worker_threads`.
Всё это — в самом начале `server.mjs`, **до** импорта Express и MCP SDK
(сделано через `await import(...)`, а не через обычный `import` сверху
файла — см. следующий пункт, почему).
### 6.2. Домен не был «приклеен» к папке сайта
После загрузки кода на сервер сайт отдавал фирменную страницу Beget
«Домен не привязан к директории на сервере» вместо приложения. Просто
создать папку сайта и залить туда файлы недостаточно — домен нужно
отдельно «прикрепить» через панель: Сайты → нужный сайт → ⋮ → «Прикрепить
домен». Неочевидный шаг, который легко пропустить.
### 6.3. `ERR_REQUIRE_ESM`: Passenger не умеет грузить ES-модули
Passenger на Beget (старая версия, `passenger40`) запускает стартовый файл
через `require()`, а `require()` в Node принципиально не умеет грузить
ES-модули (`import`/`export`, `type: module` в package.json). `server.mjs`
использует `await` на верхнем уровне файла — а это возможно только в
ES-модуле.
Решение: в `package.json` нет `"type": "module"` (по умолчанию `.js` —
CommonJS), сам код лежит в файле с расширением `.mjs` (расширение `.mjs` —
это всегда ES-модуль, вне зависимости от `package.json`), а точкой входа
для Passenger сделан `app.js` — крошечный CommonJS-файл:
```js
// app.js
import('./server.mjs').catch((err) => {
console.error('Failed to start server:', err);
process.exit(1);
});
```
`require()` спокойно загружает `app.js` (это обычный CommonJS), а внутри
него **динамический** `import()` (это функция, а не декларация) уже может
асинхронно загрузить ES-модуль `server.mjs`.
### 6.4. Секрет в пути URL
`MCP_PATH_SECRET` — случайная строка (например, `secrets.token_urlsafe(32)`
в Python, или `crypto.randomUUID() + crypto.randomUUID()` в консоли
браузера). Если нужно перевыпустить секрет — сгенерировать новый и
обновить в `.htaccess` на сервере и в настройках коннектора в Claude.
## 7. Как подключить в Claude
1. claude.ai → Настройки → Connectors → Add custom connector.
2. Name: `Bitrix24` (любое).
3. Remote MCP server URL:
`https://mcp-bitrix.karpovpartners-it.ru/mcp/<секрет>`
4. OAuth Client ID / Secret — оставить пустыми, они не нужны (авторизация
уже зашита в URL).
5. Сохранить, включить коннектор в чате.
## 8. Открытый вопрос — родной MCP-коннектор Bitrix24
Стоит написать в поддержку Bitrix24 про сломанный нативный MCP-коннектор
(«Б24» в маркетплейсе): `/authorize` и стандартные OAuth-discovery
эндпоинты отдают голый nginx 404 при включённых настройках и активной
подписке. Когда/если Bitrix24 это починит, можно будет переключиться на
официальный коннектор — либо оставить этот мост, он тоже рабочий и даёт
больше контроля (например, ограничение методов до CRM+Задачи прямо в
коде).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues