Zuplo
On this page

Authenticate an MCP server with Google Cloud IAM

The agent gets a short-lived identity token from Google and the MCP server verifies it against Google's public keys, so there is no key to store or rotate.

Method
Google Cloud IAM
Platform
Google Cloud
MCP specification
2026-07-28

How it works

The agent requests a Google OIDC ID token whose audience is the MCP server's URL, then sends it as Authorization: Bearer ID_TOKEN. The server verifies the signature against Google's JWKS, checks the aud and iss claims, and reads the calling service account's email from the token. The runtime service account's ambient credentials mint the token, so no secret appears in configuration.

Best for
Agents that hold Google credentials, calling a server that can verify a Google token
Specification
Platform identity rather than MCP authorization; the specification doesn't cover it
Works with
Your own workloads only; no MCP client can mint these tokens
Effort
A day, mostly IAM bindings

The exchange

  • Your agent runtime service account
  • Google metadata server
  • MCP server verifies via JWKS
  1. The runtime service account's ambient credentials mint the token, so nothing appears in configuration and there is no secret to rotate.

    Your agent to Google

    GET …/identity?audience=… Metadata-Flavor: Google
  2. Google to Your agent

    id_token (carries the credential) aud = https://mcp.example.com/mcp
  3. Your agent to MCP server

    POST /mcp (carries the credential) Authorization: Bearer ID_TOKEN
  4. Verification is offline against cached JWKS, so the server never calls Google, and the audience check is what stops a token minted for another service from being replayed here.

    Inside MCP server

    Verify the signature against Google's JWKS
  5. Inside MCP server

    Check aud and iss, then read the caller email = agent@project.iam.gserviceaccount.com
  6. MCP server to Your agent

    200 OK tool result
The agent asks Google for an ID token whose audience is the MCP server URL, then presents it as a bearer token. The server verifies the signature against Google's JWKS, checks the audience and issuer, and reads the calling service account from the email claim, never contacting Google itself.

Connect your agent

Pick your language and SDK.

Language
SDK
TypeScriptagent.ts
import { GoogleAuth } from "google-auth-library";
import { query } from "@anthropic-ai/claude-agent-sdk";

const MCP_URL = "https://mcp-server-abc123-uc.a.run.app";
const idClient = await new GoogleAuth().getIdTokenClient(MCP_URL);

// Headers are read when the run starts, so mint them per run rather
// than hoisting the bearer string into a module constant.
for await (const message of query({
  prompt: "List your tools.",
  options: {
    mcpServers: {
      platform: {
        type: "http",
        url: `${MCP_URL}/mcp`,
        headers: Object.fromEntries(
          new Headers(await idClient.getRequestHeaders()),
        ),
      },
    },
  },
})) {
  console.log(message);
}

When to use something else

Use this when

  • The agent runs with Google credentials — usually on Google Cloud — so it can mint tokens with no stored secret. The server only has to verify them.
  • You want the credential to expire on its own: ID tokens live about an hour and are re-minted from ambient credentials, where a static key lasts until somebody rotates it.
  • Tool calls must name a specific workload rather than a shared key.

Use something else when

  • A third party has to call the server, since only a caller holding Google credentials can mint these tokens.
  • An interactive MCP client needs to connect. Claude, ChatGPT, and Cursor can't produce a Google ID token, so they need OAuth in front or a local proxy that attaches one for them.
  • The agent calls on behalf of a named person and the API behind the server enforces per-user permissions.

This is platform identity rather than MCP authorization, so the specification has nothing to say about it. The trade-off is reach: it works for your own workloads and nothing else, so servers that need both usually put a gateway in front to translate.

Zuplo

Cloud IAM is strong between your own services and unusable outside them, so the questions that follow are all about what happens when something outside needs in.

  1. Step 1.

    Add the second door

    Cloud IAM stays exactly as it is between your own services. The gateway is a second route, for the callers Google will not mint an ID token for — a browser, a chat client, someone else's agent.

    "url": "https://api.example.com/mcp"
  2. Step 2.

    Add the policies

    A list on the route, and the options that go with it. The gateway authenticates the caller its own way, then mints the Google ID token your service already checks — an Enterprise-plan upstream policy.

    "inbound": [
      "mcp-oauth-inbound",
      "mcp-capability-filter-inbound",
      "mcp-token-exchange-inbound"
    ]
  3. Step 3.

    Deploy

    Your service keeps its audience check and its IAM binding. The service-to-service path is untouched.

    zuplo deploy

A partner's agent needs two of these tools. They have no Google account.

mcp-oauth-inbound

They don't need one. The route signs callers in against the identity provider you already run — any OIDC provider, or one of eleven with named policies — while Google IAM keeps guarding the service-to-service path. One MCP OAuth policy per project.

If we add an OAuth door, what stops one token opening every route?

RFC 8707 resource indicators

The audience discipline you already rely on, kept for you: every gateway-issued token is bound to one route's canonical resource URI, so a token minted for /mcp/orders is rejected everywhere else.

Which service account called which tool, and how often?

capability_invocation

One query: every tool call resolves to a caller and a named capability, with an outcome and a latency — instead of a shared egress IP in an access log.

Can I forward the caller's ID token to the API behind the server?

mcp-token-exchange-inbound

No — the specification forbids that with a MUST NOT. The gateway strips inbound auth and sends an independent upstream credential instead, so nothing minted for your server ever leaves it.

All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out. Your MCP server keeps the code and the authentication it has today.

Common questions

Why an ID token rather than an access token?

An ID token carries an audience claim that can be bound to one specific service, which is what prevents a token minted for another service from working. Access tokens address Google APIs, not your own endpoints.

Is the audience the server URL or the /mcp path?

The service's base URL. Mint and verify the same value. Including the /mcp path in the audience is a common cause of audience-mismatch failures.

Do I have to refresh the token myself?

Not if you cache the ID token client rather than the token string. The Google auth library re-mints as the roughly one-hour token approaches expiry, so caching the bearer string is what makes long-lived instances start failing after an hour.

Can Claude or ChatGPT connect to a server protected this way?

No MCP client can mint a Google ID token, which is why Google's own guidance is to choose a different method when the client can't supply the credential. Interactive clients need the specification's OAuth flow or a local proxy that attaches the credential for them.

Is Cloud Run IAM sufficient by itself?

It closes the ingress, which is most of the value, but it tells the application nothing. Verifying the token in the handler is what supplies the caller identity for authorization decisions and audit logs.

Does this pattern work on Lambda or Kubernetes?

The shape does, with different mechanics: AWS uses SigV4 request signing, and Kubernetes can validate projected service-account tokens against the cluster's OIDC issuer. In all three cases, no MCP client can produce the credential, so the pattern stays internal.

One policy engine for APIs, AI, and MCP

Put your MCP servers behind a gateway that speaks every identity provider, filters tools per role, and logs every call.