В наше время тяжело представить разработку цифровых продуктов, в которые хоть в какой-то степени не включили так называемый ИИ на больших языковых моделях (LLM). И я вовсе не против, но у меня вызывают вопросы подходы разработчиков к способам внедрения интеллектуальных инструментов в свои продукты.
Думаю, абсолютное большинство оптимальным способом внедрения интеллекта в продукт выбрали использование проприетарных моделей через API, с добавлением кастомного функционала через вызовы MCP серверов. Кажется, это уже даже стало стандартом, и в этом я вижу проблему.
Давайте кратко разберем схему работы какого-то нашего приложения с официальным LLM-клиентом (например, OpenAI) + MCP:
наш код получает запрос пользователя в свободной форме
подмешивается описание инструментов, доступных через MCP сервер
запрос улетает в LLM API
клиент парсит JSON из ответа модели
вызывает MCP-сервер
отправляет результат обратно модели
и только сейчас, в лучшем случае, получает финальный текст ответа, который можно отдать пользователю
Вероятно, суть моих претензий видна уже на этом моменте, но давайте продолжим. Как выглядит минимальное описание инструментов? Вот так:
TOOLS = [
{
"name": "mongo_find",
"description": "Find documents in MongoDB",
"parameters": {
"type": "object",
"properties": {
"collection": {"type": "string"},
"filter": {"type": "object"},
},
"required": ["collection", "filter"]
}
},
{
"name": "run_search",
"description": "Run a request for transportation prices between two points",
"parameters": {
"type": "object",
"properties": {
"departure_id": {"type": "string"},
"arrival_id": {"type": "string"},
"payload": {"type": "object"}
},
"required": ["departure_id", "arrival_id", "payload"]
}
}
]
И это только парочка. Системный промт будет выглядеть примерно так:
SYSTEM_PROMPT = f"""
You are an agent with access to external tools.
Available tools:
{TOOLS}
To call a tool, respond ONLY with JSON in the format:
{{
"tool": "",
"args": {{
... arguments ...
}}
}}
If no tool is needed, return:
{{ "tool": null }}
"""
Далее, к этой немаленькой пачке знаков будет добавлено еще два промпта: промежуточный ответ LLM и результат вызова MCP. Под капотом будет что-то типа:
messages.append({"role": "assistant", "content": first_response})
messages.append({
"role": "tool",
"content": json.dumps(tool_result),
"name": tool
})
Всё это вновь улетит к LLM, и только после обработки этой кучи информации мы получим от модели финальный ответ для пользователя. И это еще самый маленький цикл из возможных.
Так почему это плохо (дорого и неэффективно):
Каждая итерация удваивает или утраивает количество токенов.
OpenAI-клиент добавляет:
System prompt с описанием всех инструментов
User сообщение
Ответ модели
Tool вызов
Tool результат
Финальное сообщение
И всё это передается назад в модель в каждом раунде.
Даже если инструмент возвращает пустяк (например, один документ Mongo), SDK отправит всё это снова в контекст.
Если MCP звонков несколько подряд, токены в контексте растут экспоненциально. Это увеличение расходов и ухудшение результата.
Модель «захламляется» историей инструментальных вызовов.
LLM вообще не нужно помнить:
какие вызовы инструментов ты делал
какие аргументы ты использовал
какие промежуточные JSON приходили.
Но OpenAI клиент держит всё это в messages, и модель вынуждена:
держать это в контексте
оплачивать эти токены
учитывать шум при генерации
Систему легко перегрузить большим количеством tool_calls. А это опять деньги и проблемы с результатом.
Наше любимое – галюны. Модель начинает путаться.
Когда в контексте появляется длинная цепь:
system
user
assistant toolcall
tool response
assistant follow-up
еще один toolcall
еще один ответ
...
Модель перестает точно понимать, в каком «режиме» она сейчас работает: генерирует ли она текст пользователю, вызывает ли инструмент или принимает входные данные инструмента.
Ошибки, когда модель начинает смешивать текст и JSON, и прочее, появляются именно из-за «распухшего» контекста. В итоге результат или ниже ожидаемого, или его нет.
Вывод: официальные клиенты работают слишком расточительно, особенно если MCP-инструменты вызываются каскадно.
Этот подход удобен для конечного пользователя (в «Desktop»), но неприменим для высоконагруженных или оптимизированных серверных систем. Сойдет для вайбкодеров, иначе говоря, но не для настоящих проектов.
Что предлагаю я:
Вспоминать, как прогали деды, выжимая максимум из каждого килобайта скромного железа.
Не пользоваться готовыми решениями от всяких OpenAI, сколько бы вам не рассказывали, что это "стандарт", а делать хардкор самим.
Запомнить требования к системе нормального человека:
предсказуемое поведение
малые токены
строгая детерминированность
минимальные задержки
простая отладка.
В своих проектах я эти требования чту, как священные писания, поэтому расход токенов в них может рассмешить, а результат легко прогнозировать.
Главное, контролировать логику и изолировать LLM от лишних данных. Например:
Дали модели описание инструментов.
Получили только один JSON от модели: {"tool": "...", "args": {...}}
Вызвали инструмент без LLM.
Вернули пользователю или обработали, построили следующую итерацию...
Если нужно, сделали еще один запрос модели, но уже минимальный и чистый.
Да, это уже не MCP архитектура, а функциональные вызовы, но в этом и есть преимущество. Вы не переусложняете процесс, контролируете его, повышаете шанс корректной работы даже слабеньких моделей, и не надеетесь на разработчиков OpenAI. Ну и на токенах экономите, в конце концов.
Комментарии (4)

gmtd
03.12.2025 21:29Я написал свой собственный mcp-сервер для работы с иконками в своём проекте (показать какие иконки есть, с поиском).
Замерил в курсоре расход токенов при работе с этим mcp-сервером и без на одном и том же запросе. Без - в 6-8 раз больше.
Это практика супротив вашей теории. Не говоря уже о том что нет глюков, и всё быстрее и надёжнее происходит.

it_police Автор
03.12.2025 21:29Да, стоило оговориться, что я не противник этого подхода, если он обоснован и не превращает работу с нейронкой в бесконечный цикл вызовов модели и инструментов. К сожалению, не очень понял, как в вашем случае курсор выполняет те же действия без инструментов, но вероятно это тот случай, когда все сделано прямыми руками и нужными средствами.
Akuma
Обычно кешированные запросы стоят раз в 10 дешевле. Так что в целом пофиг получается.
На счет того, что не нужно это всё в контексте - спорно. Модель может понять что она делал и продолжать, например использовать разные поисковые запросы и тп.
it_police Автор
Но может и не понять, из-за перегруза контекста. Меньше контекста- выше точность ответа и меньше шансов поймать галюна.