Проектирование MCP-инструментов: практические подходы и компромиссы
Когда инструменты Model Context Protocol (MCP) работают хуже ожидаемого, причина редко в самом протоколе — чаще проблема в проектировании инструмента. Многие команды начинают с того, что просто выставляют существующий API как есть и надеются, что агент разберётся сам. Для простых сценариев это может сработать. Но часто — нет.
Инструменты нужно проектировать с учётом того, как работают большие языковые модели (LLM) и агентные системы. Без этого возрастает риск неудачных вызовов, неверных значений параметров и повторных попыток, которые расходуют контекст и ухудшают производительность. В этом материале мы показываем, где MCP-дизайн инструментов идёт не так, и как исправить это с помощью практических подходов context engineering.
За большинством таких сбоев стоят две проблемы. Первая — bloat. Определения инструментов загружаются в контекст LLM при каждом вызове, независимо от того, используется инструмент или нет. Несколько подключённых MCP-серверов могут съесть значительную часть контекста ещё до того, как пользователь задал первый вопрос. По мере заполнения контекста способность LLM к рассуждению может снижаться, и сессия становится менее продуктивной.
Вторая — confusion. По мере ухудшения рассуждения LLM делает худший выбор, вызывает не тот инструмент и выбирает неверные параметры. Повторные попытки только усугубляют проблему, ещё больше раздувая контекст. Семантическое сходство между инструментами, слишком большой выбор и неоднозначные названия тоже усиливают confusion. Распространённое решение — сделать описания инструментов яснее, добавить сопоставления с естественным языком и примеры использования. Это действительно помогает уменьшить confusion. Но всё, что вы добавляете, может усилить bloat и тем самым усугубить именно ту проблему, которую вы пытаетесь решить.
Такой паттерн встречается часто. Агент делает неожиданный выбор, контекст заполняется раньше, чем вы рассчитывали, и сессия быстро становится менее полезной. Борьба с bloat и confusion — это задача context engineering. Context engineering определяет, что LLM видит и когда именно оно это видит, чтобы модель выдавала лучшие результаты. Улучшение одного или обоих параметров — это сложный баланс.
В следующих разделах рассматриваются подходы и компромиссы для решения этих проблем. Чтобы сделать их нагляднее, мы собрали несколько примеров, которые через MCP протокол обращаются к имитируемому API поиска K-12-контента. Вы сможете запустить их локально и взаимодействовать с ними через Kiro CLI, чтобы самостоятельно сравнить различия.
Подходы и компромиссы
Предложенные нами подходы работают на уровне инструмента. Одни меняют то, что LLM видит, или момент, когда оно это видит. Другие перестраивают сами инструменты. Применяя их, нужно понимать, как они влияют на bloat и confusion, чтобы найти правильный баланс между ними.
Описания и ответы
Естественный первый шаг к исправлению поведения MCP-инструментов — улучшить описания. Надёжный способ снизить confusion — яснее объяснить, что означают значения, как они сопоставляются с естественным языком и для чего вообще нужен инструмент. Но если зайти слишком далеко, вы быстро увеличите bloat. Объём контекста и стоимость могут стремительно расти, когда в клиент загружено много MCP-серверов.
На поведение влияет и то, что возвращает инструмент. Инструмент, который отдаёт 50 полей на результат, быстро заполняет контекст. Если для принятия решения достаточно 5 полей, по умолчанию возвращайте именно их и предлагайте отдельную опцию для детального просмотра. По данным исследований Anthropic, переход к on-demand-выводу подробностей сокращает число токенов ответа примерно на две трети according to Anthropic’s research.
Корректные сообщения об ошибках — ещё один способ повысить эффективность. Когда вызов инструмента не удаётся, полезная ошибка может направить следующую попытку. Сообщение «search requires 2 or more terms in query» точно подсказывает LLM, что нужно изменить. А ответ, который возвращает только «no results», ничего не даёт, и модель либо сдаётся, либо продолжает гадать.
Ограничения схемы
Если описания и корректные ошибки направляют LLM к правильным значениям, то ограничения схемы, такие как enums и значения по умолчанию, могут убрать догадки полностью. Рассмотрите следующее для устранения типичных проблем схемы:
- Переименовывайте параметры так, как домен может понимать LLM, а не так, как ваша база данных называет столбцы.
- Параметр
resource_classсо значениями вроде «Student Resource» или «Teacher Support» понятнее для LLM. Столбецcontent_bucketтребует знания внутренней конвенции.
- Параметр
- Задавайте значения по умолчанию для самых частых случаев, чтобы LLM нужно было указывать только то, что отличается.
- Ограничивайте поля с конечным набором значений через enums, чтобы сама схема подсказывала LLM допустимые варианты.
- Убирайте поля, которые используются редко или с которыми LLM плохо справляется.
- AWS Prescriptive Guidance for MCP Strategies рекомендует держать число параметров инструмента на уровне примерно восьми или меньше.
Перестройка инструментов и on-demand-контекст
Разбиение многофункционального инструмента на несколько специализированных инструментов даёт модели больше ясности и более гранулярные результаты. Один из примеров — инструмент обнаружения с lazy loading. Можно убрать сложные описания инструмента из постоянно загружаемого контекста и вынести их в отдельный инструмент для выборочного получения. LLM загружает этот контекст только тогда, когда он действительно нужен. Это делает контекст более лёгким и сфокусированным. Подходы Anthropic Tool Search Tool и Amazon Bedrock AgentCore Gateway показывают, как эти идеи работают в масштабе. Anthropic сообщает о сокращении токенов до 85% за счёт загрузки определений инструментов только когда они релевантны.
Skills — ещё один пример lazy loading, но уже на стороне клиента. Эти локальные файлы содержат полезный контекст для инструмента, однако читаются в контекст только когда становятся релевантны. Это снижает сложность реализации и распространения. Но нет гарантии, что skill загрузится вовремя или останется неизменным после установки.
Серверный вывод
Context engineering особенно сложен, когда вы не контролируете, какая модель будет интерпретировать ваши инструкции. И после развёртывания вы можете не контролировать, какая LLM вызывает ваши MCP-инструменты. Описание, настроенное под одну модель, может запутать другую. Полноценно протестировать все возможные модели, которые может использовать клиент, нереалистично.
Один из способов решить это — добавить инструмент интроспекции, который напрямую вызывает внешнюю LLM. Клиент использует естественный язык, чтобы запросить ваш инструмент интроспекции, и получает точечные инструкции по использованию других инструментов. Поскольку модель выбираете вы, для этого инструмента можно корректно выполнить prompt engineering и протестировать его на golden queries. Финальный вызов инструмента по-прежнему делает клиентская LLM, но ваша LLM интерпретирует намерение и возвращает правильные значения. Поскольку сценарий узкий, с этой задачей хорошо справляется более компактная и быстрая модель, а затраты остаются разумными в масштабе.
Агентные инструменты
Когда нужна точность и полный контроль, следующий логичный шаг — построить весь MCP-сервер вокруг собственного агента. Пример с интроспекцией обрабатывает один шаг интерпретации, а agentic tool обрабатывает всё взаимодействие целиком. Для клиента инструменты становятся естественно-языковыми конечными точками. Он формулирует запрос, а ваш агент делает остальное. Остальные описанные техники по-прежнему применимы. Все компромиссы вы берёте на себя полностью, но, как и в случае с интроспекцией, вы можете полностью настраивать поведение под выбранную модель.
В следующем разделе показан рабочий код, который применяет эти подходы к одному и тому же бэкенду и одинаковым тестовым запросам, чтобы можно было напрямую сравнить их поведение.
Практический разбор
Каждая из 6 версий оборачивает один и тот же имитируемый бэкенд поиска K-12-контента с разным дизайном MCP-инструмента. В бэкенде 14 фильтруемых полей (subject, grade, format, standards alignment, language, resource class и другие) с ограниченными словарями значений. Задача LLM — связать формулировку учителя с точными значениями, которые принимают эти поля. V1 начинает с прямого passthrough, который оставляет этот разрыв открытым. Каждая последующая версия закрывает его по-своему.
Требования
Для запуска примеров вам понадобятся:
- Python 3.10+.
- SQLite (входит в состав Python).
- Kiro (есть бесплатный тариф) или другой MCP-клиент.
- Аккаунт AWS с доступом к моделям Amazon Bedrock (только для V5 и V6): Amazon Nova 2 Lite и Anthropic’s Claude Sonnet 4.6.
Склонируйте репозиторий с примерами кода и следуйте README репозитория, где подробно описаны требования, установка и запуск серверов. Далее предполагается, что все шесть версий уже запущены.
Никакая инфраструктура в AWS не разворачивается. Весь код вы запускаете локально. Единственные затраты — это вызовы инференса в Amazon Bedrock и ваш кодовый клиент. Актуальные тарифы см. в ценах Amazon Bedrock.
Этот разбор предполагает, что вы используете Kiro и выполнили в репозитории команду python scripts/setup_agents.py.
Тестовые запросы
Эти запросы возвращают реальные результаты из демонстрационной базы данных. Попробуйте их в каждой версии:
- «Найдите мне викторину по дробям для моих семиклассников.»
- «Есть ли у вас уроки для обучения испанскому в средней школе?»
- «Мне нужен контент, соответствующий TEKS, для детей, работающих над делением в средней школе.»
- «Какие типы контента я могу искать?»
- «Могу ли я получить подробности по n-sc-1096?»
Во время тестов каждой версии:
- Смотрите, угадала ли LLM правильные значения фильтров с первого вызова.
- Отмечайте, сколько вызовов инструмента потребовалось.
- Проверяйте, не попала ли в ответ лишняя информация, которая модели не нужна.
- Следите, как быстро заполняется окно контекста. Проверяйте процент в Kiro или используйте
/context showдля детальной разбивки.

Чтобы переключаться между версиями, используйте /agent swap (например, /agent swap v1-passthrough). Очищайте контекст с помощью /clear между версиями, чтобы предыдущие результаты не влияли на следующие.
V1: Прямой passthrough
v1_passthrough.py
Первая версия — базовый анти-паттерн. Она напрямую exposes backend API. Определение инструмента содержит 14 параметров с внутренними именами вроде discipline, media_type, content_bucket и однострочный docstring: «Performs a global search for educational resources.» Не указаны допустимые значения, нет сопоставления с естественным языком, нет подсказок.
Определение инструмента небольшое, но LLM вообще не знает, какие значения допустимы. Попробуйте первый запрос. Модель может передать «quiz» в media_type, когда допустимое значение — «Assessment», или «math» в поле, которое ожидает «Math». Каждая неверная попытка запускает повтор, который расходует ещё больше контекста. Единственная подсказка о сбое — пустой результат. LLM остаётся только гадать, что исправить на следующей попытке. Низкая базовая стоимость обманчива, когда confusion раздувает реальную стоимость за счёт churn.

V2: Богатые описания
v2_better_descriptions.py
Та же структура, что и в V1, без какого-либо рефакторинга бэкенда. Теперь docstring перечисляет допустимые значения и синонимы для каждого поля. Например, discipline показывает «Valid: Math, Science, Literacy/ELA…», а media_type сопоставляет «‘quiz’/‘test’ → Assessment, ‘worksheet’ → Activity». Также fuzzy search (keyword) отделён от строгих фильтров (всё остальное). Три редко используемых параметра убраны, а сообщения об ошибках теперь подсказывают, какие фильтры добавить, вместо того чтобы возвращать пустой результат.
Попробуйте те же запросы. Точность сразу улучшается, потому что LLM видит допустимые значения и сопоставления синонимов. Но определение инструмента заметно увеличивается. Это и есть компромисс bloat. Каждый вызов платит за это, независимо от того, используется инструмент или нет. После пяти запросов сравните общий объём контекста с V1. Накладные расходы на каждый вызов выше, но меньшее число повторов часто делает итоговую стоимость ниже.

V3: Схема и значения по умолчанию
v3_rethought_schema.py
В этой версии параметры переименованы так, как LLM мыслит о домене, а значения ограничиваются самой схемой. Параметры переименованы: discipline становится subject, content_bucket становится resource_class. Для каждого поля с конечным набором значений используется тип Literal, который прямо перечисляет допустимые варианты в схеме. Разумные значения по умолчанию закрывают типичный случай: structure='Asset', resource_class='Student Resource', language='en'. Отдельный инструмент get_resource_detail отвечает за детализацию. Это реализует подход перестройки, когда у каждого инструмента есть своя понятная задача, а поисковые ответы остаются краткими.
Enums помогают предотвращать неверные значения на уровне протокола. Значения по умолчанию означают, что LLM указывает только то, что отличается. В ответе есть поле defaults_applied, чтобы LLM знала, что было отфильтровано неявно. Определение меньше, чем в V2, потому что ту работу, которую раньше выполняли длинные описания, теперь делают имена и enums. Запустите запросы и сравните точность с V2. Точность растёт, а контекст уменьшается.
V4: Lazy loading (перестройка)
v4_lazy_loading.py
Вместо того чтобы встраивать enums и подробные описания в поисковый инструмент, этот подход выносит их в отдельный инструмент. Поисковый инструмент хранит только короткие подсказки, например: «Subject area, e.g. ‘Math’, ‘Science’, ‘Literacy’». Инструмент get_taxonomy принимает список имён полей и возвращает допустимые значения и сопоставления с естественным языком только для тех полей, которые релевантны текущему запросу.
Определение поискового инструмента здесь самое лёгкое из всех. Обратите внимание на число вызовов. Для неоднозначных запросов LLM сначала вызывает get_taxonomy, чтобы подтвердить допустимые значения, а уже потом ищет. Для простых запросов, где подсказок достаточно, она может вообще пропустить taxonomy и искать напрямую. Часто встречающиеся значения покрываются подсказками без дополнительного round-trip, а полная taxonomy доступна для edge cases. Поскольку taxonomy загружается только когда нужна, все предыдущие взаимодействия в сессии проходят без этой стоимости контекста. В этом примере экономия умеренная, но в средах с множеством связанных инструментов и сложными схемами эффект быстро накапливается.

V5: Интроспекция через LLM
v5_llm_introspect.py
В этой версии добавлен инструмент introspect, работающий на Amazon Nova 2 Lite в Amazon Bedrock. Инструмент introspect_query принимает естественно-языковой вопрос учителя и возвращает рекомендуемые значения фильтров с обоснованием каждого выбора. Он интерпретирует «TEKS-aligned content for kids working on dividing in middle school» и возвращает рекомендованные фильтры: subject — «Math», классы 6–8, state_standard — «TX-TEKS», topic — «dividing,division».
Kiro по-прежнему делает финальный поисковый вызов. Поскольку introspect работает на выбранной вами модели, prompt engineering и тестирование становятся более надёжными. Интерпретация происходит на стороне сервера, поэтому ваш контекст остаётся лёгким. Компромисс — стоимость. Вы платите за серверный вызов, но результаты остаются стабильными независимо от того, какую модель использует Kiro. Переключайте модели через /model и сравнивайте V4 и V5. Без интроспекции более слабые модели дают нестабильные результаты. С интроспекцией результаты остаются стабильными.
V6: Агент как инструмент
v6/app/v6/main.py
Финальная версия открывает один MCP-инструмент, за которым стоит агент Strands Agents со своим system prompt и внутренними инструментами. Внешний интерфейс — это один инструмент с одним параметром: agentic_search_content(question: str). Ваш агент внутри сам выполняет поиск taxonomy, поиск, получение деталей и форматирование ответа, используя собственные инструменты, которые клиентская LLM не видит.
Попробуйте все 5 запросов и сравните использование клиентского контекста с другими версиями. Клиентская LLM делает минимум работы. Поведение остаётся стабильным независимо от того, какой клиент подключается, потому что рассуждение находится под контролем вашего агента. История диалога сохраняется между вызовами, поэтому последующие вопросы работают естественно. Компромисс — стоимость и задержка в обмен на прямой контроль над поведением и консистентностью. Переключайте модели через /model в Kiro и обратите внимание, что результаты остаются стабильными между моделями.
Компромиссы в сравнении
Каждая версия меняет один тип затрат на другой. Эта таблица показывает их рядом.
| Версия | Подход | Компромисс |
| V2 | Богатые описания | Точность выше, определение больше |
| V3 | Схема + значения по умолчанию | Точность выше, определение меньше |
| V4 | Перестройка + lazy loading | Самый лёгкий базовый уровень, но есть дополнительный round-trip |
| V5 | Server-side introspection | Лучше справляется с неоднозначностью, но вы платите за инференс |
| V6 | Agent-as-tool | Прямой контроль, самые высокие инфраструктурные затраты |
После запуска тестовых запросов во всех 6 версиях обратите внимание на закономерности. V2 — самый быстрый путь к лучшим результатам без рефакторинга. У V4 самый лёгкий базовый контекст. V5 справляется с неоднозначными запросами, которые другие версии пропускают. V6 даёт наибольший контроль при самых высоких инфраструктурных затратах. Ни одна версия не выигрывает по всем параметрам. Правильный выбор зависит от числа полей, стабильности словаря, допустимой задержки и того, насколько важно одинаковое поведение в разных клиентах.
Очистка
Все серверы работают локально. Чтобы остановить их, выполните scripts/stop_all.sh из корня репозитория. Подробности см. в README сопутствующего репозитория.
Заключение
Проблема не в MCP как таковом. Это ключевой протокол для agentic-решений. Проблема — в проектировании инструментов. Подходы в этом материале решают bloat и confusion через context engineering на уровне инструмента. Практический разбор показывает каждый подход на одном и том же сценарии, чтобы вы могли напрямую увидеть компромиссы и решить, что подходит именно вам.
Протокол продолжает развиваться. MCP 2026 Roadmap затрагивает масштабирование транспортного слоя, коммуникацию агентов и функции для enterprise-развёртываний.
Чтобы углубиться, изучите следующие ресурсы:
- MCP Strategies on AWS — покрывает более широкую архитектуру: какие MCP-паттерны использовать, когда подключать несколько серверов и как строить систему MCP за пределами одного инструмента.
- AWS MCP Server — управляемый MCP-сервер, который даёт AI coding agents доступ к AWS API, поиску документации и отобранным agent skills через единое подключение. Посмотрите, как он использует on-demand загрузку skills, чтобы держать контекст лёгким.
- Strands Agents SDK — подробнее об agentic-подходе из V6. Создавайте многоходовые агенты с orchestration инструментов, памятью и тестируемым поведением.
- Amazon Bedrock AgentCore — разворачивайте MCP-сервер без управления инфраструктурой. Включает runtime hosting, gateway для обнаружения инструментов между несколькими серверами и постоянную память между сессиями.
Ещё больше примеров и паттернов MCP-кода:
- Open Source MCP Servers for AWS — 56 open source MCP-серверов, покрывающих документацию, инфраструктуру, AI/ML, данные, инструменты разработчика и многое другое. Выберите один и оцените его описания инструментов через рамку bloat и confusion из этого материала.
- Amazon Bedrock AgentCore Samples — руководства для старта, демонстрации функций, примеры use case, blueprints и воркшопы для AgentCore.
- Sample Serverless MCP Servers — reference implementations для размещения MCP-серверов на AWS Lambda и Amazon Elastic Container Service (Amazon ECS), включая stateless и stateful-паттерны, а также Strands agents на Lambda.
- Guidance for Vibe Coding with AWS MCP Servers — пример приложения для бронирования отеля, показывающий, как AI coding assistants используют AWS MCP-серверы для ускорения разработки с AgentCore.
Материал — перевод статьи с английского.
Оригинал: MCP tool design: Practical approaches and tradeoffs