Installation
About fifteen minutes, most of it waiting for pip. The short version is three commands; the rest of this page explains what each one does and how to vary it.
make install
make node-setup SSH_HOST=you@your-node
make register SSH_HOST=you@your-node
1. Decide where the server runs
Pick this first, because it decides which machine you run the later steps on.
Topology A — server on your workstation (recommended)
workstation microk8s node
claude CLI ── stdio ── mcp server ── :16443 ── kube-apiserver
└─ ssh ───── microk8s snap
Everything installs on your workstation. The server holds a scoped kubeconfig and reaches
the API server over the network. SSH is only needed for microk8s_status and
manage_addon — skip it and everything else still works. Nothing is installed on
the node.
Topology B — server on the node
workstation microk8s node
claude CLI ── ssh ──────────────────────────── mcp server ── localhost
Install on the node itself. No SSH configuration needed; the node backend runs locally.
The rest of this page assumes topology A and calls out where B differs.
2. Prerequisites
On the machine that will run the server:
- Python 3.10 or newer
python3-venv— on Debian/Ubuntu:sudo apt install python3-venv- The
claudeCLI, if this is where you run Claude Code kubectlon PATH — strongly recommended, and the only path that honours the scoped kubeconfigsshon PATH, if you want addon management under topology A
On the MicroK8s node: a running cluster, which
microk8s status --wait-ready will confirm.
Install kubectl locally. Without it the server falls back to
microk8s kubectl on the node, which runs under MicroK8s' own admin
credential — the scoped kubeconfig and the bundled reader role stop applying, and Secrets
become readable again. The server logs a warning when this happens, but it is easy to miss.
More on this.
Match your cluster's minor version. No root required:
V=v1.26.15 # match `kubectl version` on the node
curl -fsSLO "https://dl.k8s.io/release/$V/bin/linux/amd64/kubectl"
curl -fsSLO "https://dl.k8s.io/release/$V/bin/linux/amd64/kubectl.sha256"
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
install -m 755 kubectl ~/.local/bin/kubectl
Check what you have — this installs nothing:
make doctor
It reports your Python version, whether the venv exists, and whether kubectl, claude, ssh
and the kubeconfig are present. Add SSH_HOST= and it also probes the node for
reachability, whether microk8s is on PATH in a non-interactive shell there, and
which API server address will be used.
3. Install the server
Two ways in. Clone the repository if you want the setup tooling with it — that is the path the rest of this guide follows. Install from PyPI if you only want the server binary.
From the repository (recommended)
git clone https://github.com/pierrejochem/microk8s-mcp
cd microk8s-mcp
make install
make check
make check should print an import ok line and the absolute path
to the entry point. Note that path — the registration needs it, and it must be absolute.
From PyPI
The package is published as
microk8s-mcp. Install it
into a virtualenv of its own — not system-wide, and not into a project venv you might delete:
python3 -m venv ~/.local/share/microk8s-mcp/venv
~/.local/share/microk8s-mcp/venv/bin/pip install microk8s-mcp
Check it starts. The server speaks MCP over stdin, so closing stdin makes it log its effective policy and exit — which is exactly the confirmation you want:
~/.local/share/microk8s-mcp/venv/bin/microk8s-mcp < /dev/null
starting microk8s-mcp: mode=read-only kubeconfig=(default) node_backend=local namespaces=*
The entry point is then
~/.local/share/microk8s-mcp/venv/bin/microk8s-mcp — that absolute path is what
you register in step 5.
The wheel is the server, not the setup. rbac.yaml,
make-kubeconfig.sh and the make targets are operator tooling and
deliberately stay in the repository. Installing from PyPI skips step 4 entirely, so you
still need those files — clone the repo anyway, or apply the RBAC and mint the kubeconfig
by hand on the node. You will also register with claude mcp add directly
rather than make register, since that target lives in the Makefile.
Given that, PyPI is the better choice when the cluster identity already exists — a second workstation, a rebuild, or an upgrade — and the repository is the better choice for a first install. Upgrading a PyPI install is the ordinary thing:
~/.local/share/microk8s-mcp/venv/bin/pip install --upgrade microk8s-mcp
Editable versus copy installs
The default is an editable install into ./.venv: the venv only links
back to this checkout, so your edits are live and the source has to stay put. That is ideal
while you are iterating, and a trap if you later move or delete the repository — the
registered server breaks, and if you registered at user scope it breaks in every project at once.
For an install that outlives the checkout, put the venv somewhere stable and copy the package in:
make install VENV=~/.local/share/microk8s-mcp/venv EDITABLE=0
EDITABLE=0 copies rather than links, so the result is self-contained. The
tradeoff is that source edits then need a reinstall. The target force-reinstalls on every run,
deliberately: pip treats an already-installed same-version package as satisfied and would
otherwise leave stale code in place while reporting success.
Pass the same VENV= to every target that needs it — check,
register and register-rw.
4. Give it a scoped identity
Do not hand the server your admin kubeconfig. Mint a credential bound to a purpose-built
ServiceAccount instead. Under topology A the node-* targets do all of this over
SSH from your workstation:
make node-setup SSH_HOST=you@your-node
That is the whole step. It runs, in order:
| Stage | What it does |
|---|---|
check-apiserver | Confirms the API server address resolves and answers from here |
node-check | SSH reachability, microk8s on PATH, kubectl runnable |
node-rbac | Streams rbac.yaml to microk8s kubectl apply -f - |
node-kubeconfig | Mints the kubeconfig on the node, writes it here at mode 600 |
It creates the mcp-system namespace, a claude-mcp ServiceAccount,
a long-lived token Secret, and a reader ClusterRole bound to it. The reader role grants
get/list/watch cluster-wide except Secrets — deliberately, so cluster
credentials can never land in a model context.
About the API server address
APISERVER defaults to https://<node>:16443, where the node
is SSH_HOST put through ssh -G. That resolution matters:
SSH_HOST is very often an alias from ~/.ssh/config, and kubectl does
not read that file. Given
Host k8s-node
HostName 10.0.0.5
make node-setup SSH_HOST=k8s-node bakes https://10.0.0.5:16443
into the kubeconfig rather than the unusable https://k8s-node:16443. Override it
when the node answers on a different address than you reach it at:
make node-setup SSH_HOST=you@10.0.0.5 APISERVER=https://k8s.example:16443
Either way the address is validated before anything is minted. Unresolvable is a hard stop; resolvable but unanswered is a warning, since the node may just be behind a VPN that is currently down. The kubeconfig is written to a temp file and checked before replacing the destination, so a dropped connection can never leave a truncated credential in place.
If you are already on the node (topology B)
make rbac
make kubeconfig APISERVER=https://k8s.example:16443
APISERVER is required here — there is no SSH_HOST to infer it from.
Applying rbac.yaml unchanged grants cluster-wide write access.
The claude-mcp-operator ClusterRoleBinding is active in the repository as shipped.
If you want read-only, comment it out before applying. The
security model page covers all three ways to shape that grant.
5. Register with the Claude CLI
Read-only, which is where you should start:
make register SSH_HOST=you@your-node
That fills in absolute paths — including MICROK8S_MCP_KUBECTL, which matters
because Claude Code launches the server with its own PATH. It expands to roughly:
claude mcp add --transport stdio microk8s \
--env MICROK8S_MCP_KUBECONFIG=$HOME/.kube/claude-mcp.kubeconfig \
--env MICROK8S_MCP_SSH_HOST=you@your-node \
-- /abs/path/to/microk8s-mcp/.venv/bin/microk8s-mcp
For full management, scoped to a couple of namespaces:
make register-rw SSH_HOST=you@your-node NAMESPACES=apps,staging
That registers under the name microk8s-rw, sets
MICROK8S_MCP_MODE=read-write, and prints the resulting grant before it acts.
Four switches control the rest — see the configuration
reference for all of them.
Under topology B, omit SSH_HOST entirely; the node backend runs locally.
Use SCOPE=user to make the server available in every project rather than just
the current directory.
Running both side by side works well: a read-only microk8s entry pointed at
everything, and a microk8s-rw entry scoped to the namespaces you actually
deploy to. Pass RW_SERVER_NAME=microk8s if you would rather have one entry —
two registrations mean every tool appears twice in a session.
6. Verify
make list # or: claude mcp list
Then restart Claude Code and run /mcp to see the tool inventory. Two things
make a healthy registration look like a missing one:
- Scope. Without
SCOPE=userthe entry is local — stored per-project and only visible when your working directory is inside that project. Check where it landed withclaude mcp get microk8s. - Restart. Claude Code loads MCP servers at session start, so a server
registered mid-session will not appear in
/mcpuntil you restart, whatever its scope.
Upgrading
With the default editable install, a pull is usually enough:
git pull
make install # only needed if dependencies changed
make check
With a copy install, the pull changes nothing until you reinstall, because the venv holds its own copy:
git pull
make install VENV=~/.local/share/microk8s-mcp/venv EDITABLE=0
make check VENV=~/.local/share/microk8s-mcp/venv
Forgetting that reinstall is the failure mode: everything reports success and the old code
keeps running. If behaviour does not match the source, ask the venv where it imports from —
from outside the checkout, since python -c prepends the working directory
to sys.path:
cd /tmp && ~/.local/share/microk8s-mcp/venv/bin/python -c \
"import microk8s_mcp, inspect; print(inspect.getfile(microk8s_mcp))"
Uninstalling
make unregister SERVER_NAME=microk8s
make distclean # removes .venv and all build artefacts
To remove the cluster-side identity too:
make node-teardown SSH_HOST=you@your-node
rm -f ~/.kube/claude-mcp.kubeconfig
Delete the local kubeconfig as well — it holds a live token until the Secret is gone.
Something not working? The troubleshooting page covers the errors people actually hit.