Skip to content

Latest commit

 

History

History
116 lines (84 loc) · 5.57 KB

File metadata and controls

116 lines (84 loc) · 5.57 KB

Setting up IAP for your MCP server

iap-mcp-proxy supports both IAP deployment modes. The mode determines what --audience you pass.

Mode A — direct IAP on Cloud Run

Enable IAP directly on the Cloud Run service (no load balancer):

gcloud beta run services update MY-MCP-SERVICE \
  --region=REGION \
  --iap

gcloud beta iap web add-iam-policy-binding \
  --member="user:you@example.com" \
  --role="roles/iap.httpsResourceAccessor" \
  --region=REGION \
  --resource-type=cloud-run \
  --service=MY-MCP-SERVICE

gcloud ... --iap enables IAP with Google's auto-managed OAuth client. This client rejects Google-issued OIDC ID tokens (adc/impersonate/oauth modes) — every audience format returns Invalid IAP credentials: Invalid JWT audience. The simplest way in is a self-signed service-account JWT (--credentials=signjwt), which needs no OAuth client at all:

# Enable the Service Account Credentials API (required for signJwt).
gcloud services enable iamcredentials.googleapis.com

# Grant your ADC identity permission to sign as the SA, and the SA
# access to the IAP resource.
gcloud iam service-accounts add-iam-policy-binding \
  mcp-caller@PROJECT.iam.gserviceaccount.com \
  --member="user:you@example.com" \
  --role="roles/iam.serviceAccountTokenCreator"

gcloud beta iap web add-iam-policy-binding \
  --member="serviceAccount:mcp-caller@PROJECT.iam.gserviceaccount.com" \
  --role="roles/iap.httpsResourceAccessor" \
  --region=REGION --resource-type=cloud-run --service=MY-MCP-SERVICE

# Use the CANONICAL run.app URL (status.url), not the project-number URL.
CANONICAL_URL="$(gcloud run services describe MY-MCP-SERVICE \
  --region=REGION --format='value(status.url)')"

iap-mcp-proxy \
  --credentials=signjwt \
  --impersonate-service-account mcp-caller@PROJECT.iam.gserviceaccount.com \
  "${CANONICAL_URL}/mcp"

Audience: defaults to the exact upstream endpoint (e.g. https://my-mcp-xxxx.a.run.app/mcp), which scopes a leaked token to that one path. Origin-only and the project-number URL are not accepted; pass --audience <canonical-origin>/* if you want the token to cover all paths on the service.

signjwt is the simplest route because it needs no OAuth client. Managed-client IAP can also accept OIDC ID tokens if you configure a separate allow-listed OAuth client (see custom OAuth configuration); with such a client you can use --credentials=impersonate/adc and the client ID as --audience. Older direct-Cloud-Run IAP created with a custom client works the same way.

Mode B — IAP behind a global external Application Load Balancer

Classic backend-service IAP: Cloud Run (or GCE/GKE) behind a global external ALB with IAP enabled on the backend service.

  1. Enable IAP on the backend service (Console: Security → Identity-Aware Proxy, or gcloud iap web enable --resource-type=backend-services ...). This creates/uses an OAuth client.

  2. Find the IAP OAuth client ID (NNN-xxxx.apps.googleusercontent.com) under APIs & Services → Credentials.

  3. Grant access:

    gcloud iap web add-iam-policy-binding \
      --member="user:you@example.com" \
      --role="roles/iap.httpsResourceAccessor" \
      --resource-type=backend-services \
      --service=MY-BACKEND-SERVICE

Audience: the IAP OAuth client ID — it must be passed explicitly:

iap-mcp-proxy --audience NNN-xxxx.apps.googleusercontent.com https://mcp.internal.example.com/mcp

Creating a desktop OAuth client (for --credentials=oauth)

Needed only when your local credentials are gcloud user credentials (no service account, no impersonation). IAP's programmatic access requires an OAuth client in the same project as the IAP resource — see Programmatic authentication.

  1. APIs & Services → Credentials → Create credentials → OAuth client ID, application type Desktop app, in the same project as the IAP resource.

  2. Export its ID and secret where the proxy runs:

    export IAP_MCP_OAUTH_CLIENT_ID="NNN-yyyy.apps.googleusercontent.com"
    export IAP_MCP_OAUTH_CLIENT_SECRET="..."
  3. First run opens a browser for sign-in; the refresh token is stored in the OS keychain and reused silently afterwards.

Depending on your IAP configuration you may need to allow the desktop client's ID as a valid programmatic audience — check the current IAP documentation, as this behavior has changed over time.

Service-account impersonation (recommended for teams/CI)

gcloud iam service-accounts add-iam-policy-binding mcp-caller@PROJECT.iam.gserviceaccount.com \
  --member="user:you@example.com" \
  --role="roles/iam.serviceAccountTokenCreator"

iap-mcp-proxy \
  --impersonate-service-account mcp-caller@PROJECT.iam.gserviceaccount.com \
  --audience NNN-xxxx.apps.googleusercontent.com \
  https://mcp.internal.example.com/mcp

Grant roles/iap.httpsResourceAccessor to the service account on the IAP resource.

Debugging

  • Run with --log-level=debug (or IAP_MCP_LOG=debug); all logs go to stderr, so they show up in your MCP client's server logs without corrupting the protocol stream.

  • IAP-generated responses carry the x-goog-iap-generated-response: true header; the proxy uses it to distinguish IAP errors from application errors.

  • Test outside an MCP client:

    echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
      | iap-mcp-proxy --log-level=debug https://my-mcp-xxxx.a.run.app/mcp