Проектирование поверхностей инструментов с эффективным использованием токенов для серверов MCP с агентами
Узнайте, как свести десятки определений инструментов MCP к нескольким инструментам, ориентированным на конкретные области и работающим по заданным сценариям, не теряя при этом никакой базовой функциональности.
Источники: **From 30 Tools to 3: Designing a Token-Efficient MCP Tool Surface**
Типичная проблема с современными агентскими системами заключается не в недостатке инструментов.
Проблема в том, что их слишком много.
По мере расширения возможностей агента становится обычной практикой подключать его к Jira, GitLab, Confluence, Sentry, Elasticsearch, Jaeger, различным базам данных, панелям мониторинга, внутренним API, пайплайнам развертывания и бесчисленному количеству других систем.
Каждая из этих интеграций обычно предоставляет длинный список операций:
Jira
├── searchIssues
├── getIssue
├── createIssue
├── updateIssue
├── addComment
├── transitionIssue
├── getTransitions
├── assignIssue
└── ...
GitLab
├── listProjects
├── getProject
├── listIssues
├── createIssue
├── updateIssue
├── listMergeRequests
├── getMergeRequest
├── createComment
└── ...
Confluence
├── searchPages
├── getPage
├── createPage
├── updatePage
└── ...
Не проходит и долго, как приходится поддерживать 30, 50 или даже сотни определений инструментов.
На первый взгляд это кажется проблемой чисто технических возможностей.
На самом деле это проблема **количества инструментов**.
Агенту не нужно меньше возможностей для выполнения действий.
Ему нужно меньше **верхнего уровня инструментов**, из которых можно выбирать.
В этом тексте рассматривается подход к сокращению обширных интерфейсов MCP или инструментов до небольшого числа инструментов, сфокусированных на конкретной области, при этом не теряется ни одна из базовых функций.
Основная идея проста:
Сокращайте интерфейс инструмента, а не набор его возможностей.
Вместо того чтобы предоставлять что-то вроде:
30 MCP tools
можно предоставлять:
3–5 domain tools
и использовать поле action для внутренней перенаправленности каждого вызова.
Таким образом, набор вроде:
jira_search
jira_get_issue
jira_create_issue
jira_update_issue
jira_add_comment
jira_transition_issue
...
превращается во что-то вроде:
jira({
action: "search",
...
})
jira({
action: "getIssue",
...
})
jira({
action: "createIssue",
...
})
Бэкенд сохраняет все первоначальные возможности.
Однако с точки зрения модели интерфейс инструмента становится значительно меньше.
1. Проблема больших интерфейсов инструментов
Сервер MCP — это не просто механизм выполнения.
Это также становится частью того, что модель должна удерживать в контексте.
Каждый раз, когда агент подключается к серверу MCP, модели обычно приходится обрабатывать:
- названия инструментов
- описания
- шаблоны входных данных
- параметры
- типы значений
- ограничения
- иногда дополнительные метаданные
Представьте себе агента, подключенного к 40 разным инструментам.
Даже если каждый отдельный шаблон компактен, их совокупный объем может быстро увеличиваться.
Кроме того, модель теперь сталкивается с более сложной задачей принятия решений:
User request
│
▼
Which tool?
│
┌───┼────┬────┬────┬────┐
▼ ▼ ▼ ▼ ▼ ▼
T1 T2 T3 T4 T5 ...
Ей необходимо различать множество операций, которые концептуально близки друг к другу.
Возьмем в пример:
jira_search_issues
jira_search_projects
jira_get_issue
jira_get_issue_comments
jira_get_issue_transitions
jira_get_issue_worklogs
Все они относятся к одной и той же концептуальной области.
Рассматривать каждую из них как полностью автономную функцию на высшем уровне не приносит особой пользы.
2. Основная идея: мультиплексация инструментов
Секрет заключается в добавлении промежуточного слоя — поля-дискриминатора:
{
"action": "search",
"query": "authentication bug"
}
Вместо того чтобы определять:
jira_search
вы выдаете лишь:
jira
Сам инструмент превращается в легкий маршрутизатор.
С точки зрения структуры это выглядит так:
jira
│
action field
│
┌──────────────┼──────────────┐
▼ ▼ ▼
search getIssue createIssue
│ │ │
▼ ▼ ▼
searchHandler issueHandler createHandler
Ключевой момент заключается в том, что инструмент — это не то же самое, что функциональность.
Инструмент — это просто внешний интерфейс.
Поле действия — это то, что на самом деле несет функциональность.
Результат:
30 capabilities
↓
3 domain-oriented tools
без потери каких-либо функций по пути.
3. Используйте границы домена, а не произвольную группировку
Распространенная ошибка — мышление в духе:
«У меня 30 инструментов, поэтому я соберу по 10 операций в каждый из них».
Это не то, ради чего это делается.
Группировка должна осуществляться на основе семантических доменов, а не произвольных размеров групп.
Например:
jira
├── search
├── issue
├── comment
├── transition
└── project
gitlab
├── project
├── issue
├── mergeRequest
└── pipeline
observability
├── search
├── trace
├── log
└── error
Правильная группировка будет различаться в зависимости от интеграции.
Для базы данных подойдёт что-то вроде:
db_tables
db_query
db_advanced
Это может хорошо сработать.
Для Jira, возможно, лучше использовать:
jira_issues
jira_projects
jira_search
Для GitLab подойдёт такая структура:
gitlab_repository
gitlab_issues
gitlab_mergeRequests
Это может быть более уместно.
Не существует фиксированного числа, которое подошло бы везде.
Цель — найти наименьшую структуру инструментов, которая всё ещё соблюдает чёткие, осмысленные семантические границы.
4. Пример с базой данных
Подключение к базе данных является хорошим примером, поскольку API баз данных обычно предоставляют длинный список отдельных операций.
Вместо того чтобы определять отдельные элементы, такие как:
listTables
getTableSchema
getSampleData
getTableSize
executeQuery
getDatabaseInfo
listRelationships
getIndexes
profileColumn
searchSchema
listProcedures
getTriggers
compareSchemas
их можно объединить в одну структуру:
db_tables
db_query
db_advanced
Первый из этих инструментов может опираться на схему вида:
const tablesSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("list"),
schema: schemaField,
}),
z.object({
action: z.literal("schema"),
tableName: z.string(),
schema: schemaField,
}),
z.object({
action: z.literal("sampleData"),
tableName: z.string(),
schema: schemaField,
rowCount: z.number().optional().default(10),
}),
z.object({
action: z.literal("size"),
tableName: z.string(),
schema: schemaField,
}),
]);
С точки зрения модели существует всего один инструмент:
db_tables
опирающийся на четко определенный набор возможных действий:
list
schema
sampleData
size
На уровне выполнения кода все равно происходит перенаправление к четырем разным обработчикам:
switch (args.action) {
case "list":
return handleListTables(args.schema);
case "schema":
return handleGetTableSchema(
args.tableName,
args.schema
);
case "sampleData":
return handleGetSampleData(
args.tableName,
args.schema,
args.rowCount
);
case "size":
return handleGetTableSize(
args.tableName,
args.schema
);
}
Такое разделение имеет важное значение:
Слияние происходит на уровне интерфейса MCP, а не внутри основной бизнес-логики.
Каждый внутренний обработчик остается независимым для тестирования и легким в обслуживании.
5. Zod Discriminated Unions — идеальный выбор
В кодовых базах на TypeScript функция z.discriminatedUnion() предоставляет простой способ моделирования этого паттерна.
Например:
const querySchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("execute"),
query: z.string(),
params: z.record(z.string()).optional(),
limit: z.number().optional().default(100),
}),
z.object({
action: z.literal("info"),
}),
]);
Отсюда тип можно получить напрямую:
type QueryInput = z.infer<typeof querySchema>;
Этот подход дает три практических преимущества:
- Один инструмент MCP.
- Явное маршрутизирование на основе поля действия.
- Строгая валидация, применяемая во время выполнения.
Общая структура архитектуры выглядит следующим образом:
MCP Tool
│
▼
Discriminated Union
│
┌─────┴─────┐
│ action │
└─────┬─────┘
│
┌────────┼────────┐
▼ ▼ ▼
Handler A Handler B Handler C
│ │ │
└────────┼────────┘
▼
Backend
Для модели гораздо проще навигировать среди такого количества инструментов, чем среди десятков не связанных между собой инструментов верхнего уровня.
6. Количество инструментов — это не количество возможностей
Это, пожалуй, самая важная идея этого подхода.
Предположим, у вас получается:
3 tools
13 actions
Это не означает, что исчезло десять возможностей. На самом деле у вас есть:
3 external interfaces
13 internal capabilities
Короче говоря:
Tool count ≠ capability count
Это различие становится всё более важным по мере масштабирования систем агентов. Набор инструментов крупной организации может выглядеть следующим образом:
Jira 20 operations
GitLab 25 operations
Confluence 15 operations
Sentry 8 operations
ELK 10 operations
Jaeger 6 operations
Database 20 operations
что может легко привести к более чем 100 отдельным операциям. Прямое предоставление каждой из них модели приводит к чрезмерно громоздкому интерфейсу инструмента. Вместо этого их можно объединить в нечто вроде:
jira
gitlab
confluence
sentry
observability
database
при этом общее количество основных операций остается неизменным.
7. Почему это может снизить использование токенов
Основная выгода заключается в уменьшении объема метаданных инструмента, которые должна обрабатывать модель. Вместо того чтобы перечислять:
Tool 1
Tool 2
Tool 3
Tool 4
...
Tool 30
вы представляете:
Tool A
Tool B
Tool C
Это может сократить:
- описания инструментов
- повторяющиеся метаданные параметров
- дублирующиеся семантические пояснения
- сложность выбора между инструментами верхнего уровня
- пространство контекста, занимаемое определениями инструментов
Тем не менее это не следует рассматривать как гарантированную пропорциональную экономию. Переход от:
30 tools → 3 tools
это не приводит к автоматическому переводу в:
90% lower token cost
потому что сами объединенные схемы могут увеличиваться в размере. Более точный способ сформулировать это:
Объединение нескольких операций в один инструмент, как правило, сокращает объем метаданных, которые должен прочитать агент, однако степень экономии токенов или времени ответа зависит от таких факторов, как размер объединенной схемы, степень подробности описаний полей, особенностей обработки определений инструментов со стороны поставщика моделей и конкретного способа управления вызовами инструментов в вашей рамке агента.
Эту нюансность стоит иметь в виду.
8. Объединение всего вместе не всегда лучше
Здесь существует компромисс, который стоит отметить.
Представьте себе один инструмент, предоставляющий 40 различных действий:
enterprise({
action: ...
})
с такими действиями, как:
createCustomer
deleteCustomer
searchInvoice
rotateCredentials
deployService
createRepository
getTrace
searchLogs
...
Это технически осуществимо.
Однако это приводит к плохой структуре проекта.
Модель теперь застряла при обработке одной огромной схемы.
Вы не решили первоначальную проблему, вы лишь перенесли её из:
30 tools
в:
1 giant tool
Лучшим подходом является консолидация по направлениям доменов.
Например:
customer
billing
repository
observability
deployment
Подходящей целью могут быть пять инструментов, а не один.
Это ведёт к основному принципу:
Сокращайте функционал инструмента, но делайте это осторожно.
9. Схема служит частью интерфейса агента
Когда один инструмент обеспечивает несколько функций, параметр action приобретает большое значение.
Слабая версия:
action: z.string()
Улучшение:
action: z.enum([
"search",
"get",
"create",
"update"
])
Самый надежный вариант, когда каждая операция требует отдельных параметров:
z.discriminatedUnion("action", [
searchSchema,
getSchema,
createSchema,
updateSchema,
])
Здесь выбранная операция и ее ожидаемые аргументы связаны с помощью настоящей типобезопасности.
Например:
action = "search"
→ query required
action = "get"
→ issueId requiredaction = "create"
→ title + description required
Это гораздо точнее, чем одна схема с размытыми типами, заполненная необязательными полями для каждой возможной операции.
10. Усложнения схем JSON Schema в рамках MCP
На практике существует дополнительная сложность.
Схемы инструментов MCP в конечном итоге преобразуются в схемы JSON Schema.
В зависимости от используемого конвертера дискриминированный союз Zod может генерировать:
{
"anyOf": [...]
}
или:
{
"oneOf": [...]
}
Некоторые части процесса обработки схем MCP предполагают, что корневой элемент схемы — это обычный объект.
Это предположение не всегда справедливо.
В хотя бы одной реальной реализации слой совместимости Zod внутри MCP SDK был построен на основе форм объектов и не справлялся корректно с дискриминированным союзом.
Решение заключается в добавлении шага нормализации для генерируемой схемы.
Концептуально это выглядит так:
Zod discriminated union
│
▼
JSON Schema
│
▼
MCP normalization
│
▼
MCP-compatible object schema
Архитектурный вывод заключается в следующем:
Измените форму схемы, которую вы показываете модели, не влияя на строгость вашего фактического валидатора.
Ваша исходная схема Zod должна оставаться единственным источником правды для валидации.
11. Две разные схемы: одна для модели, другая для времени выполнения
Это указывает на более широкое различие, которое стоит четко обозначить.
Здесь на самом деле выполняются две отдельные задачи:
LLM-facing representation
│
▼
Tool selection and argument generation
Runtime representation
│
▼
Validation and execution
Схема, которая показывается LLM, должна быть:
- легкой для чтения
Схема, используемая во время выполнения, должна быть:
- строгой
- безопасной
- предсказуемой
- окончательной
Эти два вида представлений не обязаны полностью совпадать. Упрощённая структура объекта может помочь модели легче выбрать правильное действие, в то время как основанное на различениях объединение продолжает обеспечивать проверку в фоновом режиме.
Это можно обобщить в более широкое правило для систем агентов:
Настраивайте схемы с учётом того, как их читают модели, но никогда не полагайтесь на схему, предназначенную для моделей, как на единственный защитный механизм.
12. Описания приобретают большую значимость после слияния инструментов
При наличии 30 отдельных инструментов каждый из них может позволить себе узкое, конкретное описание.
При наличии 3 инструментов описание должно отражать весь спектр возможных действий.
Например:
Unified tool for advanced database analysis.
ACTIONS:
relationships
List foreign key relationships.
indexes
Get indexes for a table.
profileColumn
Analyze nullability, cardinality and top values.
searchSchema
Search tables and columns.
listProcedures
List stored procedures.
triggers
Get triggers.
compareSchemas
Compare two table definitions.
Это не просто документация для людей.
Она выполняет функцию части механизма, с помощью которого модель направляет свои запросы.
Полезный способ понимания этого:
Tool name
+
Description
+
Action enum
+
Parameter schema
↓
Agent routing signal
Поэтому объединение инструментов должно побудить вас писать более тщательные описания, а не меньше их.
13. Этот паттерн не ограничивается базами данных
Тот же подход приносит ещё большую пользу, когда его применяют в более широком наборе инженерных систем.
Jira
Вместо того чтобы предоставлять:
searchIssues
getIssue
createIssue
updateIssue
addComment
transitionIssue
assignIssue
getTransitions
предоставлять:
jira_issues
с набором действий вроде:
search
get
create
update
comment
transition
assign
GitLab
Вместо того чтобы использовать отдельные инструменты для репозиториев, задач, запросов на слияние и пайплайнов:
gitlab_repository
gitlab_issues
gitlab_mergeRequests
gitlab_pipelines
Их можно объединить в одну точку входа, причем у каждой будет свой набор действий.
Confluence / Wiki
Вместо того чтобы распределять операции по нескольким инструментам:
searchPages
getPage
createPage
updatePage
deletePage
getChildren
объединить их в один:
wiki
с использованием списка действий, например:
search
get
create
update
children
Sentry
Здесь также подходит аналогичная структура:
sentry
с поддержкой:
searchIssues
getIssue
events
releases
projects
ELK
Та же идея применима к поиску логов:
logs
с:
search
aggregate
fields
indices
Jaeger
А для дистрибуированного трейсинга:
tracing
с:
search
getTrace
services
operations
Ни один из этих примеров не зависит от конкретного выбора названий. Важен основной архитектурный подход, а не точные метки для каждого действия.
14. Можно использовать существующие серверы MCP вместо их пересоздания
Одним из наиболее практических преимуществ такого дизайна является то, что редко возникает необходимость заново реализовывать интеграцию с нуля. Для многих распространенных платформ уже существуют готовые серверы MCP. Однако их стандартный набор инструментов зачастую не настроен с учетом потребностей конкретного агента.
Вместо того чтобы подключать агента напрямую к обширному набору инструментов:
Agent
│
├── 20 Jira tools
├── 20 GitLab tools
└── 15 Confluence tools
можно вставить тонкий, специально созданный слой посредине:
Agent
│
Custom MCP Layer
│
┌──────────┼──────────┐
▼ ▼ ▼
Jira GitLab Confluence
│ │ │
existing existing existing
APIs MCP MCP
Этот пользовательский слой выступает в роли фасада для агента. Он особенно полезен, когда базовая интеграция функционально работоспособна, но предоставляет гораздо более детальные операции, чем агенту необходимо рассматривать напрямую.
15. Использование MCP в качестве слоя адаптера
Это приводит к более общей архитектурной концепции. Традиционная схема выглядит следующим образом:
Agent → MCP → Service
Персонализированная настройка выглядит следующим образом:
Agent
↓
Agent-oriented MCP facade
↓
Service-specific MCP/API
↓
Service
Промежуточный фасад может брать на себя такие задачи, как:
- консолидация инструментов
- проверка прав доступа
- нормализация входных данных
- нормализация выходных данных
- логирование
- отслеживание действий
- кэширование
- ограничение частоты запросов
- применение правил, специфичных для организации
С такой точки зрения слой MCP перестает быть просто механизмом передачи данных и становится настоящим интерфейсным слоем для агента.
16. Контроль доступа по-прежнему находится на уровне действий
Существует важный аспект безопасности, на который стоит обратить внимание. Если вы объедините:
jira_create
jira_update
jira_delete
в один вызов в таком виде:
jira({
action: ...
})
вы не можете просто предоставить общее разрешение вроде:
permission: jira
И предположим, что каждая основная операция несет одинаковый уровень риска. Проверки разрешений должны осуществляться на уровне отдельных действий, например:
jira.search → allowed
jira.get → allowed
jira.create → allowed
jira.update → approval required
jira.delete → denied
Консолидированный инструмент представляет собой лишь упрощение на уровне интерфейса — он никогда не должен превращаться в удобный способ тайного предоставления более широких прав, чем предусмотрено.
17. Журналы должны фиксировать действие, а не только инструмент
После объединения инструментов запись только названия инструмента в журнале теряет практическую ценность. Слабая запись в журнале выглядит так:
tool = jira
Лучшая запись включает описание действия:
{
"tool": "jira",
"action": "search",
"duration_ms": 142
}
А еще более подробная запись добавляет дополнительные сведения об исполнении:
{
"tool": "jira",
"action": "search",
"status": "success",
"duration_ms": 142,
"request_id": "...",
"agent_id": "...",
"user_id": "..."
}
Теперь реальной единицей исполнения является пара:
tool + action
а не инструмент в одиночку:
tool
Это различие имеет большое значение, когда вы пытаетесь отладить действия агента.
18. Держите основные обработчики отдельно
Консолидация должна происходить на границе, доступной для агента, а не внутри вашей реализации. Избегайте объединения всего в один огромный обработчик:
async function jira(args) {
// everything
}
Вместо этого направляйте запросы к отдельным функциям:
async function jira(args: JiraInput) {
switch (args.action) {
case "search":
return searchIssues(args);
case "get":
return getIssue(args);
case "create":
return createIssue(args);
case "update":
return updateIssue(args);
}
}
Каждый обработчик остается независимым и поддающимся тестированию:
jira()
│
├── searchIssues()
├── getIssue()
├── createIssue()
└── updateIssue()
Это позволяет сохранять ваш кодовый базис в работоспособном состоянии даже при сокращении количества внешних инструментов.
19. Практическое правило для определения того, что объединять
Хорошей гистерезисной оценкой того, принадлежат ли две операции к одному инструменту, является:
Способен бы инженер естественным образом объединить эти операции в одну функциональность?
Например, эти операции явно принадлежат друг к другу:
Jira search
Jira get issue
Jira update issue
Но эти — нет:
Jira update issue
Elasticsearch search logs
Create AWS infrastructure
Ещё одна полезная проверка:
Требуется ли для принятия решения между этими операциями обычно одна и та же контекстуальная информация?
Если ответ «да», то объединение их обычно оправдано. Если «нет», лучше сохранить их как отдельные инструменты.
20. Когда объединение инструментов — плохая идея
Иногда сохранение инструментов отдельно — просто правильный выбор.
Несвязанные домены
database
payments
infrastructure
email
не следует преобразовывать в что-то вроде:
enterprise({
action: ...
})
Смешивание несвязанных доменов в один диспетчер только потому, что это возможно, не делает интерфейс более целостным.
Наборы действий, слишком большие по размеру
Если один объединенный инструмент будет охватывать пятьдесят различных действий, получившаяся структура может стать чрезмерно громоздкой, что затруднит работу модели с ней и свело на нет цель объединения.
Операции с повышенными привилегиями
Действия вроде:
deleteProductionData
rotateCredentials
deployProduction
могут требовать собственных границ разрешений, даже если технически они находятся в той же области, что и операции с более низким уровнем риска.
Операции с разными гарантиями жизненного цикла
Только-чтение вызовы и разрушительные вызовы иногда лучше реализовывать как отдельные интерфейсы:
jira_read
jira_write
чем объединять их в один чрезмерно большой инструмент.
Как и раньше, не существует фиксированной формулы — это зависит от системы.
21. Меньше инструментов — не конечная цель
Цель не в том, чтобы сократить всё до:
30 → 1
Настоящая цель — это:
30 → smallest useful semantic surface
В одной системе правильное значение может выглядеть так:
30 → 3
В другой — так:
30 → 7
А в ещё одной — так:
30 → 12
На самом деле вы оптимизируете следующее:
minimize unnecessary tool metadata
не жертвуя при этом:
semantic clarity
validation
security
maintainability
22. Форма архитектуры с возможностью повторного использования
Масштабируемая структура обычно соответствует этой форме:
Agent
│
▼
┌──────────────────┐
│ Agent MCP Layer │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
jira gitlab wiki
│ │ │
┌────┼────┐ ┌────┼────┐ ┌────┼────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
search get create issues MR pipeline search get update
│ │ │
└──────────────┼──────────────┘
▼
Existing APIs/MCPs
Модель взаимодействует с небольшой, упрощённой интерфейсной поверхностью. Под ней слой интеграции по-прежнему имеет доступ ко всему спектру операций.
23. Переосмысление назначения MCP
Распространённая когнитивная модель MCP выглядит так:
"Превратить каждый вызов API в отдельный инструмент."
Это хорошая отправная точка.
Но для систем, работающих в производстве, более полезным становится следующий вопрос:
Что на самом деле должна видеть модель?
Эти два подхода к структурированию не всегда приводят к одинаковому дизайну.
Рассмотрим REST API, предоставляющий:
GET /issues
GET /issues/:id
POST /issues
PATCH /issues/:id
POST /issues/:id/comments
POST /issues/:id/transition
Нет необходимости создавать отдельный инструмент для каждого HTTP-маршрута. Слой MCP может выступать в роли семантического обёртки вокруг этих конечных точек — примерно так же, как при проектировании API учитываются потребности конкретного пользователя, а не просто предоставляется доступ к основному сервису или схеме базы данных напрямую.
24. Рассмотрение дизайна инструментов как проблемы UX агента
В классическом проектировании API задаётся вопрос:
Какие конечные точки существуют?
При проектировании для агента задаётся другой вопрос:
Какое решение на самом деле пытается принять модель?
Инженер, интегрирующийся с API, может обратиться к:
getIssue
getIssueComments
getIssueTransitions
getIssueWorklogs
поскольку каждый вызов чётко соответствует одной операции.
С другой стороны, агент может лучше работать с:
jira_issue({
action: "get",
...
})
в сочетании с:
jira_issue({
action: "comments",
...
})
потому что сначала выбирается домен, затем определяется конкретная операция — это двухэтапный процесс маршрутизации:
Domain
↓
Action
↓
Arguments
вместо того чтобы заставлять модель решать:
One large global tool-selection problem
25. Минимальный шаблон, который можно повторно использовать
Этот подход легко переносится в TypeScript:
const toolSchema = z.discriminatedUnion("action", [
searchSchema,
getSchema,
createSchema,
updateSchema,
]);
server.registerTool(
"jira",
{
description: "...",
inputSchema: toolSchema,
},
async (args) => {
switch (args.action) {
case "search":
return search(args);
case "get":
return get(args);
case "create":
return create(args);
case "update":
return update(args);
}
}
);
Сам код остается довольно простым. Настоящая работа по проектированию заключается в ответах на следующие вопросы:
- Какие операции логически связаны между собой?
- Какую конвенцию именования следует соблюдать для действий?
- Что должно входить в описание инструмента?
- Какие параметры являются обязательными для каждого действия?
- Какие действия требуют дополнительной проверки прав?
- Как следует единообразно форматировать ответы?
- Как следует логировать и отслеживать каждое действие?
26. Отслеживайте эффективность с помощью реальных цифр
Интеграция инструментов не должна основываться на вере — её необходимо подтвердить данными.
До внесения изменений вы можете записать что-то вроде:
Tools: 32
Tool schema tokens: X
Average tool-selection latency: Y
Tool-selection errors: Z
После внедрения мультиплексированной архитектуры снова фиксируются те же показатели:
Tools: 5
Tool schema tokens: X'
Average tool-selection latency: Y'
Tool-selection errors: Z'
Самые информативные показатели для отслеживания включают:
- количество используемых инструментов
- общий размер схем
- количество токенов, потребляемых запросом или контекстом
- точность выбора подходящего инструмента агентом
- частота отправки некорректных запросов к инструментам
- частота сбоев при проверке аргументов
- время отклика
- частота успешного выполнения задач
- то, завершил ли агент всю работу
Цель не в простом преследовании:
fewer tools
а в оптимизации с учётом:
better agent performance per unit of context
27. Более общий шаблон для API, предназначенных для агентов
Эта идея применима гораздо дальше, чем только к MCP.
Вы увидите, что тот же принцип актуален для:
- вызова функций
- определений инструментов в стиле OpenAI
- SDK
- собственных сред выполнения агентов
- механизмов обработки рабочих процессов
- архитектур плагинов
- адаптеров REST к агентам
- мостов GraphQL к агентам
В своей сути шаблон выглядит следующим образом:
Traditional API
↓
Many low-level operations
↓
Agent-oriented facade
↓
Small semantic tool surface
↓
Action routing
↓
Strict execution layer
Это не трюк, специфичный для баз данных.
Это общий шаблон для проектирования интерфейсов, которые используют агенты.
28. Основной принцип
Ментальная модель, которую следует придерживаться, проста:
┌──────────────────────┐
│ 30 APIs │
│ capabilities │
└──────────┬───────────┘
│
Consolidation
│
▼
┌──────────────────────┐
│ 3–5 tools │
│ semantic domains │
└──────────┬───────────┘
│
Action routing
│
▼
┌──────────────────────┐
│ 30 handlers │
│ original abilities │
└──────────────────────┘
Значимое изменение заключается не в следующем:
30 capabilities → 3 capabilities
Вместо этого оно заключается в следующем:
30 exposed tools → 3 exposed interfaces
Ничего не меняется в том, что может делать система.
Изменяется лишь то, какая часть этой поверхности должна обрабатываться моделью непосредственно.
Заключение
По мере того как агентные системы встраиваются во все больше компонентов вашей инфраструктуры, количество доступных им инструментов будет продолжать расти.
Системы вроде Jira, GitLab, Confluence, Sentry, ELK, Jaeger, различные базы данных, облачные провайдеры и внутренние сервисы могут предоставлять по десятки или сотни отдельных операций.
Передача каждой из этих операций модели как отдельного инструмента редко является правильной архитектурой.
Более эффективной стратегией является создание слоя инструментов для агентов поверх них:
Many low-level operations
↓
Semantic domain grouping
↓
Discriminated action schemas
↓
Small MCP tool surface
↓
Strict runtime validation
↓
Original capabilities
Руководящее правило заключается в следующем:
Сокращайте количество инструментов, о которых должна думать модель, а не количество функций, которые на самом деле предлагает ваша система.
Интеграция с тридцатью основными операциями не обязательно означает наличие тридцати инструментов, видимых для модели.
Возможно, потребуются всего три тщательно спроектированных интерфейса, каждый из которых имеет четко определенное пространство действий.
Это становится еще более ценным, когда вы перестаете рассматривать существующие серверы MCP как неизменные, недоступные для изменения контракты, а начинаете использовать их в качестве строительных блоков, которые можно обернуть, переформатировать и настроить в соответствии с реальными потребностями вашего агента.
В будущем хороший дизайн MCP может заключаться не столько в отображении каждой операции, поддерживаемой API, сколько в создании самого маленького и удобного интерфейса, необходимого агенту для выполнения его задач.