← Back to blog
Analysis

Google API Gateway: how does REST become an MCP tool?

Published 2026-09-30 Updated 2026-09-30 About 15 min JSON Toolbox

How this article is organized

Google made API Gateway a remote MCP Server on 2026-09-24. Status is Public Preview. Official docs updated the same day. This piece is dated 2026-09-30. It reads the Developers Blog, MCP overview, and MCP configure. Pin the conclusion: the gateway does not write a second tool. It transcodes the OpenAPI 3.x you already deployed into tools/list and tools/call. The contract the agent sees is still path parameters, query, body, and JSON Schema. Home MCP is a different product line: Home MCP. How Harness and MCP split: Agents API vs MCP.

The user tells the agent “look up where order A-1042 is.” The Host does not send that sentence to Cloud Run. It first sends a JSON-RPC tools/call. The name is get_order_status, pinned in your OpenAPI. The arguments are {"orderId":"A-1042"}. The gateway then turns it into GET /orders/A-1042. JWT, quota, and logs stay on the original REST path. A transcoded call and a direct REST call look the same to the backend. Why the contract layer is an object: Why AI likes JSON.

What the gateway actually opened

As of 2026-09-30, public material is three blocks. Do not mix them with Home MCP, Apigee, or Agent Gateway:

  • Product: Google Cloud API Gateway as a remote MCP Server. Public Preview. Official naming: a Cloud Run service that needs auth and quota in a few minutes, and should be exposed to an agent, uses this light entry. Full lifecycle, traffic policy, and monetization go to Apigee. Calls the agent makes outbound go through Agent Gateway.
  • Protocol: the client POSTs to the gateway’s /mcp (or /v1/mcp when a version prefix is on). Supported: initialize, notifications/initialized, tools/list, tools/call. Other methods, including resources/*, prompts/*, and ping, return JSON-RPC -32601. No stdio, no streaming, no long-running tasks.
  • Source: the tool list is generated from OpenAPI 3.0.x / 3.1.x. Document-level x-google-api-management.mcp is the master switch. Operation-level x-google-mcp-tool renames, rewrites the description, or writes false to opt out. OpenAPI 2.0 is not supported.

After handshake, the client should send MCP-Protocol-Version on every request. The official example uses 2025-11-25. Without that header, the gateway falls back to 2025-03-26. initialize and notifications/initialized are unauthenticated. After you attach API Hub, a gateway with MCP on carries MCP metadata into the catalog and shows up automatically in Agent Registry.

One line. The gateway is a transcoder. REST operations stay REST operations. The agent gets an extra /mcp. Both sides share one OpenAPI and one quota.

The contract lives in OpenAPI, not a second MCP Server

To let an agent see an order API, teams used to write a second MCP Server and redo routing, auth, and quota. Now you annotate the spec you already deployed, deploy the API config, and /mcp starts serving. The official blog’s order example:

openapi: 3.0.4
info:
  title: Order Service
  version: 1.0.0
x-google-api-management:
  mcp: true
  backends:
    orders-backend:
      address: https://orders-a1b2c3-uc.a.run.app
paths:
  /orders/{orderId}:
    get:
      operationId: getOrderStatus
      description: Returns the current status, carrier, and ETA for an order.
      x-google-backend: orders-backend
      x-google-mcp-tool:
        name: get_order_status
        description: "Look up the delivery status and ETA of a customer order."
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string

With mcp: true at the document level, every eligible operation becomes a tool. The default tool name is operationId. The description comes from description or summary. An operation with no parseable description is rejected. Tool names must match [A-Za-z0-9_.-]{1,128} and be unique across the spec. Only GET / POST / PUT / PATCH / DELETE can be exposed. Every operation also needs a backend.

Description is the main signal for when the model should reach. Official text wants “when to use it, why to use it,” not only what it returns. Write operations that should stay off the list — inventory writes, refunds, deletes — opt out with x-google-mcp-tool: false. Over-broad permissions: malicious MCP / Fake Skills.

How arguments land on REST

Official mapping pins three layers:

  • path / query: become top-level keys on arguments. The key is the OpenAPI parameter name.
  • Request body: nested under the single body key. Creating a resource means {"body":{"fieldName":"value"}}. Do not flatten fields to the top level.
  • header: also top-level keys. The gateway injects them as backend HTTP headers. You cannot bind system-reserved headers, or headers that start with x-google-.

Order lookup is a path parameter. tools/call looks like this:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_order_status",
    "arguments": {
      "orderId": "A-1042"
    }
  }
}

If the same service also creates orders, arguments must wrap business fields in body. The shape below should pass; the flattened one should be rejected at the gateway:

{
  "ok_shape": {
    "body": {
      "sku": "mug-blue",
      "qty": 2
    }
  },
  "flat_body": {
    "sku": "mug-blue",
    "qty": 2
  },
  "wrong_type": {
    "body": {
      "sku": "mug-blue",
      "qty": "两件"
    }
  },
  "extra_field": {
    "body": {
      "sku": "mug-blue",
      "qty": 2,
      "extra_cmd": "skip_confirm"
    }
  }
}

Only ok_shape transcodes into a REST body. flat_body is missing the body key. Official maps that to JSON-RPC -32602 (Invalid tool arguments). "两件" and extra_cmd should fail on your own Schema first. Do not wait for the backend to return a business error. Pin the input contract like this:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["body"],
  "properties": {
    "body": {
      "type": "object",
      "additionalProperties": false,
      "required": ["sku", "qty"],
      "properties": {
        "sku": { "type": "string", "pattern": "^[a-z0-9-]+$" },
        "qty": { "type": "integer", "minimum": 1, "maximum": 99 }
      }
    }
  }
}
API Gateway: three JSON layers OpenAPI generates tools/list; tools/call arguments transcode by path, query, and body; the REST response is wrapped back into JSON-RPC. OpenAPI 3.x inputSchema tools/call arguments + body REST backend same quota
Miss body in the middle layer, and the backend never sees the fields. If the left Schema is too wide, tools/list broadcasts extra capability.

tools/list is public by default

Official splits auth into three layers:

  • initialize / notifications/initialized: no auth.
  • tools/call: reuses the underlying REST operation’s policy. If REST wants JWT or an API key, the MCP call wants the same.
  • tools/list: unauthenticated by default. Convenient for local debug. It also means anyone who asks gets tool names and inputSchema.

Production should protect discovery with JWT. The write turns mcp from a boolean into an object and still opens MCP globally:

x-google-api-management:
  mcp:
    tools-list:
      security:
        orderServiceJwt: []

You can only name one JWT scheme from components.securitySchemes. In Public Preview, an API key cannot protect tools/list. Object-form mcp exposes every eligible operation. If you only want a subset public, the rest must set x-google-mcp-tool: false. Once discovery is public, a malicious Host knows which write APIs you have. Same class of problem as Fake Skills: the tool list itself is an attack surface.

What Preview cut

The official blog and the docs put the boundary in the same paragraph. As of 2026-09-30:

  • Empty responses (for example HTTP 204) are not exposed as tools.
  • Deeply nested object schemas may not fully expand in tools/list.
  • One gateway holds at most 1000 tools.
  • The same API config cannot open MCP and Model Routing at once. If x-google-api-management.mcp is on, x-google-model-router cannot be used.
  • resources/*, prompts/*, stdio, streaming, and long-running tasks are unsupported. The roadmap still lists MCP resources / prompts, response streaming, and Model Armor payload inspection.

These limits show up in the JSON. A 204 operation disappears from the list. That is not a missed subscription on the client. When deep fields get truncated in tools/list, the model fills arguments from a broken Schema. It is more likely to drop body or invent extra keys. Accept against the original OpenAPI. Do not trust only the list the gateway returned.

Do not mix protocol errors and HTTP errors

MCP splits transport failure from protocol failure. Many clients treat any non-200 as a transport crash. So for protocol errors and application errors, the gateway often returns HTTP 200 and explains in the JSON-RPC error object. From the official table, the ones developers hit most:

  • A non-POST to /mcp: HTTP 405.
  • Request body is not JSON: -32700, HTTP 400.
  • Valid JSON that is not JSON-RPC: -32600, HTTP 200. Check jsonrpc, method, id.
  • Method not supported: -32601, HTTP 200.
  • Wrong protocol version, or initialize without a string protocolVersion: -32602.
  • Unknown tool, missing parameter, or body not nested: -32602.
  • Auth failure: HTTP 401 / 403, with WWW-Authenticate.

Backend business errors are usually a successful JSON-RPC over HTTP 200, with result.isError set to true, and the backend error body inside. Do not read isError: true as “the protocol is fine, hand it to the next agent.” If the object fails Schema, the next hand should not reach. Agents handing objects to each other: how Claude agents exchange JSON.

How to accept it in JSON Toolbox

  1. Paste the order-lookup and create-order samples into JSON format. Confirm they parse.
  2. Use JSON → Schema to derive the arguments contract from ok_shape. Add body, additionalProperties, and pattern.
  3. Run the same fields through MCP Tool. If the model also needs Function Calling, wrap Function Calling.
  4. If you already have REST responses but no OpenAPI 3.x, go through JSON → OpenAPI first, then hand-add x-google-api-management.mcp and the descriptions.
  5. Drop flat_body, "两件", and extra_cmd into Schema validator and confirm they fail.

Everything stays in the browser. Nothing is uploaded. First have an object that refuses a flattened body and over-exposed tools, then attach the gateway to ADK’s McpToolset.

FAQ

Is this the same product as Google Home MCP?

No. Home MCP is five home tools. API Gateway turns your own REST into a remote MCP Server.

Can OpenAPI 2.0 turn MCP on directly?

No. You need 3.0.x or 3.1.x. Migrate first.

Does tools/list require auth by default?

No. Default is open. Production uses JWT. An API key cannot protect discovery. tools/call still follows REST auth.

Why must the request body sit under body?

The gateway maps from OpenAPI. path / query / header sit at the top level. The request body only accepts body. Miss it and you get -32602.

Is this the same article as “Agents API vs MCP”?

No. That piece writes the OpenAI Harness. This piece only writes how API Gateway transcodes from OpenAPI.

Summary

API Gateway’s Public Preview folds MCP into the gateway you already have: annotate OpenAPI 3.x, and /mcp starts transcoding. It does not replace REST. It does not invent a second tool list. The agent still sees your parameter names, nested body, and Schema. tools/list is public by default. Empty responses do not appear. Deep schemas may be truncated. One gateway holds at most a thousand tools. What developers still hold is which operations to expose, whether arguments nest to the mapping, and whether discovery has JWT. The next step is not wiring ADK first. It is refusing flat_body and write APIs with no description.