GCP Cloud Run Deployment

This guide walks you through deploying the Zscaler Integrations MCP Server to Google Cloud Run with optional GCP Secret Manager integration for secure credential storage.

Video walkthrough

Video: Zscaler MCP Server and Google Cloud Run

Walkthrough: Zscaler MCP Server and Google Cloud Run (Wistia)

Prerequisites

  • gcloud CLI installed and authenticated

  • A GCP project with billing enabled

  • Zscaler OneAPI credentials (ZSCALER_CLIENT_ID, ZSCALER_CLIENT_SECRET, ZSCALER_VANITY_DOMAIN, ZSCALER_CUSTOMER_ID)

Manual Deployment

Deploy directly with gcloud:

gcloud run deploy zscaler-mcp-server \
  --image=zscaler/zscaler-mcp-server:latest \
  --region=us-central1 \
  --platform=managed \
  --port=8000 \
  --args="--transport,streamable-http,--host,0.0.0.0,--port,8000" \
  --set-env-vars="\
ZSCALER_CLIENT_ID=your-client-id,\
ZSCALER_CLIENT_SECRET=your-client-secret,\
ZSCALER_VANITY_DOMAIN=your-domain,\
ZSCALER_CUSTOMER_ID=your-customer-id,\
ZSCALER_CLOUD=production,\
ZSCALER_MCP_ALLOW_HTTP=true,\
ZSCALER_MCP_DISABLE_HOST_VALIDATION=true,\
ZSCALER_MCP_AUTH_ENABLED=true,\
ZSCALER_MCP_AUTH_MODE=zscaler" \
  --memory=512Mi \
  --allow-unauthenticated

Warning

Credentials passed as --set-env-vars are visible in the Cloud Console. Use GCP Secret Manager for production deployments.

GCP Secret Manager Integration

The Docker image includes a built-in credential loader for GCP Secret Manager. When enabled, the server fetches Zscaler API credentials at startup — no wrapper scripts or container modifications required.

How It Works:

  1. Container starts and checks ZSCALER_MCP_GCP_SECRET_MANAGER

  2. If true, fetches each credential from Secret Manager using Application Default Credentials

  3. Sets values as environment variables before the MCP server initializes

Naming Convention:

Environment variable names are converted to Secret Manager IDs by lowercasing and replacing underscores with hyphens:

Environment Variable

Secret Manager ID

ZSCALER_CLIENT_ID

zscaler-client-id

ZSCALER_CLIENT_SECRET

zscaler-client-secret

ZSCALER_VANITY_DOMAIN

zscaler-vanity-domain

ZSCALER_CUSTOMER_ID

zscaler-customer-id

ZSCALER_CLOUD

zscaler-cloud

Step 1: Create Secrets

PROJECT_ID="your-gcp-project"

echo -n "your-client-id" | \
  gcloud secrets create zscaler-client-id \
    --data-file=- --replication-policy=automatic --project=$PROJECT_ID

echo -n "your-client-secret" | \
  gcloud secrets create zscaler-client-secret \
    --data-file=- --replication-policy=automatic --project=$PROJECT_ID

# Repeat for zscaler-vanity-domain, zscaler-customer-id, zscaler-cloud

Step 2: Grant IAM Access

PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
SA_EMAIL="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"

for SECRET in zscaler-client-id zscaler-client-secret zscaler-vanity-domain \
              zscaler-customer-id zscaler-cloud; do
  gcloud secrets add-iam-policy-binding $SECRET \
    --member="serviceAccount:$SA_EMAIL" \
    --role="roles/secretmanager.secretAccessor" \
    --project=$PROJECT_ID --quiet
done

Step 3: Deploy with Secret Manager

gcloud run deploy zscaler-mcp-server \
  --image=zscaler/zscaler-mcp-server:latest \
  --region=us-central1 \
  --set-env-vars="\
ZSCALER_MCP_GCP_SECRET_MANAGER=true,\
GCP_PROJECT_ID=$PROJECT_ID,\
ZSCALER_MCP_ALLOW_HTTP=true,\
ZSCALER_MCP_DISABLE_HOST_VALIDATION=true,\
ZSCALER_MCP_AUTH_ENABLED=true,\
ZSCALER_MCP_AUTH_MODE=zscaler" \
  --args="--transport,streamable-http,--host,0.0.0.0,--port,8000" \
  --port=8000 --memory=512Mi --allow-unauthenticated

GKE Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: zscaler-mcp-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: zscaler-mcp-server
  template:
    metadata:
      labels:
        app: zscaler-mcp-server
    spec:
      serviceAccountName: zscaler-mcp-sa
      containers:
      - name: zscaler-mcp
        image: zscaler/zscaler-mcp-server:latest
        args: ["--transport", "streamable-http", "--host", "0.0.0.0", "--port", "8000"]
        ports:
        - containerPort: 8000
        env:
        - name: ZSCALER_MCP_GCP_SECRET_MANAGER
          value: "true"
        - name: GCP_PROJECT_ID
          value: "your-gcp-project"

Connecting Clients

Claude Desktop

{
  "mcpServers": {
    "zscaler-mcp-server": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://your-service.run.app/mcp",
        "--header",
        "Authorization: Basic <base64(client_id:client_secret)>"
      ]
    }
  }
}

Generate the Base64 value:

echo -n "your-client-id:your-client-secret" | base64

Cursor

{
  "mcpServers": {
    "zscaler-mcp-server": {
      "url": "https://your-service.run.app/mcp",
      "headers": {
        "Authorization": "Basic <base64(client_id:client_secret)>"
      }
    }
  }
}

Credential Rotation

# Update the secret value
echo -n "new-client-secret" | \
  gcloud secrets versions add zscaler-client-secret --data-file=-

# Restart the service to pick up the new version
gcloud run services update zscaler-mcp-server --region=us-central1

Troubleshooting

401 Unauthorized:

  • Verify the Authorization: Basic header contains valid base64(client_id:client_secret)

  • Confirm ZSCALER_MCP_AUTH_ENABLED=true and ZSCALER_MCP_AUTH_MODE=zscaler are set on the container

Permission denied accessing secret:

gcloud secrets add-iam-policy-binding zscaler-client-secret \
  --member="serviceAccount:YOUR_SA_EMAIL" \
  --role="roles/secretmanager.secretAccessor"

Viewing logs:

gcloud run services logs read zscaler-mcp-server --region=us-central1 --limit=50