Дата публикации
ai_products

Как срезать лишние инструменты MCP через Azure API Management

Что нового

Microsoft добавила в Azure API Management (APIM) полноценную роль MCP‑шлюза. Теперь APIM умеет работать не только с REST, но и с MCP‑серверами, которые используют GitHub Copilot, Claude и другие агентные платформы.

Конкретно появилось:

  • Отдельный раздел MCP Servers в интерфейсе APIM — не в общем списке API, а как самостоятельный тип сущности.
  • Возможность подключить внешний MCP‑сервер (External MCP server): APIM выступает как reverse‑proxy, понимает MCP‑протокол (JSON‑RPC поверх HTTP или Server‑Sent Events) и вешает на него политики.
  • Возможность поверх обычного REST API включить MCP‑слой и выдавать его агентам как MCP‑сервер.
  • Поддержка политик APIM для MCP‑трафика: можно инспектировать и переписывать JSON‑RPC‑сообщения, фильтровать инструменты, валидировать аргументы, логировать вызовы.

Ключевой сценарий: вы можете дать разработчикам доступ к MCP‑серверу, но точечно отключить отдельные инструменты, которые:

  • сжигают контекст без пользы,
  • ходят в платные API,
  • не проходят требования безопасности.

MCP‑спецификация не умеет отключать инструменты поштучно. Для тонкого контроля приходится ставить перед MCP‑сервером шлюз, который видит JSON‑RPC и может его фильтровать. Эту роль теперь берёт на себя APIM.

Как это работает

Базовая идея MCP‑шлюза

MCP‑шлюз сидит между агентом (клиентом) и MCP‑сервером (бэкендом). MCP — это JSON‑RPC поверх HTTP или SSE. Любой reverse‑proxy, который умеет смотреть и менять тело запроса/ответа, теоретически может играть роль MCP‑шлюза.

APIM делает именно это:

  1. Принимает соединение от агента (Copilot, Claude и т.п.).
  2. Понимает, что внутри идёт MCP‑протокол.
  3. Применяет к JSON‑RPC‑пакетам политики: аутентификация, фильтрация инструментов, логирование, квоты и т.д.
  4. Прокидывает допустимые вызовы на реальный MCP‑сервер или возвращает синтетический ответ.

MCP по сути опирается на два метода:

  • tools/list — агент при подключении спрашивает у сервера список инструментов: имена, описания, схемы входных данных. На этом основе он решает, что может делать.
  • tools/call — агент вызывает конкретный инструмент с аргументами.

Если вы хотите спрятать инструмент, нужно обработать оба метода:

  • отфильтровать его из ответа tools/list, чтобы агент вообще не знал о его существовании;
  • заблокировать tools/call по имени инструмента, чтобы не сработал прямой вызов (например, если кто‑то жёстко прописал имя в конфиг).

Что даёт MCP‑шлюз на базе APIM

APIM переносит привычные фичи API‑шлюза в мир MCP:

  • Централизованная аутентификация и авторизация. Можно поставить Entra ID, mTLS или токены с областями поверх MCP‑серверов, которые сами по себе знают только про API‑ключ.
  • Rate limiting и квоты. Ограничить количество вызовов, чтобы зациклившийся агент не спалил бюджет на платном апстриме за пару минут.
  • Логирование и аудит. Сохранять, какой пользователь вызвал какой инструмент и с какими аргументами. Отправлять это в Log Analytics или SIEM.
  • Сетевую изоляцию. Спрятать внешние MCP‑сервера за приватными endpoint’ами и фиксированными egress‑IP. Машины разработчиков при этом не торчат в интернет напрямую.
  • Шаринг одного MCP‑сервера между многими клиентами. Из single‑tenant MCP сделать общий вход с пер‑юзер идентичностью и лимитами на уровне команд.
  • Политики и комплаенс. Управлять тем, какие инструменты видны, редактировать поля, валидировать аргументы, трансформировать ответы до того, как они попадут в агента.
  • Агрегация MCP‑серверов. Собирать несколько бэкендов за одним логическим MCP‑endpoint’ом, чтобы агенты подключались только к одной точке.
  • Устойчивость. Добавлять ретраи, circuit‑breaker’ы, кэшировать tools/list, чтобы не реализовывать всё это в каждом агент‑хосте.
  • Контроль стоимости. Вводить лимиты, алерты и внутренний чарджбек для MCP‑серверов, которые ходят в платные по‑запросу сервисы.

MCP в интерфейсе APIM

В APIM для MCP выделен отдельный раздел MCP Servers. Там можно:

  • зарегистрировать существующий внешний MCP‑сервер (External MCP server);
  • включить MCP‑поверх обычного REST API.

Политики для MCP применяются в том же движке, что и для REST, но к JSON‑RPC‑сообщениям MCP. Можно читать context.Request.Body, context.Response.Body, проверять method, аргументы и возвращать свои ответы.

Пример: Microsoft Learn MCP‑сервер

В примере используется публичный MCP‑сервер Microsoft Learn. Он предоставляет три инструмента:

  • microsoft_docs_search
  • microsoft_docs_fetch
  • microsoft_code_sample_search

Задача: оставить первые два и заблокировать третий.

Шаги настройки MCP‑шлюза в APIM:

  1. В APIM открыть раздел MCP Servers.
  2. Добавить новый MCP‑сервер.
  3. Выбрать тип External MCP server.
  4. Указать endpoint Microsoft Learn MCP.
  5. APIM сам подберёт транспорт (HTTP или SSE) в зависимости от апстрима.
  6. Сохранить.
  7. Настроить агента (Copilot, Claude и т.п.) на URL, который выдаёт APIM, а не прямой URL MCP‑сервера.

На этом этапе APIM работает как прозрачный прокси. Дальше всё решают политики.

Установка / Как запустить

Ниже — два готовых варианта политик APIM для MCP‑серверов.

Вариант A: статический allowlist инструментов

Вы жёстко задаёте список инструментов, которые MCP‑сервер «как бы» поддерживает. APIM:

  • перехватывает tools/list и возвращает свой список, игнорируя ответ бэкенда;
  • блокирует tools/call для запрещённых инструментов.

Плюсы:

  • предсказуемое поведение;
  • не нужно читать тело ответа от апстрима;
  • изменения на стороне MCP‑сервера не просачиваются к пользователям, пока вы сами не обновите политику.

Минусы:

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

Политика для варианта A (добавляется в редактор MCP Server policy в APIM):

<policies>
  <inbound>
    <choose>
      <when condition="@{ var body = context.Request.Body.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true); if (body == null) { return false; } var rpcMethod = (string)body[\"method\"]; return string.Equals(rpcMethod, \"tools/list\", System.StringComparison.OrdinalIgnoreCase); }">
        <return-response>
          <set-status code="200" reason="OK" />
          <set-header name="Content-Type" exists-action="override">
            <value>application/json</value>
          </set-header>
          <set-body>@{ var req = context.Request.Body.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true); var id = req != null ? req["id"] : Newtonsoft.Json.Linq.JValue.CreateNull(); var tools = new Newtonsoft.Json.Linq.JArray( new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("name", "microsoft_docs_search"), new Newtonsoft.Json.Linq.JProperty("description", "Search official Microsoft/Azure documentation"), new Newtonsoft.Json.Linq.JProperty("inputSchema", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("type", "object"), new Newtonsoft.Json.Linq.JProperty("properties", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("query", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("type", "string") )) )) )) ), new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("name", "microsoft_docs_fetch"), new Newtonsoft.Json.Linq.JProperty("description", "Fetch a Microsoft documentation page as markdown"), new Newtonsoft.Json.Linq.JProperty("inputSchema", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("type", "object"), new Newtonsoft.Json.Linq.JProperty("properties", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("url", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("type", "string") )) )), new Newtonsoft.Json.Linq.JProperty("required", new Newtonsoft.Json.Linq.JArray("url")) )) ) ); var response = new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("jsonrpc", "2.0"), new Newtonsoft.Json.Linq.JProperty("id", id), new Newtonsoft.Json.Linq.JProperty("result", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("tools", tools) )) ); return response.ToString(Newtonsoft.Json.Formatting.None); }</set-body>
        </return-response>
      </when>
      <when condition="@{ var body = context.Request.Body.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true); if (body == null) { return false; } var rpcMethod = (string)body[\"method\"]; var toolName = (string)body[\"params\"]?[\"name\"]; return string.Equals(rpcMethod, \"tools/call\", System.StringComparison.OrdinalIgnoreCase) && string.Equals(toolName, \"microsoft_code_sample_search\", System.StringComparison.OrdinalIgnoreCase); }">
        <return-response>
          <set-status code="200" reason="OK" />
          <set-header name="Content-Type" exists-action="override">
            <value>application/json</value>
          </set-header>
          <set-body>@{ var req = context.Request.Body.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true); var id = req != null ? req["id"] : Newtonsoft.Json.Linq.JValue.CreateNull(); var error = new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("jsonrpc", "2.0"), new Newtonsoft.Json.Linq.JProperty("id", id), new Newtonsoft.Json.Linq.JProperty("error", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("code", -32601), new Newtonsoft.Json.Linq.JProperty("message", "Tool disabled by API Management policy: microsoft_code_sample_search") )) ); return error.ToString(Newtonsoft.Json.Formatting.None); }</set-body>
        </return-response>
      </when>
    </choose>
  </inbound>
  <backend />
  <outbound />
  <on-error />
</policies>

Что делает эта политика:

  • В первом when APIM ловит tools/list, не отправляет его на MCP‑сервер, а возвращает собственный JSON‑RPC‑ответ с двумя инструментами. ID запроса берётся из входящего сообщения, чтобы клиенту всё сошлось.
  • Во втором when APIM перехватывает tools/call с name = microsoft_code_sample_search и отвечает ошибкой JSON‑RPC с кодом -32601 (Method not found) и явным текстом, что инструмент отключён политикой APIM.
  • Все остальные вызовы (tools/call для разрешённых инструментов, initialize, ping и т.д.) проходят на бэкенд без изменений.

Ключевой момент — preserveContent: true при чтении тела запроса. Без него чтение тела «съедает» поток, и дальше по конвейеру уже нечего проксировать.

Вариант B: динамический deny‑list

Здесь вы доверяете списку инструментов от MCP‑сервера и вырезаете только нежелательные. Логика:

  • tools/list идёт на бэкенд, APIM получает полный список;
  • на выходе APIM переписывает ответ, удаляя инструменты из deny‑листа;
  • tools/call для запрещённых инструментов по‑прежнему блокируется.

Плюсы:

  • меньше ручной работы: новые инструменты появляются автоматически;
  • изменения схем входных данных MCP‑сервером подхватываются без правки политики.

Минусы:

  • политика читает и переписывает context.Response.Body, а это может конфликтовать со стримингом;
  • придётся следить за новыми инструментами: если вы не добавите их в deny‑лист, они сразу станут доступны пользователям.

Политика для варианта B:

<policies>
  <inbound>
    <choose>
      <when condition="@{ var body = context.Request.Body.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true); if (body == null) { return false; } var rpcMethod = (string)body[\"method\"]; var toolName = (string)body[\"params\"]?[\"name\"]; return string.Equals(rpcMethod, \"tools/call\", System.StringComparison.OrdinalIgnoreCase) && string.Equals(toolName, \"microsoft_code_sample_search\", System.StringComparison.OrdinalIgnoreCase); }">
        <return-response>
          <set-status code="200" reason="OK" />
          <set-header name="Content-Type" exists-action="override">
            <value>application/json</value>
          </set-header>
          <set-body>@{ var req = context.Request.Body.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true); var id = req != null ? req["id"] : Newtonsoft.Json.Linq.JValue.CreateNull(); var error = new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("jsonrpc", "2.0"), new Newtonsoft.Json.Linq.JProperty("id", id), new Newtonsoft.Json.Linq.JProperty("error", new Newtonsoft.Json.Linq.JObject( new Newtonsoft.Json.Linq.JProperty("code", -32601), new Newtonsoft.Json.Linq.JProperty("message", "Tool disabled by API Management policy: microsoft_code_sample_search") )) ); return error.ToString(Newtonsoft.Json.Formatting.None); }</set-body>
        </return-response>
      </when>
    </choose>
  </inbound>
  <backend />
  <outbound>
    <choose>
      <when condition="@{ var req = context.Request.Body.As<Newtonsoft.Json.Linq.JObject>(preserveContent: true); if (req == null) { return false; } var rpcMethod = (string)req[\"method\"]; return string.Equals(rpcMethod, \"tools/list\", System.StringComparison.OrdinalIgnoreCase); }">
        <set-body>@{ var bodyText = context.Response.Body.As<string>(preserveContent: true); if (string.IsNullOrEmpty(bodyText)) { return bodyText; } var resp = Newtonsoft.Json.Linq.JObject.Parse(bodyText); var tools = resp["result"]?["tools"] as Newtonsoft.Json.Linq.JArray; if (tools == null) { return bodyText; } var filtered = new Newtonsoft.Json.Linq.JArray(); foreach (var t in tools) { var name = (string)t?["name"]; if (!string.Equals(name, "microsoft_code_sample_search", System.StringComparison.OrdinalIgnoreCase)) { filtered.Add(t); } } ((Newtonsoft.Json.Linq.JObject)resp["result"])["tools"] = filtered; return resp.ToString(Newtonsoft.Json.Formatting.None); }</set-body>
      </when>
    </choose>
  </outbound>
  <on-error />
</policies>

Разбор логики:

  • Inbound: тот же блок, что и в варианте A. Блокирует прямые вызовы microsoft_code_sample_search с ошибкой -32601.
  • Backend: пустой — все разрешённые запросы идут на настоящий MCP‑сервер, включая tools/list.
  • Outbound: если исходный запрос был tools/list, политика читает тело ответа, парсит JSON, фильтрует массив result.tools, выкидывая инструмент из deny‑листа, и возвращает обновлённый JSON.

Весь остальной трафик идёт без переписывания.

Как проверить, что всё работает

После сохранения политики подключите к APIM‑endpoint’у любой агент, который умеет MCP, и проверьте три вещи:

  1. В ответе на tools/list видны только microsoft_docs_search и microsoft_docs_fetch. microsoft_code_sample_search отсутствует.
  2. Вызов tools/call с name = microsoft_code_sample_search возвращает ошибку JSON‑RPC с кодом -32601.
  3. Вызовы разрешённых инструментов проходят до реального MCP‑сервера и возвращают корректный результат.

Что это значит для вас

Когда стоит использовать APIM как MCP‑шлюз

Это решение полезно, если вы:

  • запускаете GitHub Copilot, Claude или другие агенты в корпоративной среде;
  • подключаете MCP‑сервера, которые вы не контролируете (SaaS, сторонние команды, публичные сервисы);
  • хотите дать разработчикам мощные инструменты, но обязаны ограничить риски — финансовые и по безопасности.

Конкретные кейсы:

  • Платные апстримы. MCP‑инструмент ходит в дорогой API по вызову. Через APIM вы ставите квоты, лимиты по пользователям и командам, алерты, а при необходимости — полностью отключаете инструмент.
  • Секьюрити‑политики. Какой‑то инструмент умеет, например, вытаскивать чувствительные ресурсы. Вы скрываете его из tools/list и блокируете прямые вызовы.
  • Общий доступ к MCP‑серверу. Один MCP‑сервер может обслуживать несколько команд и продуктов. APIM добавляет аутентификацию, разделяет лимиты и ведёт раздельный аудит.
  • Безопасный выход в интернет. Разработчики сидят за NAT без прямого выхода. APIM выступает единой точкой выхода к внешним MCP‑серверам с контролируемыми IP и правилами.

Когда лучше не усложнять

  • Если MCP‑сервер полностью ваш, и вы можете просто изменить список инструментов в его коде, проще починить всё на источнике. Шлюз не нужен.
  • Если у вас один маленький внутренний проект без жёстких требований по аудиту и бюджету, MCP‑шлюз через APIM может быть избыточным.
  • Если ваш агент активно использует стриминг ответов MCP (особенно для долгих операций), вариант с переписыванием тела ответа (deny‑list) нужно тестировать очень аккуратно. В спорных случаях лучше остаться на статическом allowlist.

Доступность и ограничения

APIM — облачный сервис Microsoft Azure. Для полноценной работы требуется доступ к Azure. Если ваша организация ограничивает использование зарубежных облаков или трафик в Azure, придётся согласовать это с безопасностью и IT.

Если доступ к Azure в вашей сети закрыт или вы не можете использовать облачные сервисы Microsoft по юридическим причинам, развернуть APIM не получится. В этом случае придётся искать альтернативный шлюз (self‑hosted reverse‑proxy с кастомной логикой для MCP).

Место на рынке

На практике роль MCP‑шлюза может выполнять любой достаточно умный reverse‑proxy:

  • Nginx или Envoy с Lua/Wasmtime;
  • собственный сервис на Node.js, Go или .NET;
  • коммерческие API‑шлюзы других вендоров.

Ключевое отличие APIM — глубокая интеграция в экосистему Azure и готовый движок политик:

  • те же механизмы аутентификации (Entra ID, mTLS);
  • общая система логирования и мониторинга (Log Analytics, SIEM);
  • единая точка управления квотами и тарифами для REST и MCP.

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

Как выбирать между вариантами политик

  • Если вам нужна жёсткая и предсказуемая конфигурация, список инструментов меняется редко, а стриминг критичен — берите Option A (allowlist). Вы полностью контролируете, что видит агент, и не трогаете тело ответов.
  • Если MCP‑сервер часто обновляется, добавляет инструменты и меняет схемы, а вы хотите минимум ручной работы — используйте Option B (deny‑list), но внимательно тестируйте поведение агентов и стриминг.

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


Читайте также