Ассистенты без MCP
MCP — не единственный способ, которым ассистент вызывает инструменты. YandexGPT, навыки Алисы, локальные модели в своём контуре и свои сборки на вызове функций подключаются через слой функций: каталог инструментов тот же, меняется только форма вызова.
Сначала проверьте, точно ли слой нужен. MCP умеет не только модель: чаще его поддерживает фреймворк, в котором собран агент. Для GigaChat это LangChain и LangGraph — MCP-клиент даёт пакет langchain-mcp-adapters (транспорты stdio и HTTP), а сама модель подключается через langchain-gigachat. В такой сборке наш сервер работает как есть, по быстрому старту, и слой функций не нужен вовсе. То же у GigaAgent от Сбера и AIRI: внешние сервисы он подключает каталогом коннекторов и серверами MCP, а модель берёт из LangChain — GigaChat, ChatGPT или другую.
Слой функций готовится. Ядро — перевод каталога в формат функций, разбор вызовов, защита от повторов — написано и покрыто тестами. Транспорт для этих клиентов и адаптеры под конкретные платформы пока нет: контракты вызова функций у провайдеров меняются, и писать их по памяти мы не станем. Нужна конкретная платформа — напишите в поддержку, мы уточним контракт и вернёмся с примером.
Как это устроено
Инструмент описан один раз: имя, параметры, область действия, нужно ли подтверждение. Адаптер переводит описание в форму, понятную конкретному клиенту, а проверки, защита от дублей, подтверждения и журнал остаются общими. Иначе поведение начнёт различаться, и документация станет врать одной из половин.
flowchart LR
core["Каталог инструментов:<br/>проверки, дубли,<br/>подтверждения, журнал"]
mcp["Нативный MCP<br/>stdio и HTTP"]
fn["Формат функций<br/>вызов функций у модели,<br/>свои SDK"]
tag["Разбор из текста<br/>локальные модели"]
api["API Инвойсбокс"]
core --> mcp
core --> fn
core --> tag
core --> apiЧто меняется в описании инструмента
- Схема сводится к общему подмножеству. Плоские объекты, строки, числа, перечисления и массивы объектов — без
anyOfиoneOf: часть платформ отбрасывает такое поле вместе со значением. Пара «единица измерения или её код» превращается в два необязательных поля, а согласованность проверяет сервер. - Описания короче. У провайдеров есть предел длины описания функции и поля; сервер отдаёт сжатый вариант, сохраняя главное.
- Подтверждение только двухфазное. Метки «изменяет данные» в формате функций нет вовсе, и попросить человека посреди вызова платформа не умеет. Поэтому первый вызов возвращает сводку и одноразовый токен, а второй исполняет — то же, что в безопасности.
- Описания по-русски. Русскоязычным моделям описания отдаются на русском: на переводе теряются «счёт-фактура» и «УПД», и модель начинает путать их с инвойсом.
Как выглядит функция
{
"name": "create_order",
"description": "Выставляет счёт покупателю и возвращает ссылку на оплату. Покупателем может быть организация, ИП или физлицо. Подтверждение в два шага: первый вызов возвращает сводку и confirmation_token, второй исполняет.",
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["description", "customer", "basket_items", "amount", "vat_amount", "currency_id", "expiration_date"],
"properties": {
"description": { "type": "string" },
"amount": { "type": "string", "description": "сумма в копейках строкой: 12200 = 122,00 ₽" },
"vat_amount": { "type": "string" },
"currency_id": { "type": "string", "enum": ["RUB", "USD", "EUR", "GBP", "CNY"] },
"customer": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["legal", "private"] },
"name": { "type": "string" },
"vat_number": { "type": "string" }
}
},
"confirmation_token": { "type": "string" }
}
}
}Локальные модели, которые вызывают инструмент разметкой
Модели семейства Hermes и родственные вставляют вызов в текст ответа, а сервер вывода разбирает его. Разбор ненадёжен, поэтому сервер отклоняет невалидное вместо того, чтобы дополнять догадками.
<tool_call>
{"name": "get_order", "arguments": {"order_id": "01771534-1a57-f184-dee3-ebeb91dded75"}}
</tool_call>- Невалидный JSON, выдуманное имя функции и аргументы массивом отклоняются с причиной.
- Два одинаковых вызова в одном ответе — реальное поведение части моделей — сводятся в один: иначе получился бы второй счёт.
- В контуре без авторизации и ролей права по умолчанию — только чтение. Запись включается явно, и ответственность за действия ассистента остаётся на владельце контура: сервер не отличает «модель ошиблась» от «оператор попросил» — кроме подтверждений и потолков.
Что ломается на своём узле — и как это лечится
Проверено на своём узле с моделью в августе 2026. Причина каждый раз в сервере вывода, поэтому то же самое встретит любого, кто подключает MCP к модели у себя.
- Схема с шаблоном во вложенном поле роняет вызов инструментов. Серверы вывода на llama.cpp строят по схеме грамматику. Шаблон (
pattern) у поля верхнего уровня они принимают, а тот же шаблон внутри объекта или элемента массива отвечает400 failed to parse grammar— причём на весь набор инструментов сразу, и по отказу не видно, чья схема виновата. Лечение: снимать шаблоны перед отправкой модели, а требование к формату переносить в описание поля. Проверять — подавая инструменты по одному. - А вот
formatснимать нельзя. Без него модель отправляет дату вместо метки времени и получает отказ по кругу: по формату она понимает, чего требует поле. - Ссылки внутри схемы (
$ref) понимают не все. Указатели вида#/properties/…, которыми генераторы схем убирают повторы, разбирает не каждый сервер вывода. Надёжнее раскрыть их в самодостаточную схему. - Рассуждение вслух съедает ответ. У модели с размышлением весь отведённый запас может уйти в рассуждение, и вместо ответа приходит пустота. Давайте запас с избытком, а для показа рассуждение лучше выключить средствами своего сервера вывода.
- Один слот и сон по простою. Локальный сервер часто держит один запрос за раз и выгружает модель из памяти по таймауту: первый вызов после простоя ждёт загрузки. Показывайте это состояние человеку отдельно от «недоступно», иначе диалог выглядит сломанным.
- Дважды один отказ — это круг. Модель правит не то поле и повторяет вызов. Ставьте предел на шаги и останавливайтесь на втором одинаковом отказе, пересказав причину человеку.
- Модель может не знать сегодняшнюю дату и поставить счёту вчерашний срок. Дату и требуемый формат (ISO 8601 со временем и зоной) называйте в системной подсказке.
- Ставка НДС — текущая. Модель охотно берёт код с прежней ставкой 20 %, а контур отвечает
vat_not_found. Действующая ставка — 22 %, коды — в справочнике.
Чего мы не поддерживаем
- Прямое подключение модели к API Инвойсбокс без нашего слоя: тогда исчезают защита от дублей, подтверждения, потолки и журнал — то есть всё, что делает канал безопасным.
- Автоматическое исполнение денежной операции «агентом без человека» — ни в одном протоколе, даже если платформа это позволяет.
- Обещание совместимости с платформой, на которой мы не прогнали сквозной тест на живом ключе.