Kubernetes (Helm Chart)¶
Deploy the Zscaler MCP Server to any Kubernetes cluster via Helm — EKS, GKE, AKS, OpenShift, Rancher, k3s, Talos, kind / minikube for local dev. The chart is cluster-vendor-agnostic and never calls aws, az, or gcloud. You bring the cluster; the chart brings the workload.
Note
Need a hyperscaler-managed deploy instead? This isn’t for you. Use:
AWS Bedrock AgentCore — Amazon Bedrock AgentCore Deployment
Azure Container Apps / VM / AKS-Preview script — Azure Deployment
GCP Cloud Run / GKE-script / Compute Engine — GCP Cloud Run Deployment
Those scripts provision and manage the underlying cloud infrastructure (clusters, networks, IAM, Key Vaults, etc.) end-to-end. This chart assumes you already have a Kubernetes cluster and want to install one more workload into it.
When to Use This vs. Other Deploy Options¶
You want to… |
Use |
|---|---|
Install into an existing K8s cluster (any cloud, any distro, on-prem) |
This chart |
Wire into ArgoCD / Flux / a corporate GitOps pipeline |
This chart (Helm-source |
Run locally on Claude Code / Cursor / Gemini CLI without containers |
|
Run a single container without Kubernetes |
|
Have AWS host the runtime for you on Bedrock |
|
Stand up brand-new Azure infra and deploy on top |
|
Stand up brand-new GCP infra and deploy on top |
This chart is the right answer when the cluster is already a fact and your operating model treats every workload as a Helm release.
Why a Helm chart at all?¶
The MCP server is an HTTP service that needs credentials, an ingress, a few kubectl-flavoured knobs (replicas, resources, probes), and the option to bring its own pre-existing Kubernetes Secret. Helm encodes that contract once and lets it run identically on:
EKS with IRSA-fed Secrets via External Secrets Operator (ESO) → AWS Secrets Manager
GKE with Workload Identity + Secret Manager via ESO
AKS with Workload Identity Federation + Azure Key Vault via ESO or the Key Vault CSI driver
OpenShift with
Secretprovisioned by the OpenShift secret-injection operatorVanilla / on-prem K8s with HashiCorp Vault Agent Injector, SealedSecrets, or sops-encrypted GitOps
Local dev (kind / minikube / colima) with an inline
Secretrendered by the chart
In each of those cases, the cluster-side Helm command is identical; only the source-of-credentials story differs.
Prerequisites¶
Kubernetes 1.24+
A Zscaler OneAPI client —
ZSCALER_CLIENT_ID,ZSCALER_CLIENT_SECRET(orZSCALER_PRIVATE_KEY),ZSCALER_VANITY_DOMAIN, andZSCALER_CUSTOMER_ID(for ZPA tools)(Optional) cert-manager for auto-issued TLS certs
(Optional) External Secrets Operator or another secret-injection mechanism for production credential storage
(Optional) An Ingress controller (NGINX, Traefik, ALB, etc.) or Gateway API v1 if you want to expose the MCP endpoint outside the cluster
Credential Setup — Choose Your Path¶
The chart never asks you to translate your .env into values.yaml syntax. Pick the path that matches how your team already manages secrets:
# |
Path |
When to use |
|---|---|---|
1 |
Interactive script ( |
Local dev, kind / minikube, day-1 walkthroughs. Recommended starting point. |
2 |
Manual ``kubectl + helm`` with |
CI pipelines, GitOps reconcilers (Argo, Flux). |
3 |
Inline ``–set`` credentials |
Quick local smoke tests, templating pipelines. Never use for production. |
4 |
Pre-existing ``Secret`` (GitOps) |
ArgoCD / Flux + SealedSecrets / sops-encrypted manifests. |
5 |
External Secrets Operator |
Production with AWS Secrets Manager / Azure Key Vault / GCP Secret Manager / Vault / 1Password. |
All five paths converge on the same chart contract: the Deployment uses envFrom: secretRef: to bulk-import every key in the Secret as an environment variable. Whatever ZSCALER_MCP_* / ZSCALER_* variable you put in your .env (or remote-secret backend) flows into the container without translation.
Quick Start¶
1. Interactive guided install via helm_mcp_operations.py (recommended)¶
The chart ships an interactive deployment script that mirrors the GCP / Azure ones. It reads your existing .env, materialises it into a Kubernetes Secret, runs helm upgrade --install, waits for the rollout, starts kubectl port-forward, and writes Cursor / Claude Desktop entries automatically.
python integrations/helm-chart/helm_mcp_operations.py deploy
You’ll be asked, in order: which kubectl context to target, the path to your .env file (defaults to the project root), namespace name, Helm release name, image tag, and how to expose the endpoint (port-forward / Ingress / none).
Follow-up commands:
python integrations/helm-chart/helm_mcp_operations.py status # release + pods + svc + port-forward
python integrations/helm-chart/helm_mcp_operations.py logs # tail Deployment logs
python integrations/helm-chart/helm_mcp_operations.py configure # re-write Cursor / Claude configs
python integrations/helm-chart/helm_mcp_operations.py test # run `helm test` smoke probe
python integrations/helm-chart/helm_mcp_operations.py destroy # uninstall + optional ns deletion
2. Manual install with raw helm + an existing .env¶
kubectl create namespace zscaler-mcp
kubectl -n zscaler-mcp create secret generic zscaler-mcp-creds \
--from-env-file=/path/to/.env
helm install zscaler-mcp \
./integrations/helm-chart/charts/zscaler-mcp-server \
--namespace zscaler-mcp \
--set secret.create=false \
--set secret.existingName=zscaler-mcp-creds
kubectl -n zscaler-mcp rollout status deployment/zscaler-mcp-zscaler-mcp-server
kubectl -n zscaler-mcp port-forward svc/zscaler-mcp-zscaler-mcp-server 8000:80 &
curl http://localhost:8000/health
3. Local dev with inline --set credentials¶
Not for production.
helm install zscaler-mcp \
./integrations/helm-chart/charts/zscaler-mcp-server \
--namespace zscaler-mcp --create-namespace \
--set secret.values.clientId=$ZSCALER_CLIENT_ID \
--set secret.values.clientSecret=$ZSCALER_CLIENT_SECRET \
--set secret.values.vanityDomain=$ZSCALER_VANITY_DOMAIN \
--set secret.values.customerId=$ZSCALER_CUSTOMER_ID
4. Production with pre-existing Secret (GitOps-friendly)¶
Create the Secret out-of-band (External Secrets Operator, Vault Agent Injector, SealedSecrets — your choice). The chart will reference it by name:
kubectl create namespace zscaler-mcp
kubectl -n zscaler-mcp create secret generic zscaler-mcp-creds \
--from-literal=ZSCALER_CLIENT_ID="$ZSCALER_CLIENT_ID" \
--from-literal=ZSCALER_CLIENT_SECRET="$ZSCALER_CLIENT_SECRET" \
--from-literal=ZSCALER_VANITY_DOMAIN="$ZSCALER_VANITY_DOMAIN" \
--from-literal=ZSCALER_CUSTOMER_ID="$ZSCALER_CUSTOMER_ID"
helm install zscaler-mcp \
./integrations/helm-chart/charts/zscaler-mcp-server \
--namespace zscaler-mcp \
--values - <<'EOF'
secret:
create: false
existingName: zscaler-mcp-creds
ingress:
enabled: true
className: nginx
hosts:
- host: zscaler-mcp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: zscaler-mcp-tls
hosts:
- zscaler-mcp.example.com
certificate:
enabled: true
secretName: zscaler-mcp-tls
commonName: zscaler-mcp.example.com
dnsNames:
- zscaler-mcp.example.com
issuerRef:
name: letsencrypt-production
kind: ClusterIssuer
EOF
5. Production with External Secrets Operator¶
Assumes ESO is installed and a ClusterSecretStore is wired to your secret backend (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault, 1Password, etc.).
# external-secret.yaml — apply this BEFORE helm install
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: zscaler-mcp-creds
namespace: zscaler-mcp
spec:
refreshInterval: 1h
secretStoreRef:
name: my-cluster-secret-store
kind: ClusterSecretStore
target:
name: zscaler-mcp-creds
data:
- secretKey: ZSCALER_CLIENT_ID
remoteRef: { key: zscaler/mcp/client_id }
- secretKey: ZSCALER_CLIENT_SECRET
remoteRef: { key: zscaler/mcp/client_secret }
- secretKey: ZSCALER_VANITY_DOMAIN
remoteRef: { key: zscaler/mcp/vanity_domain }
- secretKey: ZSCALER_CUSTOMER_ID
remoteRef: { key: zscaler/mcp/customer_id }
kubectl apply -f external-secret.yaml
helm install zscaler-mcp \
./integrations/helm-chart/charts/zscaler-mcp-server \
--namespace zscaler-mcp \
--set secret.create=false \
--set secret.existingName=zscaler-mcp-creds
Configuration Reference¶
Every key below is also documented inline in charts/zscaler-mcp-server/values.yaml.
Image¶
Key |
Default |
Description |
|---|---|---|
|
|
Docker Hub repo. Override to point at a private mirror / Marketplace ECR. |
|
|
Docker Hub currently publishes only the |
|
|
Pin by digest ( |
|
|
|
|
|
Image pull Secrets for private registries. |
Service / Ingress / HTTPRoute¶
ingress.enabled and httproute.enabled are mutually exclusive — picking both fails the install with a clear error.
Key |
Default |
Description |
|---|---|---|
|
|
|
|
|
Service port. |
|
|
Container port — matches the MCP server default. |
|
|
Generate a |
|
|
e.g. |
|
|
Generate a Gateway API v1 HTTPRoute instead. |
|
|
Generate a cert-manager |
MCP runtime (mcp.*)¶
These map 1:1 to the ZSCALER_MCP_* env vars the server already reads.
Key |
Default |
Maps to |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MCP Client Configuration¶
Once the chart is installed, point your MCP client at the Service / Ingress hostname. The endpoint is /mcp.
Recommended — helm_mcp_operations.py configure¶
If you installed the chart via Quick Start path 1, the script already wrote the right entry into Cursor + Claude Desktop. To rebuild those configs at any time:
python integrations/helm-chart/helm_mcp_operations.py configure
Manually — derive the auth header from the cluster Secret¶
kubectl --namespace zscaler-mcp get secret zscaler-mcp-creds \
-o jsonpath='{.data.ZSCALER_CLIENT_ID}:{.data.ZSCALER_CLIENT_SECRET}' \
| base64 -d | tr -d '\n' | base64
Prefix the resulting string with the literal Basic (followed by a space) to form the Authorization header value.
Port-forwarded local dev (no Ingress)¶
kubectl -n zscaler-mcp port-forward svc/zscaler-mcp-zscaler-mcp-server 8000:80
# Then point your MCP client at http://localhost:8000/mcp
Operations¶
Smoke-test the install:
helm test zscaler-mcp -n zscaler-mcp
Inspect the rendered manifests without installing:
helm template zscaler-mcp \
./integrations/helm-chart/charts/zscaler-mcp-server \
--set secret.create=false \
--set secret.existingName=zscaler-mcp-creds \
--set ingress.enabled=true \
--set ingress.className=nginx
Upgrade in place:
helm upgrade zscaler-mcp \
./integrations/helm-chart/charts/zscaler-mcp-server \
-n zscaler-mcp \
-f my-values.yaml
Uninstall:
helm uninstall zscaler-mcp -n zscaler-mcp
Troubleshooting¶
Symptom |
Likely cause / Fix |
|---|---|
|
Pick one and set the other to |
|
Set |
Pod CrashLoopBackOff with |
Confirm |
|
Auth header missing or wrong format. |
MCP client sees zero tools |
Entitlement filter trimmed everything. Your OneAPI client isn’t entitled to the loaded toolsets. Either request entitlements, or set |
For deeper debugging:
kubectl -n zscaler-mcp logs deploy/zscaler-mcp-zscaler-mcp-server --tail=200 -f
kubectl -n zscaler-mcp describe pod -l app.kubernetes.io/name=zscaler-mcp-server
kubectl -n zscaler-mcp get events --sort-by='.lastTimestamp' | tail -30
For the full chart contract — every value key, every template, the deployer script reference, GitOps integration recipes, and the complete troubleshooting matrix — see integrations/helm-chart/README.md.