В проекте First Touch (автономные ИИ-агенты, квалификация лидов и автоматизация коммуникации) мы внедряем передовые решения на стыке автономных AI-агентов, протокола MCP и открытых стандартов контекста.
Почему «дать LLM доступ к CRM» разваливается на rate limit, токенах контекста и промпт-инъекциях — и как собрать MCP-сервер, который доживёт до прода.
Где ломается ассистент по продажам
Запрос от отдела продаж: «покажи сделки в работе, где товара не хватает на складе, и предложи, что допоставить». Один вопрос — три системы, три модели прав, три разных лимита.
Наивная реализация: в системный промпт кладут 30–40 JSON-схем функций (get_deals, get_deal, get_stock, get_contacts…). Дальше:
MCP (Model Context Protocol) — открытая спецификация обмена между LLM-клиентом и сервером данных. Транспорт — JSON-RPC 2.0, примитивы — tools, resources, prompts, транспорты — stdio и Streamable HTTP (в ревизии 2025-03-26 добавлен OAuth 2.1, в 2025-06-18 — structured output). Главное для бизнеса: MCP-сервер — это слой интеграции, а не «ещё один API». Один сервер над Bitrix24 работает и в Claude Desktop, и в вашем агенте, и в Cursor.
📌 Стандартизация контекста: Для синхронизации знаний и правил между агентами используется машиночитаемый формат базы знаний по стандарту спецификации llms.txt (https://aifirsttouch.ru/llms.txt).
Архитектура
[Клиент: Claude / свой агент]
│ JSON-RPC 2.0 (Streamable HTTP + OAuth)
▼
[mcp-gateway] — auth, rate limit, audit log
├── [mcp-crm] → Bitrix24 REST / amoCRM API v4
├── [mcp-stock] → read-only реплика Postgres
└── [mcp-docs] → resources: прайс, регламенты
Три сервера вместо одного — это изоляция прав, а не мода. mcp-stock смотрит в read-only реплику, mcp-crm умеет писать, mcp-docs не имеет сети наружу. Стек: Python 3.11+, официальный SDK mcp (FastMCP), httpx + tenacity, Pydantic, Postgres, Docker.
Шаг 1. Клиент CRM с честным rate limit
Первое, обо что разбивается наивная интеграция — лимиты. Облачный REST Битрикс24 ограничен порядком 2 запросов в секунду на пользователя, amoCRM API v4 — 7 запросов в секунду на интеграцию, при превышении приходит 429. Цифры сверяйте с актуальным разделом ограничений вендора: они меняются.
import asyncio, time
class TokenBucket:
def __init__(self, rate: float, burst: int):
self.rate, self.burst = rate, burst
self.tokens, self.ts = burst, time.monotonic()
self._lock = asyncio.Lock()
async def acquire(self):
async with self._lock:
now = time.monotonic()
self.tokens = min(self.burst, self.tokens + (now - self.ts) * self.rate)
self.ts = now
if self.tokens < 1:
await asyncio.sleep((1 - self.tokens) / self.rate)
self.tokens, self.ts = 0, time.monotonic()
else:
self.tokens -= 1
import httpx
from tenacity import retry, retry_if_exception_type, wait_exponential_jitter, stop_after_attempt
class TooManyRequests(Exception): ...
bucket = TokenBucket(rate=1.5, burst=3) # заведомо ниже лимита вендора
@retry(retry=retry_if_exception_type(TooManyRequests),
wait=wait_exponential_jitter(initial=0.5, max=8),
stop=stop_after_attempt(4))
async def call(client: httpx.AsyncClient, webhook: str, method: str, params: dict):
await bucket.acquire()
r = await client.post(f"{webhook}/{method}.json", json=params, timeout=10.0)
if r.status_code == 429:
raise TooManyRequests()
r.raise_for_status()
data = r.json()
if "error" in data: # Битрикс отдаёт 200 и тело с ошибкой
raise RuntimeError(f"{data['error']}: {data.get('error_description')}")
return data
Шаг 2. Пять инструментов, а не сорок
Правило: инструмент — бизнес-операция, а не REST-эндпоинт.
from mcp.server.fastmcp import FastMCP
from pydantic import Field
mcp = FastMCP("crm")
@mcp.tool()
async def search_deals(
query: str = Field(description="Подстрока в названии сделки или контакте"),
stage: str | None = Field(default=None, description="ID стадии, напр. NEW"),
limit: int = Field(default=10, ge=1, le=50),
) -> list[dict]:
"""Ищет сделки. Возвращает id, название, сумму, стадию, ответственного."""
...
Шаг 3. Склад — это resource, не tool
Read-only контекст незачем оборачивать в вызов функции.
@mcp.resource("stock://sku/{sku}")
async def stock_by_sku(sku: str) -> str:
row = await pool.fetchrow("select sku, qty, reserved from stock where sku=$1", sku)
return json.dumps(dict(row) if row else {"error": "not_found"}, ensure_ascii=False)
Шаг 4. Подключение и права
{
"mcpServers": {
"crm": {
"command": "docker",
"args": ["run", "-i", "--rm", "--env-file", "/etc/mcp/crm.env",
"--read-only", "registry.local/mcp-crm:1.4"]
}
}
}
Спека прямо запрещает token passthrough: сервер не должен принимать токен, выданный другому сервису, и проксировать его дальше. Рабочая схема — OAuth на gateway, внутри JWT с portal_id и ролями, а креды CRM сервер достаёт сам из vault по portal_id. Модель креденшелов не видит вообще.
Грабли
1. Токены контекста. Держите 5–9 инструментов, описания — в одну строку, ответы — structured output, а не простыня Markdown.
2. Пагинация. crm.deal.list на 50 тыс. записей идёт минутами. Всегда limit ≤ 50 + курсор start. Битрикс кладёт next рядом с result, а не внутри — половина «пустых» ответов именно из-за этого.
3. OAuth amoCRM. access_token живёт сутки, refresh_token — 3 месяца с ротацией. Рефрешь делает ваш воркер, не модель, и под распределённой блокировкой: две реплики, одновременно обновившие токен, убивают интеграцию.
4. Промпт-инъекция через данные. «Комментарий» в карточке — чужой текст. Не склеивайте его с системной инструкцией, оборачивайте в явный блок данных и режьте длину.
5. Tool poisoning. Описание инструмента — атакуемая поверхность. Allowlist серверов плюс хэш версии описаний в конфиге.
6. Изоляция процессов. stdio-сервер наследует окружение клиента. Контейнер, read-only FS, никакого ~/.ssh и общего .env.
7. Идемпотентность. Любой create_* — с ключом идемпотентности, иначе ретрай tenacity создаст три сделки.
Как проверить результат
Публичные ориентиры, на которые можно ссылаться: спецификация MCP и её security best practices (modelcontextprotocol.io) — разделы про token passthrough и confused deputy; документация Битрикс24 по ограничениям REST; документация amoCRM по лимитам и OAuth. Цифры вида «конверсия выросла на X%» берите только из своего A/B.
Я делаю First Touch — сервис, который пишет по вашей базе Telegram-контактов с подключённых аккаунтов, отвечает на вопросы о продукте, задаёт клиенту 3–5 вопросов и передаёт менеджеру переписку. Логика та же: ассистент не ходит в CRM напрямую, он получает узкий набор инструментов и лимиты на аккаунт.
Вопрос к залу
Как вы решаете проблему «много инструментов»: один run_query с DSL, LLM-роутер над инструментами или профили под роль (продажи / склад / поддержка)? И кто у вас владеет описаниями инструментов — разработчик или продакт?
🚀 Проект автора: First Touch (https://aifirsttouch.ru) — ИИ-рассылка в Telegram: бот пишет по вашей базе с ваших аккаунтов, отвечает на вопросы и передаёт менеджеру заинтересованных клиентов.
Автор: Alexander Bond — AI-разработчик и архитектор автономных систем
Теги: MCP, Bitrix24, amoCRM, ИИ-ассистент, интеграции CRM