Self-hosting
Self-hosting8 min

Kubernetes install with Helm

Run an HA MCPG fleet on Kubernetes with the operator + Helm charts. NATS or Redis for shared state, plugin sets via CRD.

This guide installs MCPG on Kubernetes for production: the operator, an HA gateway deployment, and shared state via NATS (or Redis). Target audience: SREs and platform teams.

Prerequisites

  • Kubernetes 1.26 or newer
  • Helm 3.x
  • A namespace to deploy into (we'll use mcpg)
  • Either NATS or Redis available in-cluster (the gateway chart can deploy NATS as a subchart)

1. Install the operator

The operator reconciles eight CRDs (API group mcpg.dev, version v1alpha1): MCPGGateway, MCPGPlugin, MCPGPluginSet, MCPGRevocationList, MCPGCluster, MCPGRoute, MCPGTenant, and MCPGPluginMirror.

bash
kubectl create namespace mcpg
helm install mcpg-operator oci://ghcr.io/mcpg-dev/source-code/charts/mcpg-operator --version 0.1.0 -n mcpg \
  --set rbac.create=true \
  --set webhook.enabled=true

The operator serves admission webhooks on :9443 and its metrics / health endpoint on :8443. Verify:

bash
kubectl -n mcpg get pods
# mcpg-operator-7d8c4b9b8-xj2pq   1/1   Running

2. (Optional) Install the control plane

The control plane is optional for single-instance and operator-managed deployments; install it when you want the fleet dashboard + agent registration.

bash
helm install mcpg-cp oci://ghcr.io/mcpg-dev/source-code/charts/mcpg-control-plane --version 0.1.0 -n mcpg \
  --set postgresql.enabled=true \
  --set auth.mode=oidc \
  --set auth.oidc.issuer=https://idp.example.com \
  --set ingress.enabled=true \
  --set ingress.hosts[0].host=mcpg-cp.example.com

The CP serves its HTTP API + dashboard on 7843 and the gRPC agent endpoint on 7844. With postgresql.enabled=true it deploys a Postgres subchart for state; auth.mode is one of none / oidc / mock.

3. Install a gateway fleet

Use the mcpg chart with autoscaling, a PDB, and the bundled NATS subchart for shared state:

bash
helm install gw oci://ghcr.io/mcpg-dev/source-code/charts/mcpg --version 0.1.0 -n mcpg \
  --set replicaCount=3 \
  --set autoscaling.enabled=true \
  --set autoscaling.minReplicas=3 \
  --set autoscaling.maxReplicas=10 \
  --set podDisruptionBudget.enabled=true \
  --set nats.enabled=true

With nats.enabled=true the chart auto-wires cluster.kind: nats so any replica can answer any session. The gateway's own config block is rendered into config.yaml; override it via the chart's config: / extraConfig: values, or manage it declaratively through the MCPGGateway CRD (next section).

4. Manage the gateway via the MCPGGateway CRD

For GitOps-managed deployments, drive the gateway through the operator. The spec.config field is the gateway's own AppConfig schema verbatim (snake_case, deny_unknown_fields):

yaml
apiVersion: mcpg.dev/v1alpha1
kind: MCPGGateway
metadata:
  name: gw
  namespace: mcpg
spec:
  replicas: 3
  image:
    repository: ghcr.io/mcpg-dev/source-code/gateway
    tag: v1.2.3
  pluginSetRef:
    name: production
  config:
    gateway:
      server:
        bind_address: "0.0.0.0:8787"
        allowed_origins:
          - "https://gateway.example.com"
    governance:
      access:
        jwks:
          url: "https://idp.example.com/.well-known/jwks.json"
          audience: "mcpg-prod"
    mcp:
      capabilities:
        tools:
          - name: api.account.lookup
            description: Look up an account by id.
            backend:
              kind: http
              url: "https://accounts.internal.example.com/v1/accounts/${arguments.account_id}"
              method: get
              expected_status_codes: [200]
  autoscaling:
    enabled: true
    minReplicas: 3
    maxReplicas: 10

Tools live under spec.config.mcp.capabilities.tools[] — the same AppConfig shape the standalone gateway boots from. Validate any inline config with mcpg config check before committing it.

5. Bind a plugin set

Plugins are distributed and bound declaratively. A plugin set lists entries that reference cluster-scoped MCPGPlugin artifacts:

yaml
apiVersion: mcpg.dev/v1alpha1
kind: MCPGPluginSet
metadata:
  name: production
  namespace: mcpg
spec:
  entries:
    - id: dev.mcpg.identity.oidc
      pluginRef:
        name: identity-oidc-1.0.0-linux-amd64
      enabled: true
    - id: dev.mcpg.policy.cedar
      pluginRef:
        name: policy-cedar-1.0.0-linux-amd64
      enabled: true
      config:
        policy_dir: "/etc/mcpg/policies"
    - id: dev.mcpg.rate-limit
      pluginRef:
        name: rate-limit-1.0.0-linux-amd64
      enabled: true
      config:
        requests_per_minute: 100
  capabilityGrants:
    dev.mcpg.identity.oidc:
      - network_outbound

Apply, then bind it to the gateway:

bash
kubectl apply -f plugin-set.yaml
kubectl patch mcpggateway gw -n mcpg --type=merge \
  -p '{"spec":{"pluginSetRef":{"name":"production"}}}'

The operator reconciles, materialises each plugin's projected Secret, and the gateway pods reload with the rendered plugins[] config.

Cluster state options

BackendPick when
NATS JetStream (nats.enabled=true / cluster.kind: nats)You don't already run Redis. JetStream gives KV + pub/sub + leases in one component.
Redis (redis.enabled=true / cluster.kind: redis)You already run Redis. Lower operational overhead.
Built-in single-node (cluster.kind: single_node)Single-instance only. Don't use for HA.

For multi-tenant clusters, add the MCPGTenant and MCPGRoute CRDs — see the multi-tenant guide.

What's next