Skip to main content
Glama
KarpovPartnersCom

Bitrix24 MCP Bridge

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-файл:

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