Недавно я решал две задачи. Личную и по работе. Личная, это настроить Nanabot, ИИ‑агента, который автоматизирует мою деятельность как программиста. По работе, это написание MCP‑сервера по управлению продуктами компании. И я столкнулся с проблемами. Если методов MCP очень много, то их подробные описания сильно раздувают контекст LLM. Если их прячешь за Code Mode MCP, то LLM иногда при поиске подходящего метода MCP не находит то, что ему нужно, или вообще не догадывается, что их надо поискать.

Про Code Mode MCP я узнал из новости о том, что Cloudflare столкнулась с проблемой чрезмерного расхода контекстного окна ИИ‑агентов. При традиционной схеме MCP‑клиент может передавать LLM описания всех подключенных инструментов. У Cloudflare более 2500 API‑методов, и, по их оценке, полное представление этих методов в виде MCP‑инструментов потребовало бы около 1,17 миллиона токенов. Cloudflare предложила решение: использовать Code Mode MCP, который вместо тысяч инструментов предоставляет агенту всего два: search() и execute().

Первый позволяет находить необходимые API‑методы и получать их сигнатуры по запросу, а второй позволяет выполнять JavaScript‑код, вызывающий эти методы. В показанной реализации один вызов execute() может объединить несколько вызовов MCP‑методов и вернуть итоговый результат, не обращаясь к LLM между каждой операцией. Это может экономить токены, но повторные поиски и загрузка сигнатур тоже расходуют их. Размер экономии зависит от объема каталога, частоты поисков и количества вызовов. В моем сценарии она вероятна, но для точной оценки нужны замеры на конкретных задачах.

Как эта технология работает на примере? Вы пишете ИИ‑агенту: «Открой example.com и сделай скриншот страницы». Ниже показан один вариант реализации агрегатора: поиск по массиву MCP‑инструментов tools. Это не универсальный синтаксис Code Mode. Например, Cloudflare search() ищет операции в OpenAPI‑спецификации. В этом агрегаторе search можно представить так:

return tools.filter(t =>
  t.name.includes("navigate") ||
  t.name.includes("screenshot")
);

Возвращается описание:

[
  {
    "name": "navigate_page",
    "description": "Go to a URL, or back, forward, or reload. Use project URL if not specified otherwise.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "pageId": {
          "type": "number",
          "description": "Targets a specific page by ID."
        },
        "type": {
          "type": "string",
          "enum": ["url", "back", "forward", "reload"],
          "description": "Navigate the page by URL, back or forward in history, or reload."
        },
        "url": {
          "type": "string",
          "description": "Target URL (only type=url)."
        },
        "ignoreCache": {
          "type": "boolean",
          "description": "Whether to ignore cache on reload."
        },
        "handleBeforeUnload": {
          "type": "string",
          "enum": ["accept", "dismiss"],
          "description": "Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept."
        },
        "initScript": {
          "type": "string",
          "description": "A JavaScript script to be executed on each new document before any other scripts for the next navigation."
        },
        "timeout": {
          "type": "integer",
          "description": "Maximum wait time in milliseconds. If set to 0, the default timeout will be used."
        }
      },
      "required": ["pageId"]
    }
  },
  {
    "name": "take_screenshot",
    "description": "Take a screenshot of the page or element.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "pageId": {
          "type": "number",
          "description": "Targets a specific page by ID."
        },
        "filePath": {
          "type": "string",
          "description": "The absolute path, or a path relative to the current working directory, to save the screenshot to instead of attaching it to the response."
        },
        "format": {
          "type": "string",
          "enum": ["png", "jpeg", "webp"],
          "default": "png",
          "description": "Type of format to save the screenshot as. Default is png."
        },
        "fullPage": {
          "type": "boolean",
          "description": "If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid."
        },
        "quality": {
          "type": "number",
          "minimum": 0,
          "maximum": 100,
          "description": "Compression quality for JPEG and WebP formats (0-100). Higher values mean better quality but larger file sizes. Ignored for PNG format."
        },
        "uid": {
          "type": "string",
          "description": "The uid of an element on the page from the page content snapshot. If omitted, takes a page screenshot."
        }
      },
      "required": ["pageId"]
    }
  }
]

В этой реализации описание метода возвращается по запросу, а не обязательно целиком загружается в контекст. Сигнатура показывает, что pageId обязателен для обоих методов. Для примера считаем, что нужное значение pageId уже получено заранее.

Далее вызывается execute:

await chrome_devtools.navigate_page({
  pageId: pageId,
  type: "url",
  url: "https://example.com"
});

const screenshot = await chrome_devtools.take_screenshot({
  pageId: pageId,
  format: "png"
});

return screenshot;

Заметьте: между navigate_page и take_screenshot нет нового запроса к LLM. Агрегатор Code Mode сам выполняет следующий оператор JavaScript, дождавшись результата первого. Думаю, выгоды очевидны.

Между тем Cloudflare рекомендуют использовать SKILL‑ы вместе с MCP. SKILL‑ы объясняют агенту, когда и какие возможности Cloudflare использовать. MCP обеспечивает доступ к операциям Cloudflare, а Code Mode предоставляет search() и execute() для поиска методов и запуска их через JavaScript.

В итоге я использовал ту же архитектуру. Я спрятал большое число MCP под Code Mode. Теперь LLM видит всего лишь один MCP‑сервер, который умеет искать и запускать MCP‑методы. Но, как я говорил выше, иногда ИИ‑агент не находит подходящие методы или вообще не догадывается их поискать. Тут как раз приходят на помощь SKILL‑ы. В их описании я перечисляю, какие MCP и методы использовать. Но перечисляю я не полное описание, а краткое. Вот пример SKILL‑а, который баги правит:

---
name: bugfix-process
description: >
  Автоматическое исправление багов по номеру тикета.
  Используй, когда пользователь просит исправить баг,
  задачу или ошибку из системы тикетов.
---

# Bugfix Process

## Доступные MCP-серверы и методы

### ticket_mcp - система тикетов
- `get_ticket` - получить описание тикета.
- `get_comments` - получить все комментарии.
- `list_attachments` - получить список вложений.
- `download_attachment` - скачать вложение.
- `update_status` - изменить статус тикета.

### samba_mcp - файловый сервер
- `list_files` - найти связанные с тикетом файлы и логи.
- `download_file` - скачать файл с Samba.

### filesystem_mcp - файловая система
- `create_directory` - создать каталог.
- `write_file` - записать файл.
- `read_file` - прочитать файл.

### git_mcp - работа с Git
- `current_branch` - получить текущую ветку.
- `stage_files` - добавить файлы в индекс.
- `commit` - создать коммит.
- `show_commit_diff` - получить DIFF коммита.
- `push` - отправить изменения в удаленный репозиторий.

### codex_mcp - программирование
- `start_task` - запустить Codex с промптом.
- `wait_task` - дождаться завершения Codex.
- `get_task_result` - получить результат выполнения.

### jenkins_mcp - сборка проекта
- `trigger_build` - запустить Jenkins-сборку.

## Процесс исправления бага

1. Определи номер тикета, репозиторий, ветку и Jenkins-сборку из запроса пользователя.

2. Собери информацию о тикете:
   - Получи описание тикета и все комментарии.
   - Скачай все вложения и связанные логи с Samba.
   - Сохрани материалы в `tickets/TICKET_NUMBER/` выбранного репозитория.

3. Сделай Git-коммит только содержимого `tickets/TICKET_NUMBER/`.

4. Запусти Codex и дождись завершения работы. Передай ему промпт:

   "Исправь баг, описанный в файлах каталога tickets/TICKET_NUMBER/. Изучи описание,
   комментарии, вложения и логи. Исправь причину ошибки. Выполни необходимые
   тесты и сообщи результат."

6. Создай отдельный Git-коммит с изменениями, которые внес Codex. Не 
   включай посторонние изменения.

7. Отправь коммит с исправлением в удаленный репозиторий, используя текущую
   ветку, если пользователь не указал другую.

8. Запусти Jenkins-сборку, указанную пользователем.

9. После завершения Jenkins проверь результат Codex, тестов и сборки,
   затем измени статус тикета:
   - Если ошибка исправлена, проверки прошли и Jenkins завершился успешно,
     установи `Resolve`.
   - Если исправление не удалось или его корректность не удалось подтвердить,
     установи `In Progress`.

10. Изучи DIFF коммита с исправлением и подготовь отчет по результатам:
   - Для статуса `Resolve`: `tickets/TICKET_NUMBER/result.fixed.md`.
   - Для статуса `In Progress`: `tickets/TICKET_NUMBER/result.in-progress.md`.

   Укажи в отчете проблему, внесенные изменения, результаты проверок и оставшиеся проблемы.
   Сохрани отчет и создай отдельный Git-коммит. 

11. Отправь коммит с отчетом в удаленный репозиторий.

12. В ответе пользователю укажи имя итогового файла и процитируй его содержимое.

Чтобы данный SKILL активировался и были найдены нужные MCP‑методы, достаточно пользователю написать такой промпт: «Исправь баг 731 в репозитории two‑server, ветка текущая, запусти сборку two_jar_java17». Вот что происходит дальше:

  1. В контексте ИИ‑агента есть краткие описания доступных SKILL‑ов. Одно из них указывает, что bugfix-process применяется для исправления багов из системы тикетов.

  2. Агент читает подходящий SKILL и узнает последовательность действий, нужные MCP‑серверы и названия методов.

  3. Для задачи ему понадобятся ticket_mcp, samba_mcp, filesystem_mcp, git_mcp, codex_mcp и jenkins_mcp. SKILL дает их названия и краткое назначение, но полных сигнатур методов в контексте пока нет. Агенту нужно прочитать данные тикета, сохранить вложения, подготовить коммиты, поручить исправление Codex и проверить сборку Jenkins.

  4. Агент использует search(), чтобы получить описания и сигнатуры нужных методов. Способ поиска зависит от агрегатора: в примере выше поиск идет по массиву MCP‑инструментов, а Cloudflare ищет операции в OpenAPI‑спецификации. Агенту не приходится угадывать названия методов, нужные уже перечислены в SKILL. Например, в начале работы с тикетом агент ищет подходящие методы:

    return tools.filter(t =>
      t.name.includes("get_ticket") ||
      t.name.includes("get_comments") ||
      t.name.includes("list_attachments") ||
      t.name.includes("download_attachment")
    );

    Code Mode возвращает назначение операций, их параметры, типы данных и обязательные поля. Агент может подготовить вызов, не загружая заранее описания всего каталога.

    Получив сигнатуры, агент формирует JavaScript‑код и вызывает execute(). Например, одним сценарием можно получить описание тикета, комментарии и вложения, а затем скачать нужные файлы. Операции можно выполнять последовательно, а независимые запросы объединять через Promise.all(). JavaScript обрабатывает промежуточные результаты, а методы выполняются соответствующими MCP‑серверами. Отдельное обращение к LLM между каждым вызовом не требуется.

  5. Затем агент ищет на Samba связанные с тикетом файлы и логи, например логи приложения для ошибки № 731. Он скачивает найденные материалы и сохраняет их вместе с описанием, комментариями и вложениями в tickets/731/:

    two-server/
    └── tickets/
        └── 731/
            ├── ticket.md
            ├── comments.md
            ├── attachments/
            └── logs/

    Для создания каталогов и записи файлов используются методы filesystem_mcp. Большие логи и вложения остаются на диске, а не передаются целиком в контекст LLM. Codex сможет прочитать их по указанному пути и взять только сведения, нужные для исправления.

  6. Агент использует stage_files и commit из git_mcp, чтобы добавить в Git‑индекс только содержимое tickets/731/ и создать коммит с собранными материалами. Так посторонние изменения не попадают в коммит, а исходные материалы остаются отделены от будущего исправления и отчета.

  7. Теперь начинается исправление бага. В codex_mcp для этого есть методы start_task, wait_task и get_task_result. Агент передает Codex путь к каталогу и промпт на исправление:

    Исправь баг, описанный в файлах каталога tickets/731/.
    Изучи описание, комментарии, вложения и логи.
    Исправь причину ошибки.
    Выполни необходимые тесты и сообщи результат.

    Codex читает материалы по пути, выполняет работу и возвращает результат. Основной агент дожидается завершения задачи, не передавая весь тикет и логи через собственный контекст. Так основной агент организует процесс, а непосредственное программирование делегирует Codex. Если интерфейс MCP и ограничения времени позволяют, ожидание Codex и получение результата можно объединить в одном вызове execute().

  8. После завершения Codex агент изучает DIFF изменений через show_commit_diff и проверяет результат. Затем создает отдельный коммит, отправляет его в текущую ветку через push и запускает Jenkins‑сборку two_jar_java17. Сборка начинается после успешной отправки, чтобы проверить именно эти изменения. Запуск выполняет метод trigger_build сервера jenkins_mcp.

  9. Когда Jenkins завершится, агент оценивает исправление, тесты Codex и результат сборки. Через ticket_mcp.update_status он устанавливает Resolve, если ошибка исправлена, тесты прошли и Jenkins завершился успешно. Если исправление не удалось или результат нельзя подтвердить, устанавливает In Progress.

  10. По итоговому статусу агент готовит отчет. Для Resolve он сохраняет его в tickets/731/result.fixed.md, а для In Progress — в tickets/731/result.in-progress.md. В отчете указаны проблема, изменения, результаты проверок Codex и Jenkins, а также оставшиеся вопросы. Его содержание сверяется с DIFF коммита и итогом сборки. Отчет добавляется отдельным коммитом и отправляется в ту же ветку.

  11. В ответе агент указывает имя итогового файла и приводит его содержимое. Так пользователь получает не просто сообщение «баг исправлен», а полноценный отчет о проделанной работе. Весь процесс от получения тикета до запуска Jenkins описан в одном SKILL, а нужные методы MCP выбираются по мере выполнения задачи.

И что в итоге получилось?

Мы разделили информацию, необходимую ИИ‑агенту, на три уровня:

  • Постоянный контекст. Содержит только краткие описания доступных SKILL‑ов и два инструмента Code Mode: search() и execute().

  • SKILL. Загружается, когда агент определяет, что он подходит для задачи. Содержит алгоритм работы, названия необходимых MCP‑серверов и методов, но не их полные сигнатуры.

  • Code Mode MCP. В описанной реализации по запросу находит полные описания конкретных методов и позволяет выполнять последовательность вызовов через JavaScript.

Получается своеобразная многоуровневая система поиска и выполнения инструментов.

Раньше у агента было два не очень хороших варианта.

Первый, загрузить тысячи инструментов в контекст и потратить огромное количество токенов на их описания.

Второй, спрятать инструменты за Code Mode, но надеяться, что LLM самостоятельно догадается, какие методы ей нужны и как их найти.

В этой схеме появляется третий вариант: SKILL подсказывает, что искать и как использовать, а Code Mode находит полные сигнатуры и выполняет операции.

Конечно, это не дает стопроцентной гарантии. LLM все еще может не выбрать нужный SKILL, ошибиться при поиске метода или неправильно сформировать JavaScript. И не все операции получится объединить в один вызов, иногда агенту необходимо проанализировать промежуточный результат и принять новое решение.

В такой схеме агенту не нужно держать в контексте подробные описания всех доступных MCP‑инструментов. Вместо этого он получает краткую инструкцию по конкретному процессу и загружает полные сведения о методах только тогда, когда они действительно понадобятся.

В этой схеме SKILL становится инструкцией и навигатором по возможностям MCP, а Code Mode — механизмом их обнаружения и выполнения.

В итоге у меня получился следующий набор SKILL‑ов для автоматизации моей работы как программиста (список, конечно, неполный):

  1. Исправление багов по тикету — собирает описание задачи, комментарии, вложения и связанные логи, организует работу над исправлением, фиксирует результат и запускает проверочную сборку.

  2. Реализация задачи по тикету — помогает пройти полный цикл разработки новой функции: изучить требования и материалы, подготовить план, реализовать изменения, сохранить результат и запустить сборку.

  3. Работа с системой тикетов — открывает задачи, показывает их описание, историю обсуждений, вложения и основные поля.

  4. Работа с GitLab — помогает находить проекты, просматривать ветки, коммиты и файлы, а также выполнять типовые операции с репозиториями.

  5. Работа с Jenkins — показывает доступные сборки, помогает запускать их и проверять статус выполнения.

  6. Работа с файловым сервером — ищет, скачивает и размещает файлы, логи и материалы, необходимые для разработки и диагностики.

Считаю, что к зимовке в Таиланде я готов. Буду, лежа на шезлонге возле моря или бассейна, через телефон отдавать приказы ИИ‑агенту. Он у меня теперь все знает, все умеет. Знает все процессы, правила и доступы.