@setra06/mcp-cnpg-axians
v1.0.2
Published
MCP server for CloudNativePG (CNPG 1.24+/1.29). 67 tools covering cluster lifecycle, backups & restore, declarative Database/Publication/Subscription CRDs, ImageCatalog upgrades, PgBouncer pooler, hibernation, switchover/promote, multi-context, and observ
Maintainers
Readme
CloudNativePG MCP Server by Axians Data Management
A Model Context Protocol (MCP) server for managing CloudNativePG PostgreSQL clusters through Claude Code, Claude Desktop, and other MCP clients.
Developed by Axians Data Management for the Kubernetes and PostgreSQL community.
Features
Coverage spans the CNPG operator surface up to 1.29:
- Cluster lifecycle: create, delete, scale, restart, reload, hibernate/resume, switchover, force-promote, in-place image upgrades.
- Declarative database management (CNPG 1.24+
DatabaseCRD): databases, owners, encodings, extensions, schemas — operating on live clusters, not just at bootstrap. - Logical replication (CNPG 1.24+):
PublicationandSubscriptionCRDs. - Backups & restore: on-demand
Backup,ScheduledBackuplifecycle, PITR viarestore_cluster,barmanObjectStoreconfig (S3-compatible). - Replica clusters: streaming replicas with proper
externalClusterswiring. - Image catalogs (CNPG 1.29):
ImageCatalogCRUD plususe_image_catalogto switch a cluster fromimageNameto a catalog ref. - Pooler (PgBouncer): create, list, get, delete.
- Sync replication via
spec.minSyncReplicas/spec.maxSyncReplicas(the operator managessynchronous_standby_names). - Observability: status, pods, events, logs, certificate secrets, connection info (rw/ro/r service hostnames + credential secret refs).
Technical
- Native
@kubernetes/client-node(nokubectldependency on the host). - Bearer-token auth (or default kubeconfig fallback for local dev).
- Per-resource modules under
src/tools/*.ts(clusters,backups,databases,replication,poolers,observability,image_catalogs). - Smoke test (
npm run test:smoke) — 18 end-to-end checks against a live CNPG cluster.
Installation
For Claude Desktop Users
Add this to your claude_desktop_config.json:
{
"mcpServers": {
"cnpg": {
"command": "npx",
"args": ["@setra06/mcp-cnpg-axians"],
"env": {
"K8S_API_URL": "https://your-k8s-api-server.com",
"K8S_TOKEN": "your_bearer_token_here"
}
}
}
}Prerequisites
- Node.js 18+
- Access to a Kubernetes cluster with CloudNativePG installed
- Kubernetes API bearer token
Configuration
| Environment Variable | Description | Required |
|---------------------|-------------|----------|
| K8S_API_URL | Kubernetes API server URL | Yes (or fall back to ~/.kube/config) |
| K8S_TOKEN | Bearer token for authentication | Yes (with K8S_API_URL) |
| K8S_CA_CERT | Path to a CA file, base64-encoded PEM, or inline PEM. Used to verify the API server's certificate. | No |
| K8S_SKIP_TLS_VERIFY | Set to true to disable TLS verification (lab self-signed clusters only). Default: false since v3.1.0. | No |
| READ_ONLY | Set to true to filter all mutating tools from the server's surface at startup. The remaining read-only tools and the get_server_mode tool are exposed. Pairs naturally with the cnpg-mcp-reader ClusterRole. | No |
| K8S_CONTEXTS | JSON array of context descriptors for multi-cluster mode. Example: [{"name":"prod","apiUrl":"https://prod","tokenEnv":"PROD_TOKEN","caCertEnv":"PROD_CA"},{"name":"staging","kubeconfigPath":"/etc/staging.kubeconfig"}]. When set, every tool accepts an optional context: string arg; list_contexts enumerates them. When unset, single-context fallback uses K8S_API_URL/K8S_TOKEN/K8S_CA_CERT (or default kubeconfig). | No |
| TRANSPORT | stdio (default) or http. Selects the MCP transport at startup. | No |
| MCP_HTTP_HOST | Bind address for HTTP transport. Default 127.0.0.1. Set to 0.0.0.0 to expose the server outside localhost. | No |
| MCP_HTTP_PORT | TCP port for HTTP transport. Default 3000. | No |
| MCP_HTTP_PATH | URL path served by the HTTP transport. Default /mcp. | No |
| MCP_HTTP_TOKEN | When set, every HTTP request must carry Authorization: Bearer <token>. Compared with constant time. When unset, the endpoint is open — only use that behind a trusted reverse proxy or on a private network. | No |
HTTP transport
The server also speaks the official MCP Streamable HTTP transport (single endpoint, stateful sessions). Useful for sidecar deployments inside Kubernetes, agentic IDE plugins, or any client that can't spawn a subprocess.
TRANSPORT=http \
MCP_HTTP_HOST=0.0.0.0 \
MCP_HTTP_PORT=3000 \
MCP_HTTP_TOKEN=$(openssl rand -hex 32) \
K8S_API_URL=https://your-k8s-api-server.com \
K8S_TOKEN=your_bearer_token \
npx @setra06/mcp-cnpg-axiansThe server exposes one endpoint at MCP_HTTP_PATH (default /mcp):
POST /mcp— JSON-RPC requests. The first call must beinitialize; the response sets aMcp-Session-Idheader. Subsequent calls must echo that header back.GET /mcp— opens an SSE stream for server-initiated notifications on the current session.DELETE /mcp— terminates the session.
Each session runs its own Server instance, so concurrent clients are isolated. Sessions are kept in memory only — restart the process and clients must re-initialize.
Claude Desktop / mcp-inspector / custom clients
For clients that support remote MCP servers, point them at http(s)://<host>:<port>/mcp and pass the bearer token in Authorization: Bearer <MCP_HTTP_TOKEN>. For clients that only support stdio (older Claude Desktop builds), keep TRANSPORT=stdio and use the existing command: npx config.
Getting a Bearer Token
# For service account token
kubectl create serviceaccount cnpg-mcp
kubectl get secret $(kubectl get sa cnpg-mcp -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 -dContainer image
A pre-built multi-arch image (linux/amd64, linux/arm64) is published to GitHub Container Registry on every release. Use it whenever you need to run the server inside Kubernetes instead of relying on npx.
docker pull ghcr.io/setra06/mcp-cnpg-axians:latest
# Or pin to an exact release:
docker pull ghcr.io/setra06/mcp-cnpg-axians:1.0.1Tags published:
:latestand:X.Y.Z/:X.Y/:X— release tags (push ofvX.Y.Z):edge— every commit onmain
Why a container image instead of
npx -y? npm 11.6.x has a regression innpm exec/npxwhere it fetches package metadata then exits silently without installing or launching the binary. The image bypasses that path entirely —node:22-alpineships npm 10.x, deps are pre-installed at build time, and the entrypoint is a directnode dist/index.js.
The container is configured by the same environment variables as the npm package — see the Configuration table. Quick stdio sanity check:
docker run --rm -i \
-e K8S_API_URL=https://your-k8s-api-server.com \
-e K8S_TOKEN=your_bearer_token \
ghcr.io/setra06/mcp-cnpg-axians:latestDeploy on Kubernetes (kagent)
The repository ships ready-to-apply manifests under deploy/kagent/ that deploy this MCP server as a kagent MCPServer custom resource — exposed as an HTTP tool server for in-cluster AI agents.
Prerequisites
- A Kubernetes cluster (1.24+) with kagent installed (provides the
kagent.dev/v1alpha1MCPServerCRD and controller). - Pull access to
ghcr.io/setra06/mcp-cnpg-axians(see Private vs public image). kubectlconfigured against the target cluster.
Quick start
git clone https://github.com/setra06/mcp-cnpg-axians.git
cd mcp-cnpg-axians/deploy/kagent
./install.shThe script applies RBAC, mints the SA token, injects it into the MCPServer CR, waits for the pod to become Ready, and probes POST /mcp to confirm the server responds. Override defaults with NAMESPACE=... / MCP_NAME=.... Remove everything with ./uninstall.sh.
Architecture
kagent runs each MCPServer as a pod with two containers: an agentgateway sidecar (Rust, port 3000) that exposes the official Streamable HTTP transport, and the MCP server itself (here, node dist/index.js) which the gateway speaks to over stdio.
AI agent / kagent controller Pod (managed by kagent MCPServer CR)
──────────────────────────── ─────────────────────────────────────
┌─────────────────────┐
│ agentgateway │ HTTP /mcp on :3000
HTTP POST /mcp ─────────────────▶ │ (Rust sidecar) │ ◀── exposed via
└──────────┬──────────┘ ClusterIP svc
│ stdio
┌──────────▼──────────┐
│ node dist/index.js │ this repo, 67 tools
└──────────┬──────────┘
│ K8s API (CNPG CRDs)
▼
CloudNativePGMCPServer fields
| Field | Value | Notes |
|---|---|---|
| spec.transportType | stdio | kagent talks stdio to the container ↔ exposes HTTP outward. Do not set TRANSPORT=http here — the gateway handles HTTP. |
| spec.deployment.image | ghcr.io/setra06/mcp-cnpg-axians:main | Pin to a semver tag in prod (:v1.0.1). |
| spec.deployment.cmd / args | node / ["dist/index.js"] | Direct exec form — no shell, signals propagate. |
| spec.deployment.port | 3000 | Port the agentgateway listens on. |
| spec.deployment.env.K8S_API_URL | https://kubernetes.default.svc | In-cluster API endpoint. |
| spec.deployment.env.K8S_CA_CERT | /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | Auto-mounted by the projected SA volume. |
| spec.deployment.env.K8S_TOKEN | (injected by install.sh) | JWT of the cnpg-mcp-server SA. |
| spec.timeout | 30s | Per-request timeout the gateway enforces upstream. |
In-cluster Kubernetes authentication
Three values together cover in-cluster auth:
K8S_API_URL=https://kubernetes.default.svc— the API server's well-known DNS name inside any pod.K8S_CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt— the projected CA bundle, mounted on every pod that has a SA.K8S_TOKEN=<JWT>— the JWT of thecnpg-mcp-serverServiceAccount, sourced from thecnpg-mcp-server-tokenSecret.install.shinjects it into the CR at install time.
The MCP server reads these from the environment and uses them to build an out-of-cluster-style client even when running inside the pod — this keeps the auth path identical between local dev and in-cluster deployments and lets RBAC changes take effect by re-running install.sh.
Private vs public image
A package published to GHCR from a public repo is not public by default — visibility is set per-package, not inherited from the repo. Two options:
(a) Make the package public (one-time):
- Go to
https://github.com/users/setra06/packages/container/mcp-cnpg-axians - Package settings → Change visibility → Public.
(b) Pull from a private registry with a docker-registry Secret:
kubectl create secret docker-registry ghcr-pull \
--namespace kagent \
--docker-server=ghcr.io \
--docker-username=<github-username> \
--docker-password=<PAT with read:packages scope>
kubectl patch serviceaccount cnpg-mcp-server -n kagent \
-p '{"imagePullSecrets":[{"name":"ghcr-pull"}]}'Troubleshooting
connection refused 10.x.x.x:3000inkagent-controllerlogs — the controller raced ahead of pod startup. Force a reconcile:kubectl annotate mcpserver setra06-mcp-cnpg-axians -n kagent \ "kagent.dev/reconcile-trigger=$(date +%s)" --overwritePOST /mcpreturns 30s timeout when the deployment usescmd: ["npx", "-y", "@setra06/mcp-cnpg-axians"]— known npm 11.6.x regression innpm exec/npx: the metadata fetch completes, then npm exits silently without installing or launching the binary. The pre-built image bypasses this entirely. Do not build a DIY image based onnode:24withcmd: ["npx", "-y", ...]— it will hang on every cold start. Use the published image (or build your own fromnode:22-alpinewith the package pre-installed).ImagePullBackOffwith401 Unauthorized— the GHCR package is still private. Either make it public or attach an image-pull Secret (see Private vs public image).POST /mcpreturns 400Bad Request: missing session ID— every call afterinitializemust echo back theMcp-Session-Idheader. The install probe handles this; client-side bugs in third-party MCP clients sometimes don't.
Post-deployment verification
kubectl get mcpserver setra06-mcp-cnpg-axians -n kagent \
-o jsonpath='{.status.conditions}' | jq .
POD=$(kubectl get pod -n kagent \
-l app.kubernetes.io/instance=setra06-mcp-cnpg-axians \
-o name | head -1)
kubectl logs -n kagent "$POD" -c mcp-server --tail=30The MCP container should print on startup:
CloudNativePG MCP server v3.6.0 running on stdio (67 tools, mode=full, single-context (default))Hardening for production
- Pin the image to a semver tag (
:v1.0.1), not:main. The latter rolls forward on every commit tomain. - Run read-only when possible — apply
cnpg-mcp-readerinstead ofcnpg-mcp-manager(swaproleRef.nameindeploy/kagent/rbac.yaml) AND setenv.READ_ONLY: "true"on the MCPServer. RBAC is the real boundary;READ_ONLYis a second layer that filters the surface clients see. K8S_TOKENends up in cleartext on the MCPServer CR because kagent's CRD does not currently acceptsecretKeyRefonspec.deployment.env. Restrictget/list/watchonmcpservers.kagent.devin thekagentnamespace via RBAC, and audit who cankubectl get mcpserver -o yaml.- Add a NetworkPolicy that only allows ingress to the MCPServer pod from kagent's own controller and agent pods. The pod holds cluster-wide RBAC against the CNPG CRDs; a leaked HTTP endpoint is a serious attack surface.
Available Tools (67)
Cluster lifecycle (src/tools/clusters.ts)
list_clusters · get_cluster · create_cluster · delete_cluster · scale_cluster · patch_cluster_config · switchover_primary · promote_replica · pause_cluster · resume_cluster · restart_cluster · reload_config · upgrade_postgres_version
Backup & restore (src/tools/backups.ts)
create_backup (with ifNotExists) · list_backups · get_backup_details · get_backup_status · delete_backup · restore_cluster · create_scheduled_backup · list_scheduled_backups · delete_scheduled_backup · configure_object_store (with replace=false for no-op) · wipe_object_store_path (deletes objects under the cluster's barman path; confirm-protected)
Declarative databases (src/tools/databases.ts, CNPG 1.24+)
list_databases · get_database · create_database · delete_database · manage_extensions · manage_schemas
Replication (src/tools/replication.ts)
create_replica_cluster · set_synchronous_replication · list_publications · create_publication · delete_publication · list_subscriptions · create_subscription · delete_subscription · get_replication_status · register_external_cluster · unregister_external_cluster · setup_logical_subscription (composite)
PgBouncer pooler (src/tools/poolers.ts)
list_poolers · get_pooler · create_pooler · delete_pooler
Observability (src/tools/observability.ts)
get_cluster_status · get_cluster_pods · get_cluster_logs · get_cluster_events · get_cluster_metrics (real Prometheus scrape, :9187/metrics) · get_cluster_pod_resources · get_cluster_certificates · get_connection_info
Image catalogs (src/tools/image_catalogs.ts, CNPG 1.29+)
list_image_catalogs · create_image_catalog · use_image_catalog · delete_image_catalog
Waits (src/tools/waits.ts)
wait_for_cluster (phase / readyInstances) · wait_for_backup · wait_for_database · wait_for_pooler
Meta (src/tools/meta.ts, src/index.ts)
get_server_mode — reports full or readonly and the list of excluded mutating tools when READ_ONLY=true. · list_contexts — when K8S_CONTEXTS is configured, lists the available named contexts. Every other tool accepts an optional context: string argument to target a specific cluster.
Operations (src/tools/operations.ts, kubectl-cnpg parity)
get_cluster_overview (phase + pods + events + last backup + cert expiry, in one call) · hibernate_dump (re-applyable JSON of the cluster + secrets) · run_sql (psql via pods/exec; read-only by default — wraps in BEGIN READ ONLY; ...; COMMIT;)
Usage
Drive the server in natural language from any MCP client. A few representative prompts:
- "List all PostgreSQL clusters across namespaces."
- "Create a 3-instance cluster called
analyticsindata, PostgreSQL 17, 50Gi." - "Switch the primary of
prodto podprod-2for the maintenance window." - "Hibernate the
devcluster, then resume it on Monday." - "Add the
pgcryptoandpg_stat_statementsextensions to theappDatabase indata." - "Configure barmanObjectStore on
prodpointing ats3://backups/prodwith the existings3-credssecret." - "Create a Publication for all tables in db
appon clusterprod, then a Subscription on clusteranalyticsconsuming it." - "Show me the connection info for
prodand include credentials."
The full per-tool schema is exposed by the standard MCP list_tools capability — descriptions explain when to use each tool and what its arguments do.
Examples
Basic Usage
# Test the server locally
npx @setra06/mcp-cnpg-axiansAdvanced Configuration
{
"mcpServers": {
"cnpg-production": {
"command": "npx",
"args": ["@setra06/mcp-cnpg-axians"],
"env": {
"K8S_API_URL": "https://prod-k8s.company.com",
"K8S_TOKEN": "prod_token_here"
}
},
"cnpg-staging": {
"command": "npx",
"args": ["@setra06/mcp-cnpg-axians"],
"env": {
"K8S_API_URL": "https://staging-k8s.company.com",
"K8S_TOKEN": "staging_token_here"
}
}
}
}Troubleshooting
Common Issues
- Authentication errors: Verify your K8S_TOKEN has proper RBAC permissions
- Connection refused: Check K8S_API_URL is accessible from your machine
- No clusters found: Ensure CloudNativePG is installed in your cluster
Required RBAC
Ready-to-apply manifests live in deploy/kagent/rbac.yaml. They define a ServiceAccount, two ClusterRoles (cnpg-mcp-manager for full read/write on CNPG CRDs, cnpg-mcp-reader for read-only), a ClusterRoleBinding, and a static SA token Secret. Default namespace is kagent to match the kagent deployment story — change the namespace: fields and the ClusterRoleBinding's subjects[0].namespace if you deploy elsewhere.
Development
git clone https://github.com/setra06/mcp-cnpg-axians.git
cd mcp-cnpg-axians
npm install
npm run build
npm startSmoke test
test/smoke.ts exercises 18 tool calls (cluster lifecycle, Database CRD, extensions, pooler, observability) against a live CNPG cluster. Requires a kubeconfig with permissions to create resources in the test namespace.
KUBECONFIG=~/.kube/config TEST_NAMESPACE=cnpg-mcp-test TEST_CLUSTER=mcptest npm run test:smokeContributing
We welcome contributions! Please see our Contributing Guide.
About Axians Data Management
This project is developed by Axians Data Management, part of Axians (a VINCI Energies brand), specializing in ICT solutions and services.
Support
- Report Issues: https://github.com/setra06/mcp-cnpg-axians/issues
- Discussions: https://github.com/setra06/mcp-cnpg-axians/discussions
- Email: [email protected]
License
MIT
