Розробка ефективних з точки зору токенів інтерфейсів інструментів для серверів 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, модель зазвичай мусить обробляти:
- назви інструментів
- описи
- схеми вхідних даних
- параметри
- типи даних
- обмеження
- іноді додаткові метадані
Навіть якщо кожна окрема схема є компактною, їхня сукупна кількість може швидко зрости.
Крім того, модель тепер стикається з більшою кількістю варіантів прийняття рішень:
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. Об’єднання всього разом не завжди краще
Тут є компроміс, який варто зазначити.
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 discriminated union може створити:
{
"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, скільки у створенні найменшого та найзручнішого інтерфейсу, необхідного агенту для виконання завдань.