チュートリアル

既存の REST API を AI エージェントに渡す:OpenAPI 3.x で Google API Gateway 上に MCP ツールを宣言する

API はすでにゲートウェイの後ろで動いています。もう一台 MCP サーバーを立てる前に、この OpenAPI がツール一覧になれるかを見てください。

2026年9月24日、Google は開発者ブログで具体的な入口を出しました。Cloud API Gateway のパブリックプレビューは、既にデプロイしている OpenAPI を読み、同じゲートウェイで MCP の JSON-RPC を受け、呼び出しを REST に戻します。発表は Turn your REST APIs into MCP tools です。フィールド、検証、エラーコードは同日更新の Configure Model Context Protocol に従い、ページ先頭は Pre-GA 条項の対象だと明記しています。仕様をドキュメントと型の源にする手順は OpenAPI からドキュメント、型、クライアントを生成する を見てください。プレビューの制限で自前のリモートサーバーが必要なら、配置は Remote MCP を本番に載せる にまとめてあります。

注釈だけで足りるとき

エージェントが呼ぶ能力の多くは、すでに REST です。よくある継ぎ足しは、パスと認証とクォータを書き直す MCP サーバーをもう一台立て、バックエンドへ HTTP することです。ゲートウェイ側の JWT、API キー、クォータ、ログはそのまま残ります。エージェントがそこに届かないだけです。このプレビューが省くのはそのプロセスです。同じ API config をデプロイすると、MCP はゲートウェイの /mcp に出ます。tools/call は対応する REST になり、ポリシーの経路もその操作のクォータも直接呼び出しと共通です。変換後のリクエストは、バックエンドから見ると通常の REST と区別できません。

プレビューが覆うのは REST、OpenAPI 3.x、そして今使っている認証です。Resources、Prompts、レスポンスのストリーミング、Model Armor はロードマップ上にあり、今回はありません。HTTP 204 のように空ボディを返す操作はツールになりません。深くネストした object は tools/list で欠けることがあります。ゲートウェイ 1 台の上限は 1000 ツールです。同じ API config で MCP と model routing は同時に有効にできません。HTTP 操作ではないツールは注釈では表現できません。その場合は自前のサーバーを書き、入力の形は MCP と JSON Schema を参照してください。

API Gateway の MCP と Apigee の MCP は同じスイッチではありません。Google は Gateway を軽い入口に置いています。サービスがすでに Cloud Run 上にあり、管理とエージェント入口を早く足したいときです。ライフサイクル、より重いトラフィックポリシー、収益化は Apigee の MCP 側です。エージェントが外向きにどの MCP を呼んでよいかを縛るのは Agent Gateway であり、この OpenAPI 拡張ではありません。製品を取り違えると、注釈が正しくても今動いているゲートウェイには届きません。

手元にあるもの Gateway に注釈する MCP サーバーを書く
操作はすでに REST で、認証とクォータはゲートウェイにあるまずこのプレビュープレビューの制限に当たってから
resources、prompts、ストリーム結果が必要今はできない自分で実装する
ツールが HTTP 操作ではない注釈では受け取れないinputSchema を自分で書く

全体をオンにしてから、モデルに見せない操作を外す

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 をオブジェクトにすると、tools/list に security を付けるためであっても、対象操作はすべて公開されます。「発見だけ鎖をかけて、操作は出さない」にはなりません。出したくない操作には一つずつ 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 を平らに写したものではない

ゲートウェイは OpenAPI に従ってツール引数を HTTP に戻します。path と query は arguments のトップレベルフィールドになり、キーはパラメータ名です。header もトップレベルで、ゲートウェイがバックエンドのリクエストヘッダーへ写します。システム予約ヘッダーと、x-google- で始まるヘッダーはバインドできません。リクエストボディは平らになりません。JSON 全体が body というプロパティの下に入ります。注文照会は {"orderId":"A-1042"} です。注文作成は {"body":{"sku":"A-1042","qty":1}} です。

注文を作るとき、REST の JSON ボディは 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 で、components.securitySchemes にある JWT をちょうど一つ指すよう求めています。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 config を作るときに走り、最初の 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 です。/mcp への POST 以外は 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 の一覧と、製品として出したい操作の一覧を突き合わせます。二回のデプロイの間は差分を見て、誰が削除操作を再びオンにしたかを見ます。

モデルがツールを選び間違えたら、セッションのプロンプトより先にツール説明を直します。説明は tools/list の中でモデルが読む文です。「いつ呼ぶか」はその文に書き、削除を呼ばせないことは明示的なオプトアウトにします。毎回あるシステムプロンプトは、すでに一覧へ出たツールを取り消せません。

デプロイ前の確認は三歩で足ります。JSON フォーマッタで仕様を広げ、JSON Schema 検証でボディの見本と arguments.body を照合し、JSON 差分で、誰がオプトアウトを変えたかを見ます。

よくある質問

OpenAPI 2.0 のまま MCP をオンにできますか?

できません。プレビューが受けるのは OpenAPI 3.0.x と 3.1.x だけです。Swagger 2.0 は先に 3.x へ移し、backend、空でない説明、MCP 拡張を足します。変換ツールは古い拡張の位置を残しがちです。backend はドキュメントレベルの x-google-api-management.backends に上げ、そこを参照します。

API キーで tools/list を守れますか?

守れません。発見を鎖かけるには、定義済みの JWT をちょうど一つ指名します。API キーは、その REST 操作が元からキーを要求しているときの tools/call は守れます。発見用と呼び出し用の資格情報は一つにまとめないでください。

バックエンドはリクエストが MCP 経由だと判別できますか?

できません。文書は、変換後のリクエストは直接の REST とプログラムでは区別できないと書いています。呼び出し元ごとの計上はゲートウェイポリシーで行います。サービス内で足したヘッダーは、自分の REST クライアントと衝突することもあります。

自前の Remote MCP とどう使い分けますか?

操作がすでに Gateway の後ろにあり、プレビューの範囲に収まるなら仕様へ注釈します。resources、prompts、ストリーム結果、1000 を超えるツール、空ボディ、HTTP 操作ではないツールが必要なら自前のサーバーです。同じ API config で model routing も要るなら、MCP とルーティングは同時にオンにできないので config を分けます。

まとめと次の一歩

9月24日のプレビューは、「もう一台 MCP サーバーを書く」を既定の動作から選択肢に変えました。契約は引き続き OpenAPI 3.x です。全体スイッチ、操作ごとのオプトアウト、モデル向けの名前と説明、arguments 直下の path と query、body の下に置くリクエストボディです。

公開前に tools/list を JWT で鎖かけ、204 と削除系の操作が一覧に漏れていないことを確認し、同じ JSON でハンドシェイク、一覧、読み取り、書き込みを打ちます。プレビュー条項はまだ有効です。制限はデプロイする日の文書で見直してください。