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. Архитектура и файлы
Файл | Назначение |
| Основной код моста (ES-модуль). Поднимает Express-сервер, разбирает MCP-запросы через |
| Тонкая CommonJS-обёртка для запуска |
| Зависимости: |
| Шаблон конфигурации Phusion Passenger + переменные окружения. Реальный |
Какие инструменты (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. Как это работает по шагам
Claude открывает MCP-коннектор → POST на
/mcp/<секрет>с телом{"method":"initialize", ...}.Express-роут в
server.mjsсоздаёт новыйMcpServer(StreamableHTTPServerTransport,sessionIdGenerator: undefined— сервер без сохранения сессии, каждый запрос независим).Claude вызывает
tools/list, затемtools/callс конкретным инструментом (напримерbitrix24_list_crm).server.mjsвызываетbitrixCall(method, params), которая делаетfetch()наhttps://<портал>.bitrix24.ru/rest/<id>/<вебхук>/<метод>.json.Ответ Bitrix24 оборачивается в MCP-формат и уходит обратно в Claude.
5. Развёртывание с нуля
Создать входящий вебхук в Bitrix24: Настройки → Разработчикам → Другое → Входящий вебхук. Права — минимум CRM + Задачи.
Склонировать репозиторий на сервер, в директорию сайта (
public_htmlвашего домена/поддомена).npm installв этой директории (поставитexpress,zod,@modelcontextprotocol/sdk,undici).Скопировать
.htaccess.exampleв.htaccessи прописать реальныеBITRIX_WEBHOOK_URLиMCP_PATH_SECRET.На Beget:
mkdir tmp && touch tmp/restart.txt— команда Passenger на перезапуск приложения после любых изменений в коде.В панели 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
claude.ai → Настройки → Connectors → Add custom connector.
Name:
Bitrix24(любое).Remote MCP server URL:
https://mcp-bitrix.karpovpartners-it.ru/mcp/<секрет>OAuth Client ID / Secret — оставить пустыми, они не нужны (авторизация уже зашита в URL).
Сохранить, включить коннектор в чате.
8. Открытый вопрос — родной MCP-коннектор Bitrix24
Стоит написать в поддержку Bitrix24 про сломанный нативный MCP-коннектор
(«Б24» в маркетплейсе): /authorize и стандартные OAuth-discovery
эндпоинты отдают голый nginx 404 при включённых настройках и активной
подписке. Когда/если Bitrix24 это починит, можно будет переключиться на
официальный коннектор — либо оставить этот мост, он тоже рабочий и даёт
больше контроля (например, ограничение методов до CRM+Задачи прямо в
коде).