Troubleshooting
The errors people actually hit, what each one really means, and the fix.
Start with make doctor — it checks most of the preconditions in one go and
installs nothing.
Registration
Server shows disconnected in claude mcp list
Claude Code launches subprocesses with a different environment than your shell. The command
must be an absolute path to the venv binary. make register always
uses one; if you registered by hand, check it. Confirm the binary works on its own:
make check
The server does not appear at all
Two causes, both of which make a healthy registration look like a missing one.
Scope. Without SCOPE=user the entry is local — stored
per-project, visible only when your working directory is inside that project. Nothing is wrong;
you are just looking from the wrong place:
claude mcp get microk8s
Restart. Claude Code loads MCP servers at session start. A server
registered during a running session will not appear in /mcp until you restart,
whatever its scope.
Every tool appears twice
You have both a microk8s and a microk8s-rw entry registered. That
is a supported setup, but if you would rather have one, re-register with
RW_SERVER_NAME=microk8s to replace the read-only entry instead of adding to it.
Connecting to the cluster
connection refused on :16443
MicroK8s only binds the API server to addresses in its certificate SANs. If you are connecting by hostname or a non-primary IP, add it on the node and refresh the certificates:
# on the node
sudo nano /var/snap/microk8s/current/certs/csr.conf.template
microk8s refresh-certs --cert server.crt
x509: certificate is valid for ..., not <host>
The address in the kubeconfig is reachable, but it is not one the certificate covers. See which names are present:
ssh you@your-node 'openssl x509 -in \
/var/snap/microk8s/current/certs/server.crt -noout -text' | grep -A3 "Alternative Name"
Then either re-mint against an address that is listed, or add yours to
csr.conf.template and refresh as above.
'<host>' does not resolve on this machine
From make node-check. The API server address is not usable from here — almost
always an ~/.ssh/config alias that ssh -G could not expand, or a name
only the node knows. Pass the real one:
make node-setup SSH_HOST=<alias> APISERVER=https://<ip-or-dns>:16443
Nothing was minted; this check fires before any cluster contact.
cannot reach <host> over ssh
Check ssh you@your-node true works on its own first. If you need a key or a
jump host:
make node-check SSH_HOST=you@your-node SSH_KEY=~/.ssh/id_ed25519
make node-check SSH_HOST=you@your-node SSH_OPTS='-J bastion -o ConnectTimeout=10'
The node-* targets do not force BatchMode, so password and
passphrase prompts still work. The running server does force it — it never prompts —
so key-based auth is required at runtime.
Secret mcp-system/claude-mcp-token not found
The RBAC step did not run, or ran against a different cluster:
make node-rbac SSH_HOST=you@your-node
make node-kubeconfig SSH_HOST=you@your-node
Remote script failed; ... left untouched
Nothing was damaged — node-kubeconfig validates the minted kubeconfig before it
replaces anything. Run make node-check SSH_HOST=... to find the missing
precondition.
The node backend
microk8s: command not found over SSH
SSH command execution does not source your login profile, so /snap/bin is often
missing from PATH. There are two separate settings, for two separate stages:
# at runtime, for the server
--env MICROK8S_MCP_MICROK8S=/snap/bin/microk8s
# during setup, for the make targets
make node-setup SSH_HOST=you@your-node KUBECTL='/snap/bin/microk8s kubectl'
No node backend available
A tool that needs the node backend — microk8s_status or
manage_addon — was called with no way to reach it. Either run the server on the
MicroK8s host, or set MICROK8S_MCP_SSH_HOST=user@host with key-based auth.
metrics-server errors from top
Enable the addon on the node, then give it a minute to scrape:
microk8s enable metrics-server
Install and upgrade
No module named microk8s_mcp
The venv is stale or the install did not complete:
make distclean install
bad interpreter after moving or renaming the repo
Virtualenvs are not relocatable. Scripts in <venv>/bin hardcode the
absolute path of the interpreter that created them, and an editable install records the
absolute path of the checkout. Renaming breaks both, and make install cannot
repair it because pip itself is one of those scripts:
make distclean install
An out-of-repo copy install is immune to repo renames — that is the point of it. Moving the
venv itself has the same problem, so reinstall rather than mv a venv.
Code changes have no effect
The classic copy-install trap: with EDITABLE=0 the venv holds its own copy, so
a git pull changes nothing until you reinstall. Everything reports success and the
old code keeps running. Ask the venv where it imports from — from outside the
checkout, since python -c prepends the working directory to sys.path
and would otherwise answer with the local source:
cd /tmp && ~/.local/share/microk8s-mcp/venv/bin/python -c \
"import microk8s_mcp, inspect; print(inspect.getfile(microk8s_mcp))"
A path under site-packages is a copy install; a path inside the repo means it
is editable after all.
Permission denied writing the kubeconfig
KUBECONFIG_OUT points somewhere you cannot write:
make kubeconfig APISERVER=... KUBECONFIG_OUT=/tmp/claude-mcp.kubeconfig
Unexpected behaviour
It refuses to change anything
Read-only is the default, and the refusal message says so. Register a read-write entry:
make register-rw SSH_HOST=you@your-node NAMESPACES=apps,staging
Then restart Claude Code. If it still refuses, work through these in order: the namespace is
outside MICROK8S_MCP_NAMESPACES; or it is a protected namespace and
MICROK8S_MCP_ALLOW_PROTECTED_WRITES is false; or the tool needs its own switch
(ALLOW_ADDON_CHANGES, ALLOW_NODE_OPS); or the RBAC binding does not
grant it — check with auth can-i.
It can do more than you expected
Almost always the kubectl fallback: with no
local kubectl, commands run under the node's admin credential and your scoped
kubeconfig stops applying. Check the logs for
falling back to `microk8s kubectl`, and set
MICROK8S_MCP_KUBECTL to an absolute path.
Then confirm what the credential can really do:
kubectl --kubeconfig ~/.kube/claude-mcp.kubeconfig auth can-i --list
Output is truncated
Anything over MICROK8S_MCP_MAX_OUTPUT characters (24000 by default) is clipped
in the middle, with a note saying how much was dropped. Narrow the query — a label selector, a
single namespace, fewer tail_lines — rather than raising the limit, since the
output has to fit in a context window either way.
Commands time out
MICROK8S_MCP_TIMEOUT is 60 seconds per command. Addon operations and node
drains raise it internally, but a slow cluster or a slow SSH hop can still hit it. Raise it if
the timeout is legitimate rather than a symptom.
Still stuck? Open an issue on GitHub with the startup log line — it records the mode, kubeconfig, backend and namespace allowlist the server is actually running with.