Главная / Статьи

Проектирование поверхностей инструментов с эффективным использованием токенов для серверов 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, модели обычно приходится обрабатывать:

  • названия инструментов
  • описания
  • шаблоны входных данных
  • параметры
  • типы значений
  • ограничения
  • иногда дополнительные метаданные

Представьте себе агента, подключенного к 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>;

Этот подход дает три практических преимущества:

  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. Объединение всего вместе не всегда лучше

Здесь существует компромисс, который стоит отметить.

Представьте себе один инструмент, предоставляющий 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);
        }
      }
    );
    

    Сам код остается довольно простым. Настоящая работа по проектированию заключается в ответах на следующие вопросы:

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