GCP GKE Deployment¶
This guide walks you through deploying the Zscaler Integrations MCP Server to Google Kubernetes Engine (GKE). The unified gcp_mcp_operations.py script provisions an Autopilot cluster on demand (or reuses an existing one), generates the manifests, applies them, and wires up GCP Secret Manager for credential delivery.
Video walkthrough¶
▶ Walkthrough: Zscaler MCP Server and Google GKE (Wistia)
Prerequisites¶
gcloud CLI installed and authenticated (
gcloud auth login)A GCP project with billing enabled
kubectlinstalled (brew install kubernetes-clion macOS,gcloud components install kubectlotherwise)Zscaler OneAPI credentials (
ZSCALER_CLIENT_ID,ZSCALER_CLIENT_SECRET,ZSCALER_VANITY_DOMAIN,ZSCALER_CUSTOMER_ID)
Required GCP APIs¶
gcloud services enable \
container.googleapis.com \
secretmanager.googleapis.com \
compute.googleapis.com \
--project YOUR_PROJECT_ID
Required IAM Roles¶
The default Compute Engine service account (PROJECT_NUMBER-compute@developer.gserviceaccount.com) needs:
Role |
Purpose |
|---|---|
|
Read Zscaler credentials from GCP Secret Manager at runtime |
|
Bind the Kubernetes |
PROJECT_ID="your-project"
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SA" --role="roles/secretmanager.secretAccessor"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SA" --role="roles/iam.workloadIdentityUser"
Quick Start¶
cd integrations/google/gcp
python gcp_mcp_operations.py deploy
When prompted for the deployment target, select GKE. The script then asks for:
GCP project ID and region / zone (defaults pulled from
.envif present)Cluster mode — create a new GKE Autopilot cluster (PoC / testing) or use an existing cluster (production)
Cluster name — defaults to
zscaler-mcp-clusterfor new clustersKubernetes namespace — defaults to
defaultContainer image — defaults to
zscaler/zscaler-mcp-server:latestfrom Docker HubGCP Secret Manager — recommended (
y). When enabled, the script provisions secrets and grants the runtime service accountroles/secretmanager.secretAccessor; when disabled, credentials are baked into the manifest as plain env values.MCP auth mode — JWT, API Key, Zscaler, or None
What the Script Does¶
Verifies
kubectlandgcloudare installed and the user is logged inEnables the GKE API on the target project
Creates the GKE Autopilot cluster (if requested) — this typically takes 5–10 minutes
Runs
gcloud container clusters get-credentialsto set the kubectl context(Secret Manager path) creates Zscaler secrets in Secret Manager and grants IAM access
Generates a Kubernetes manifest (
Deployment+Serviceof typeLoadBalancer)Applies the manifest with
kubectl applyPolls for the LoadBalancer external IP
Updates Claude Desktop and Cursor configs with
http://<EXTERNAL_IP>/mcp
Operations¶
python gcp_mcp_operations.py status # cluster state, pod, and service
python gcp_mcp_operations.py logs # kubectl logs deployment/zscaler-mcp-server -f
python gcp_mcp_operations.py destroy # tear down (full or partial — see below)
python gcp_mcp_operations.py destroy -y # non-interactive teardown
Destroy behavior:
If the script created the cluster (
newmode),destroydeletes the cluster and any per-deployment GCP resources.If you supplied an existing cluster,
destroyremoves only the K8sDeploymentandServicewe created — your cluster and any other workloads remain intact.
Direct kubectl access works after deployment — the script sets your kubectl context to the cluster automatically:
kubectl get pods -n default -l app=zscaler-mcp-server
kubectl get svc zscaler-mcp-server -n default
kubectl describe deployment zscaler-mcp-server -n default
GCP Secret Manager Integration¶
When you answer Yes to “Use GCP Secret Manager for credentials?”, the script:
Stores each Zscaler credential as a separate secret in Secret Manager (with the canonical naming convention
zscaler-client-id,zscaler-client-secret,zscaler-vanity-domain,zscaler-customer-id,zscaler-cloud)Grants
roles/secretmanager.secretAccessorto the project’s default Compute Engine service accountConfigures the Pod with
ZSCALER_MCP_GCP_SECRET_MANAGER=trueandGCP_PROJECT_ID=<project>
The MCP server’s built-in credential loader fetches each secret at startup before the server initializes — no wrapper scripts or container changes required. See GCP Cloud Run Deployment for the full naming convention table.
To rotate a credential:
echo -n "new-client-secret" | \
gcloud secrets versions add zscaler-client-secret --data-file=-
kubectl rollout restart deployment/zscaler-mcp-server -n default
Authentication Modes¶
GKE supports four MCP client authentication modes (the same set as Cloud Run):
Mode |
Description |
Client Auth Header |
|---|---|---|
JWT |
Validate JWTs against a JWKS endpoint |
|
API Key |
Shared secret (auto-generated if not provided) |
|
Zscaler |
Validate via OneAPI client credentials |
|
None |
No authentication — development only |
No header |
When authentication is enabled, the script generates the appropriate Authorization header and writes it into your Claude Desktop and Cursor configs alongside the LoadBalancer URL.
Manual Deployment¶
If you prefer a hand-rolled manifest, here is a minimal reference that mirrors what the script generates (Secret Manager path):
apiVersion: apps/v1
kind: Deployment
metadata:
name: zscaler-mcp-server
namespace: default
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"
- name: ZSCALER_MCP_ALLOW_HTTP
value: "true"
- name: ZSCALER_MCP_DISABLE_HOST_VALIDATION
value: "true"
---
apiVersion: v1
kind: Service
metadata:
name: zscaler-mcp-server
namespace: default
spec:
type: LoadBalancer
selector:
app: zscaler-mcp-server
ports:
- port: 80
targetPort: 8000
You will also need a Kubernetes ServiceAccount (zscaler-mcp-sa) bound to a GCP service account that holds roles/secretmanager.secretAccessor via Workload Identity. The script handles all of this end-to-end.
Connecting Clients¶
The script updates Claude Desktop and Cursor automatically. If you need to configure a client manually:
Claude Desktop:
{
"mcpServers": {
"zscaler-mcp-server": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"http://<EXTERNAL_IP>/mcp",
"--allow-http",
"--header",
"Authorization: Basic <base64(client_id:client_secret)>"
]
}
}
}
Cursor (``mcp.json``):
{
"mcpServers": {
"zscaler-mcp-server": {
"url": "http://<EXTERNAL_IP>/mcp",
"headers": {
"Authorization": "Basic <base64(client_id:client_secret)>"
}
}
}
}
For HTTPS endpoints (e.g. when you place an Ingress with TLS in front of the LoadBalancer), drop the --allow-http flag.
Production Hardening¶
The default deployment exposes plain HTTP via a LoadBalancer Service for simplicity. For production:
TLS — front the Service with an Ingress + Google-managed certificate or NGINX Ingress +
cert-manager(Let’s Encrypt).Internal-only ingress — switch the Service to
ClusterIPand expose via an internal LB or a private Ingress if clients are inside the VPC.Replicas / HPA — scale beyond the default single replica (
kubectl scale deployment zscaler-mcp-server --replicas=3) and add anHorizontalPodAutoscaler.Network policies — restrict pod ingress to the Ingress controller / known clients only.
Troubleshooting¶
Pod stuck in ``Pending`` — usually an Autopilot resource constraint. Run kubectl describe pod <pod> to see the scheduler events.
``CrashLoopBackOff`` with ``permission denied`` on Secret Manager — confirm the project’s default Compute Engine service account has roles/secretmanager.secretAccessor (the script grants this; rerun if you skipped Secret Manager originally).
LoadBalancer external IP stays ``<pending>`` — the script polls for up to 5 minutes. Beyond that, GKE/GCP is still provisioning the Standard Load Balancer; kubectl get svc zscaler-mcp-server -w will show it eventually. Verify the project has the Compute Engine API enabled and a default network.
``Connection refused`` from clients — check the firewall (gcloud compute firewall-rules list) and confirm the Service shows EXTERNAL-IP with port 80 open.
References¶
integrations/google/ — full integration source, env templates, and per-client configuration examples
GCP Cloud Run Deployment — Cloud Run deployment guide (sister target)
GCP Compute Engine VM Deployment — Compute Engine VM deployment guide