Ситуация: подключаешь ИИ-агента к корпоративной системе с помощью MCP-сервера, и руки чешутся дать ему достаточно прав, чтобы он мог решать задачи без лишних уточнений. Вот только где проходит граница безопасности и как понять, какие права на самом деле требуются агенту?

Меня зовут Евгений, я инженер по виртуализации в Cloud.ru, и когда я начал разбираться с MCP, мне казалось, что задача довольно простая: взять существующее API, обернуть его в набор инструментов и дать пользователю возможность «разговаривать» с инфраструктурой на обычном языке. Но довольно быстро выяснилось, что только лишь удобный интерфейс не делает технологию простой изнутри.

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

Зачем вообще понадобился MCP

Я занимаюсь API наших сервисов: оно позволяет автоматизировать операции с инфраструктурой, получить информацию об объектах, создать ресурс, изменить его параметры и еще много других действий.

Проблема в том, что у нашего API исторически сложилась довольно сложная структура. Есть старая версия — OpenAPI на XML — и более современная версия — CloudAPI на JSON. При этом методы между ними распределены неравномерно: часть операций доступна только через старую версию, часть — только через новую.

Разработчику, который хочет решить конкретную задачу, приходится держать в голове обе версии, понимать, где искать нужный метод, и при необходимости объединять их в несколько запросов. Даже если каждый отдельный вызов несложный, собрать из них работающий сценарий бывает непросто.

Мы хотели спрятать эту сложность под капот. Чтобы заказчику не приходилось изучать документацию, подбирать методы и писать код для каждого сценария, достаточно было бы сформулировать задачу естественным языком. Например: найти виртуальную машину, посмотреть ее параметры и увеличить ей диск.

Для этого я решил разобраться с MCP (Model Context Protocol), который помогает модели взаимодействовать с внешними системами через стандартизированный интерфейс. Вместо того чтобы самостоятельно разбираться в устройстве API, модель получает описание доступных инструментов и может вызывать их для решения поставленной задачи.

На первый взгляд все просто: описываем набор функций, подключаем сервер к агенту — и можно работать. Но в процессе разработки я понял, что главный вопрос не в том, как передать модели очередной метод API. Важно учесть, что именно она сможет с ним сделать и как поведет себя, если результат окажется не таким, как она ожидала.

Почему доступ к API нельзя просто отдать модели

У обычного API довольно строгий интерфейс. Если я передал некорректный параметр, запрос завершится ошибкой. Дальше решение за мной: я разбираю ответ, исправляю запрос и повторяю попытку.

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

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

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

То есть вместо:

private setupErrorHandling() {

    this.server. => {

      console.error('[MCP Error]', error);

    };

    process.on('SIGINT', async () => {

      await this.server.close();

      process.exit(0);

    });

  }

использовать что-то вроде этого:

private setupErrorHandling() {

    this.server. => {

      console.error('[MCP Error]', sanitizeError(error));

    };

    process.on('SIGINT', async () => {

      await this.server.close();

      process.exit(0);

    });

    process.on('unhandledRejection', (reason, promise) => {

      console.error('[Unhandled Rejection]', sanitizeError(reason));

    });

    process.on('uncaughtException', (error) => {

      console.error('[Uncaught Exception]', sanitizeError(error));

      process.exit(1);

    });

  }

где sanitizeError — функция, фильтрующая чувствительные данные, которые может возвращать система.

Далее я столкнулся с тем, что при широких правах на чтение агент мог обращаться к информации, которая не требовалась для его задач. Например: получать сведения о владельцах объектов или правилах межсетевого экрана. Если для работы с этими данными инструментов в MCP-сервере не предусмотрено, такой доступ сам по себе не делает агента полезнее.

Поэтому я бы разделял два вопроса:

  1. Какие инструменты нужны агенту для решения поставленной задачи?

  2. К каким данным и операциям эти инструменты должны иметь доступ?

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

Как ограничить не только инструменты, но и права

В первую очередь рекомендую выдавать учетным данным MCP-сервера минимально необходимые права. Причем ограничивать стоит не только операции, но и объекты, с которыми они могут работать.

Это касается и прав на чтение. Часто кажется, что чтение безопасно по умолчанию, ведь оно ничего не меняет. Но чем больше информации доступно модели, тем больше данных попадает в контекст и тем больше вариантов действий она может рассматривать.

Но есть и обратная сторона. Если оставить агенту только чтение, он не сможет выполнять полезные задачи: создавать виртуальные машины, увеличивать диски или создавать снапшоты. Значит, нужно найти баланс между возможностями и ограничениями.

Рекомендую начинать с конкретного сценария использования:

  • определите, какие действия нужны пользователю;

  • подберите инструменты для этих действий;

  • настройте права для соответствующей учетной записи.

Иногда агент не находит нужный ответ и начинает искать обходной путь. В нашем случае он мог обратиться к веб-поиску, а затем попробовать получить данные, которые изначально не входили в задачу. Кроме прямого сценария действий, также важно проверять, что модель делает, когда не может выполнить запрос привычным способом.

Главное правило: агент должен иметь доступ только к тем объектам и операциям, которые действительно нужны для его задач.

Почему инструменты должны быть однозадачными

Когда я начал разрабатывать MCP-сервер, то пошел от простых операций: получить список объектов, найти виртуальную машину по имени или идентификатору, посмотреть ее параметры, получить метрики. Мне хотелось, чтобы каждый инструмент выполнял одну понятную задачу. В итоге у нас получилось около 36 инструментов. Это небольшие функциональные блоки, из которых модель может собирать более сложные сценарии.

Почему не сделать один универсальный инструмент, который сам разберется, что нужно выполнить? Такой инструмент сложнее ограничить и проверить. Если он умеет искать ресурсы, изменять их параметры и удалять объекты, труднее заранее определить последствия конкретного вызова. Чем точнее задача инструмента, тем проще контролировать его возможности.

Например, если инструмент изменяет параметры виртуальной машины, он должен получить однозначный идентификатор объекта и проверить входные данные. Если идентификатор не передан или имеет неверный формат, инструмент должен вернуть ошибку, а не пытаться угадать, какой объект имелся в виду.

Входные данные нужно проверять в коде до обращения к API, в том числе отсеивать неожиданные значения и команды, которые инструмент не должен обрабатывать. Описание инструмента помогает модели понять, как им пользоваться, но не заменяет проверки на стороне сервера.

В MCP описаны аннотации инструментов — метаданные, которые характеризуют их предполагаемое поведение:

  • readOnlyHint

  • destructiveHint

  • idempotentHint

  • openWorldHint

С их помощью можно указать, что инструмент предназначен только для чтения, может изменять данные, допускает безопасный повторный вызов или задействует web search для поиска информации. Это помогает модели и клиенту лучше понимать назначение инструмента и уровень его безопасности, но сами аннотации не ограничивают доступ и не заменяют технические проверки.

Где нужны технические ограничения, а где — подтверждение человека

Можно ли решить проблему ограничения прав ИИ-агента с помощью системного промпта? Например, написать: «Не удаляй ресурсы», «Не меняй настройки безопасности», «Не повторяй запрос, если он завершился ошибкой»?

Да, но такие инструкции, как правило, бесполезны. Считать их полноценным механизмом контроля доступа нельзя. Промпт не заменяет проверку входных данных, полномочий учетной записи и допустимости операции в самом инструменте. Кроме того, в разных средах могут действовать другие инструкции на другом уровне. К примеру, Claude Code поддерживает файл settings.json в нескольких вариантах, и каждый уровень варианта определяет скоуп действия конфига:

  1. Managed (все пользователи на АРМ)

  2. User (директория конкретного проекта)

  3. Project (для всех в репозитории)

  4. Local (локальные пользователи)

И инструкции в нашем инструменте не будут иметь глобального эффекта.

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

Первый уровень — права учетной записи. Она должна иметь доступ только к необходимым объектам и операциям.

Второй уровень — ограничения в коде инструмента. Обязательно проверяем параметры, валидируем все входные данные, не выполняем операции за пределами заданного сценария и возвращаем понятные ошибки.

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

К таким операциям можно отнести удаление объектов, удаление снапшотов и уменьшение ресурсов, если такая операция вообще предусмотрена. Для каждой из них нужно отдельно определить, какие проверки и подтверждения необходимы. 

При этом не стоит превращать подтверждение в обязательный шаг для любого вызова. Если агент получает список виртуальных машин или собирает инвентарную информацию, постоянное подтверждение будет мешать работе. Дополнительный контроль нужен там, где последствия ошибки достаточно серьезны.

Что проверить перед выходом в прод

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

Уровень 1. Проверить код инструментов

Для каждого инструмента нужно подготовить тестовые входные данные и заранее определить ожидаемый результат.

Проверить следует не только штатные сценарии, но и ошибки:

  • обязательный параметр не передан;

  • вместо числа пришла строка или другое некорректное значение;

  • объект не найден;

  • у учетной записи нет нужных прав;

  • API вернул ошибку;

  • операция не поддерживается;

  • ответ API пустой или содержит чувствительные данные.

Отдельно стоит проверить, что агенты под капотом не раскрывают лишнюю информацию. Например, если в ответе API могут оказаться пароли, идентификаторы или другие чувствительные значения, нужно определить, какие из них допустимо передавать модели и пользователю. При необходимости данные следует маскировать.

Когда я разрабатывал MCP-сервер, стало понятно, что повторяющиеся проверки лучше выносить в общие функции. Например, обработку ошибок или очистку результатов. Если реализовать ее отдельно в каждом сценарии, при исправлении одного из них легко забыть обновить остальные.

Уровень 2. Проверить работу агента

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

Почему это важно? Потому что инструменты связаны через контекст. Если модель перестала получать нужный идентификатор или описание объекта, следующий шаг может не сработать, даже если сам инструмент написан правильно.

В нашем случае я столкнулся с тем, что после исправления одного сценария приходилось повторно проверять остальные инструменты. Это одна из причин, почему имеет смысл заранее продумать общие функции и тестовую инфраструктуру. Если изменения затрагивают обработку ошибок, формат ответа или работу с контекстом, нужно повторно прогонять связанные сценарии и не ограничиваться одним исправленным запросом.

Чек-лист перед запуском MCP-сервера

Перед тем как подключать MCP-сервер к рабочим процессам, я советую проверить следующее:

  • Учетные данные: у сервера есть только необходимые права на чтение и изменение ресурсов.

  • Объекты доступа: сервер не может читать или изменять объекты, с которыми не должен работать.

  • Инструменты: каждый инструмент выполняет одну понятную задачу и не обладает избыточными полномочиями.

  • Входные данные: параметры валидируются в коде, а неоднозначные или некорректные запросы отклоняются.

  • Результаты операций: успех и ошибка явно различимы, ошибки содержат понятное объяснение.

  • Критические действия: для опасных операций предусмотрены необходимые ограничения и подтверждения.

  • Чувствительные данные: ответы не раскрывают лишнюю информацию, при необходимости данные маскируются.

  • Тесты инструментов: проверены штатные сценарии и негативные случаи.

  • Тесты агента: проверены типовые запросы и последовательности вызовов инструментов.

  • Логирование: достаточно информации, чтобы разбирать ошибки и понимать, что происходило при вызове инструментов.

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

В Evolution AI Agents можно отдельно настраивать инструменты, доступные агенту, включать логирование запросов и тестировать MCP-сервер перед подключением к рабочим процессам. Это помогает организовать часть проверок на уровне платформы, но корректность самих инструментов и ограничения доступа все равно нужно проектировать и проверять в реализации. 

Подробнее о настройке сервера можно почитать в документации:

Что в итоге

Самое важное, что я вынес из этого опыта: языковая модель делает взаимодействие с инфраструктурой удобнее, но не отменяет привычные инженерные требования. Наоборот, поскольку модель может выбирать разные способы решения одной задачи, инструменты нужно проектировать особенно внимательно.

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

MCP позволяет создать для модели удобный интерфейс к внешним системам. Наша задача как разработчиков — сделать так, чтобы за этим интерфейсом оставалась предсказуемая и контролируемая логика.

Комментарии (2)


  1. rosliakov_b24
    21.10.2026 09:00

    Но все равно это все не работает, когда доходишь до практики и первый раз ошибаешься


    1. restoniich
      21.10.2026 09:00

      ну может просто руки тогда не из того места растут, хз)