Руководство
Как отдать существующий REST API ИИ-агенту: объявить MCP-инструменты в OpenAPI 3.x на Google API Gateway
API уже стоит за шлюзом. Прежде чем поднимать ещё один MCP-сервер, проверьте, может ли этот документ OpenAPI стать списком инструментов.
24 сентября 2026 Google описал конкретный вход в блоге для разработчиков: публичный превью Cloud API Gateway читает уже развёрнутую спецификацию OpenAPI, принимает MCP JSON-RPC на том же шлюзе и переводит каждый вызов обратно в REST. Анонс — Turn your REST APIs into MCP tools. Поля, проверки и коды ошибок берутся из Configure Model Context Protocol, обновлённого в тот же день и по-прежнему на условиях Pre-GA. Спецификация остаётся источником документации и типов: документация, типы и клиент из OpenAPI. Если предел превью заставляет поднимать свой удалённый сервер, форма выкладки — в удалённый MCP в проде.
Когда хватает аннотации
То, что агент должен вызывать, чаще всего уже REST. Обычная заплатка — второй MCP-сервер, который заново описывает пути, аутентификацию и квоты, а затем шлёт HTTP в бэкенд. JWT, API-ключ, квота и журналы остаются на шлюзе. Агент до них просто не доходит. Этот превью убирает лишний процесс. Вы выкладываете ту же конфигурацию API, и MCP появляется на /mcp. tools/call становится соответствующим REST-запросом, по тому же пути политик и с той же квотой операции. После перекодирования бэкенд не отличает вызов от прямого REST.
Превью покрывает REST, OpenAPI 3.x и уже настроенную аутентификацию. Resources, prompts, потоковые ответы и Model Armor — в дорожной карте, не в этом выпуске. Операции с пустым телом, например HTTP 204, инструментами не становятся. Глубоко вложенный object может прийти в tools/list неполностью. Один шлюз отдаёт не больше 1000 инструментов. MCP и маршрутизацию моделей нельзя включить в одной конфигурации API. Инструмент, который не является HTTP-операцией, аннотацией не выразить. Для него пишите свой сервер, форму входа смотрите в MCP и JSON Schema.
Переключатель MCP у API Gateway — не тот, что у Apigee. Google ставит Gateway как лёгкий вход: сервис уже на Cloud Run, и нужны управление плюс вход для агента без нового стека. Жизненный цикл, более тяжёлая политика трафика и монетизация — это MCP в Apigee. Какие внешние MCP-серверы агент может вызывать на выходе, решает Agent Gateway, а не это расширение OpenAPI. Верная аннотация на неверном продукте не попадает на шлюз, который вы реально эксплуатируете.
| Что уже есть | Аннотировать шлюз | Писать MCP-сервер |
|---|---|---|
| Операция уже REST, аутентификация и квота на шлюзе | Сначала этот превью | Только когда упрётесь в предел превью |
| Нужны resources, prompts или потоковый результат | Сейчас нельзя | Делать самим |
| Инструмент — не HTTP-операция | Аннотация это не выразит | inputSchema писать самим |
Включить MCP, затем убрать операции, которые модель видеть не должна
MCP принимает только OpenAPI 3.0.x или 3.1.x. Документ Swagger 2.0 списком инструментов не станет — сначала мигрируйте. Переключатель документа — x-google-api-management.mcp. Значение true открывает каждую допустимую операцию. Допустимая — это GET, POST, PUT, PATCH или DELETE, разрешимый backend и непустое описание. Имя инструмента по умолчанию — operationId. Описание берётся из description, иначе из summary.
На операции x-google-mcp-tool — это логическое значение или объект. false исключает операцию. Объект заменяет имя и описание. Имена должны совпадать с [A-Za-z0-9_.-]{1,128} и быть уникальными во всей спецификации. getOrderStatus шаблон проходит; модели лучше показывать get_order_status. В описании пишите ситуацию вызова, а не перечень полей ответа. Эту фразу модель и читает, когда выбирает инструмент.
Как только mcp становится объектом — чтобы повесить security на tools/list — MCP включён для каждой допустимой операции. Это не режим «закрыть обнаружение и ничего не публиковать». Исключайте по одной через x-google-mcp-tool: false. Расширение допустимо только на операции. На path или в корне документа загрузка отклоняется. У каждой публикуемой операции должен находиться backend: x-google-backend на операции или значение по умолчанию на уровне документа. JWT-схему оставьте ту, что уже работает для этого API. В примере только имя orderServiceJwt, новый блок issuer не придумывается.
Этот JSON — один контракт: MCP включён, для tools/list назван один JWT, у создания и поиска свои имена, удаление явно исключено.
{
"openapi": "3.0.4",
"info": {
"title": "Order Service",
"version": "1.0.0"
},
"x-google-api-management": {
"mcp": {
"tools-list": {
"security": {
"orderServiceJwt": []
}
}
},
"backends": {
"orders-backend": {
"address": "https://orders.example.run.app"
}
}
},
"paths": {
"/orders": {
"post": {
"operationId": "createOrder",
"description": "Creates an order for a known SKU and quantity.",
"x-google-backend": "orders-backend",
"x-google-mcp-tool": {
"name": "create_order",
"description": "Create an order when the user gives a SKU and a quantity. Do not use this to check delivery status."
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["sku", "qty"],
"properties": {
"sku": { "type": "string" },
"qty": { "type": "integer" }
}
}
}
}
},
"responses": {
"201": { "description": "Created" }
}
}
},
"/orders/{orderId}": {
"get": {
"operationId": "getOrderStatus",
"description": "Returns status, carrier, and ETA for one order.",
"x-google-backend": "orders-backend",
"x-google-mcp-tool": {
"name": "get_order_status",
"description": "Look up delivery status and ETA when the user asks where an order is or when it will arrive."
},
"parameters": [
{
"name": "orderId",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": { "description": "Order status" }
}
},
"delete": {
"operationId": "deleteOrder",
"summary": "Cancels an order that has not shipped.",
"x-google-backend": "orders-backend",
"x-google-mcp-tool": false,
"parameters": [
{
"name": "orderId",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"responses": {
"200": { "description": "Cancelled" }
}
}
}
}
}
arguments — не плоская копия REST-вызова
Шлюз отображает аргументы инструмента обратно в HTTP по документу OpenAPI. Параметры path и query становятся полями верхнего уровня arguments, ключ — имя параметра. Заголовки тоже на верхнем уровне; шлюз переносит их в запрос к бэкенду. Нельзя привязать зарезервированные системные заголовки и заголовки, имя которых начинается с x-google-. Тело запроса не расплющивается. Всё JSON-значение лежит в свойстве с именем body. Запрос статуса — {"orderId":"A-1042"}. Создание заказа — {"body":{"sku":"A-1042","qty":1}}.
При создании заказа JSON-тело REST кладётся под arguments.body.
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "create_order",
"arguments": {
"body": {
"sku": "A-1042",
"qty": 1
}
}
}
}
Модели пропускают этот слой чаще остальных. Неверные аргументы возвращаются как HTTP 200 с кодом JSON-RPC -32602. Многие клиенты считают любой не-200 сбоем транспорта, поэтому ошибки протокола остаются на 200. Прикладная ошибка бэкенда — это всё ещё успешный JSON-RPC, с result.isError равным true и телом бэкенда внутри. Прежде чем менять спецификацию, разделите три слоя: транспорт (401, 403, 405, 413), протокол (200 плюс error.code), приложение (200 плюс isError).
В этом вызове нет обёртки body. sku и qty лежат на верхнем уровне, шлюз отклоняет аргументы.
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "create_order",
"arguments": {
"sku": "A-1042",
"qty": 1
}
}
}
Глубоко вложенный object может отобразиться в tools/list неполностью. Если модель не видит всё тело, она выдумывает поля. Слой, который вы показываете модели, держите коротким: обязательные поля в required, замкнутые наборы в enum. Рукопожатие — это initialize, protocolVersion равен документированной строке 2025-11-25. Дальше на каждый запрос ставьте MCP-Protocol-Version. Без заголовка шлюз откатывается к 2025-03-26. Успешный notifications/initialized — это HTTP 202 без тела результата JSON-RPC.
Найти инструмент и вызвать его — разные замки
initialize и notifications/initialized без аутентификации. tools/list по умолчанию тоже. В разработке это удобно, в проде это публикует имена инструментов и форму входа любому, кто достучался до /mcp. Документация требует, чтобы mcp.tools-list.security указывал ровно на одну JWT-схему в components.securitySchemes. API-ключ tools/list не защищает. Несколько схем или API-ключ роняют загрузку.
tools/call этот замок обнаружения не смотрит. Он повторяет то, что REST-операция уже требует. Если операции нужен API-ключ, вызову тоже нужен ключ. Если нужен JWT — вызову нужен JWT. Закрыть обнаружение через JWT не значит авторизовать вызов. Заголовок x-api-key на соединении обслуживает только те операции, которые ключ уже используют. Когда список закрыт, запросу списка нужен ещё и Bearer. Два секрета храните отдельно.
Журналы остаются метриками API Gateway. MCP-трафик от обычного REST отличает путь, который кончается на /mcp, или своя метрика. Бэкенд не получает волшебный заголовок «это пришло от агента». Квота по вызывающему ставится в политике шлюза, до перекодирования. Внутри сервиса запрос уже смешан с прямым REST.
| Метод | Кто может вызвать по умолчанию | Какой секрет принимает превью |
|---|---|---|
| initialize, notifications/initialized | Кто угодно | Без аутентификации |
| tools/list | Кто угодно, пока не закроете | Ровно один JWT, если закрываете |
| tools/call | Как у этой REST-операции | API-ключ или JWT, как написано у операции |
Ошибки, которые видны уже при загрузке спецификации
Проверка идёт при создании конфигурации API, а не на первом tools/call. Операция без разрешимого описания отклоняется. Текст может прийти из description, summary или x-google-mcp-tool.description. Повтор имени, имя вне шаблона, расширение не на том месте и метод вне пяти глаголов — это сбой загрузки. HTTP 204 инструментом не станет. Напишите x-google-mcp-tool: false сами, чтобы «не публиковать» было решением в спецификации, а не сюрпризом в списке.
Коды протокола держите в runbook. -32700 и HTTP 400: тело — не JSON. -32600 и HTTP 200: это JSON, но не корректный JSON-RPC, нет jsonrpc, method или нужного id. -32601: метод вне набора, например ping, resources или prompts. -32602: неверная версия протокола, в initialize нет строкового protocolVersion, неизвестное имя инструмента или неверные аргументы. Сначала проверьте обёртку body. -32000: ответ слишком большой или тело бэкенда не разобрать. Слишком большое сырое тело HTTP — это 413. Всё, кроме POST на /mcp, — это 405.
401 и 403 остаются HTTP-статусами и несут WWW-Authenticate со ссылкой на метаданные защищённого ресурса. Это другой инцидент, не ошибка объекта аргументов. Если клиент закэшировал старое имя инструмента, -32602 Unknown tool значит: сначала сверить выкладку, потом сбросить кэш. Превью даётся как есть. Прежде чем считать MCP шлюза входом, прогоните по той же спецификации initialize, tools/list, одно чтение с параметром пути и одну запись с body.
Считать OpenAPI JSON контрактом, который вы ревьюите
Имена инструментов, описания, форма под body и операции со значением false живут в одном JSON. Ревьюьте отформатированную спецификацию. Убедитесь, что openapi — 3.0 или 3.1, найдите пустые описания, затем сверьте список x-google-mcp-tool: false с операциями, которые продукт действительно хочет публиковать. Между двумя выкладками diff показывает, кто снова включил удаление.
Если модель выбирает не тот инструмент, сначала меняйте описание инструмента, потом промпт сессии. Описание — это фраза внутри tools/list. «Когда вызывать» пишите в эту фразу, а «не вызывать удаление» выражайте явным исключением. Системный промпт, который всегда на месте, не отменяет инструмент, уже попавший в список.
Перед выкладкой хватает трёх проверок. Форматирование JSON раскладывает спецификацию, проверка JSON Schema сверяет образец тела с arguments.body, а сравнение JSON показывает, кто изменил исключение.
Частые вопросы
Можно ли включить MCP прямо на OpenAPI 2.0?
Нет. Превью принимает только OpenAPI 3.0.x и 3.1.x. Сначала переведите Swagger 2.0, затем добавьте backend, непустое описание и расширение MCP. Конвертеры часто оставляют старое место расширений. Поднимите backend на уровень документа в x-google-api-management.backends и ссылайтесь оттуда.
Может ли API-ключ защитить tools/list?
Нет. Чтобы закрыть обнаружение, нужно назвать ровно одну уже определённую JWT-схему. API-ключ по-прежнему может защищать конкретный tools/call, если эта REST-операция и так требует ключ. Секрет обнаружения и секрет вызова не склеивайте.
Может ли бэкенд понять, что запрос пришёл из MCP?
Нет. В документации перекодированный запрос программно неотличим от прямого REST. Учёт по вызывающему делайте в политике шлюза. Заголовок, который вы добавите внутри сервиса, может столкнуться и с вашими собственными REST-клиентами.
Когда это заменяет удалённый MCP-сервер, который вы ведёте сами?
Если операция уже за API Gateway и укладывается в пределы превью — аннотируйте спецификацию. Свой сервер нужен, когда требуются resources, prompts, потоковый результат, больше 1000 инструментов, пустое тело или инструмент, который не является HTTP-операцией. Если той же конфигурации API нужна ещё и маршрутизация моделей, MCP и маршрутизацию вместе не включить: разделите конфигурацию.
Итог и следующий шаг
Превью от 24 сентября делает «написать ещё один MCP-сервер» необязательным шагом. Контракт по-прежнему OpenAPI 3.x: переключатель документа, исключение по операции, имя и описание для модели, path и query на верхнем уровне arguments, тело запроса под body.
Перед выпуском закройте tools/list на JWT, убедитесь, что 204 и операции удаления не попали в список, затем прогоните рукопожатие, список, чтение и запись по тому же JSON. Условия превью всё ещё действуют. Пределы сверьте с документацией того дня, когда выкладываете.