Головна / Статті

Розробка ефективних з точки зору токенів інтерфейсів інструментів для серверів MCP з агентами

Дізнайтеся, як об’єднати десятки визначень інструментів MCP у кілька інструментів, орієнтованих на конкретну сферу та працюючих за певними діями, не втрачаючи при цьому жодної з базових функцій.

Видавець: REACTAPP.TOP19 хв читання4404 слів

Джерела: **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>;

Цей підхід надає вам три практичні переваги:

  1. Один інструмент MCP.
  2. Чітке маршрутизування на основі поля дії.
  3. Сувора перевірка, яка застосовується під час виконання.

Загальна структура архітектури виглядає так:

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);
        }
      }
    );
    

    Сам код залишається досить простим. Справжня робота з проектуванням полягає у відповідях на такі запитання:

    1. Які операції логічно групуються разом?
    2. Яку конвенцію найменування слід дотримуватися для дій?
    3. Що має бути в описі інструменту?
    4. Які параметри є обов’язковими для кожної дії?
    5. Які дії потребують додаткових перевірок дозволів?
    6. Як слід послідовно формувати відповіді?
    7. Як слід логувати та відстежувати кожну дію?

    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, скільки у створенні найменшого та найзручнішого інтерфейсу, необхідного агенту для виконання завдань.