Deploying with Helm
The chart at
charts/ami-mcp
deploys ami-mcp over HTTP transport on Kubernetes. It supports the two
hosted auth models the server implements, selected by auth.mode:
sharedSecret— a single env-built pyAMI client: the pod carries one server-managed VOMS proxy (mounted from a Secret you refresh out-of-band) and gates every request behind a static bearer token.broker— the AF credential broker: bearers are broker-issued identity JWTs verified against the broker's JWKS, and each AMI call redeems the caller's own VOMS proxy at the broker. The server holds no AMI credential itself, so no proxy is mounted.
There is no published ami-mcp image. An init container runs the
ghcr.io/prefix-dev/pixi image and installs the pinned amiMcp.version from
conda-forge into a shared volume; the main container then runs ami-mcp serve.
The conda-forge package already pulls in ca-policy-lcg (which sets
X509_CERT_DIR on environment activation), voms, voms-lsc, and the broker
client library.
Prerequisites
- A Kubernetes cluster and Helm 3+.
- An ingress controller and (for TLS) cert-manager, if
ingress.enabled(the default). Defaults assumenginx+ aletsencrypt-prodClusterIssuer.
Shared-secret mode (server-managed VOMS proxy)
First create the Secrets the pod consumes — the chart never holds the x509 material itself. Provide a VOMS proxy in a Secret you manage and refresh out-of-band (proxies are short-lived; the chart only consumes the Secret):
voms-proxy-init -voms atlas
kubectl -n mcp create secret generic ami-x509 \
--from-file=proxy=/tmp/x509up_u$(id -u)
Then install:
helm install ami-mcp ./charts/ami-mcp \
--namespace mcp --create-namespace \
--set ingress.host=ami-mcp.example.com \
--set auth.sharedSecret.x509.existingSecret=ami-x509 \
--set auth.sharedSecret.x509.proxyKey=proxy \
--set auth.sharedSecret.secretValue="$(openssl rand -hex 32)"
Notes:
- The proxy is mounted read-only and exported as
X509_USER_PROXY. pyAMI reads only a proxy from the environment — extra keys in the Secret (e.g. a robot cert/key used by an out-of-band refresher) are mounted alongside but not wired to any env var. - The CA trust bundle (
X509_CERT_DIR) is set automatically byca-policy-lcg, which ami-mcp already depends on. To force the latest CA bundle, add it to the renderedpixi.toml:--set amiMcp.extraPixiDependencies.ca-policy-lcg='*'. - Prefer
auth.sharedSecret.existingSecret(a Secret you create with keyshared-secret) oversecretValuein production so the token never lands in Helm values or CI logs. The bearer always reaches the server via theAMI_MCP_SHARED_SECRETenv var, never argv, keeping it out of the process table.
Retrieve the generated bearer and configure clients with it out-of-band:
kubectl -n mcp get secret ami-mcp-shared-secret \
-o jsonpath='{.data.shared-secret}' | base64 -d; echo
{
"mcpServers": {
"ami": {
"type": "http",
"url": "https://ami-mcp.example.com/mcp/",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
Broker mode (per-user VOMS proxies)
helm install ami-mcp ./charts/ami-mcp \
--namespace mcp --create-namespace \
--set ingress.host=ami-mcp.example.com \
--set auth.mode=broker \
--set auth.broker.brokerUrl=https://broker.af.example.com
The broker URL rides the AMI_MCP_BROKER_URL env var; auth.broker.jwksUrl and
auth.broker.issuer default server-side to values derived from the broker URL,
and auth.broker.audience defaults to ami. No x509 Secret is mounted in this
mode — per-user proxies are redeemed per call and disposed of immediately.
PMG cross-section database (CVMFS)
The xsec tools read PMGxsecDB_<campaign>.txt files from ATLAS_PMGXSEC_PATH
(see Configuration). Off by default — without a mount the
xsec tools report the path as unavailable, while all AMI query tools still work.
Provide the files by passing CVMFS through the generic
extraVolumes/extraVolumeMounts values, which are rendered verbatim into the
pod spec and the main container (any volume source works — PVC, hostPath, CSI,
Secret, ...; fields like mountPropagation pass through untouched):
ami:
pmgXsecPath: /cvmfs/atlas.cern.ch/repo/sw/database/GroupData/dev/PMGTools
extraVolumes:
- name: cvmfs
persistentVolumeClaim:
claimName: cvmfs
extraVolumeMounts:
- name: cvmfs
mountPath: /cvmfs
mountPropagation: HostToContainer
Monitoring
ami-mcp exposes no /metrics endpoint yet, so the chart ships no ServiceMonitor
or Grafana dashboard. They will be added once the server grows a Prometheus
endpoint.
Security context
podSecurityContext and securityContext (applied to both the init container
and the main container) are rendered as-is via toYaml. The defaults drop all
Linux capabilities and disable privilege escalation, but stop short of
runAsNonRoot/readOnlyRootFilesystem: the init container installs packages
with pixi install and the main container runs pixi shell-hook, both of which
currently expect the pixi image's default (root) user and may write outside the
/workspace emptyDir. See the commented-out suggestions in values.yaml if
you've verified your pixi image tolerates a stricter profile.
Freezing the deployed version
The chart ships pixi.toml (pinning amiMcp.version) but no pixi.lock —
a release must exist before it can be locked, and pixi install otherwise
resolves dependencies fresh at each pod start. For reproducible rollouts,
generate a lock against the rendered pixi.toml and feed it back in:
helm template a ./charts/ami-mcp --show-only templates/configmap.yaml \
--set ingress.host=ami-mcp.example.com \
--set auth.sharedSecret.secretValue=dummy \
| yq '.data["pixi.toml"]' > /tmp/pixi.toml
pixi lock --manifest-path /tmp/pixi.toml # writes /tmp/pixi.lock alongside it
helm upgrade ami-mcp ./charts/ami-mcp --reuse-values \
--set-file amiMcp.pixiLockContent=/tmp/pixi.lock
When amiMcp.pixiLockContent is set, the lock is mounted next to pixi.toml
and pixi installs from it.
Verifying and validating the chart
pixi run -e helm helm-lint # lint + render both auth modes and the CVMFS passthrough
pixi run -e helm helm-template # render with default (shared-secret) values
helm test ami-mcp -n mcp # run the /healthz test hook against a live release
Uninstall
Secrets you created yourself (the x509 Secret, and any existingSecret for the
bearer) are not owned by the release and must be removed separately.