Skip to main content
Glama
KarpovPartnersCom

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+Задачи прямо в
коде).

Maintenance

ActivitySlowing
ResponsivenessNo issues