No description
  • Python 96.8%
  • Makefile 1.7%
  • Dockerfile 1.5%
Find a file
Kai Siren 34a69ac445
All checks were successful
build-publish / test (push) Successful in 5m4s
build-publish / publish (push) Successful in 3m16s
feat: add disk pressure MCP tools
2026-07-15 00:43:29 -07:00
.forgejo/workflows chore(ci): add Telegram main-failure alerts 2026-07-10 09:02:53 +00:00
.ward feat: node-local host-introspection MCP (initial scaffold) 2026-07-03 13:08:30 -07:00
docs feat: add disk pressure MCP tools 2026-07-15 00:43:29 -07:00
src/node_stats_mcp feat: add disk pressure MCP tools 2026-07-15 00:43:29 -07:00
tests feat: add disk pressure MCP tools 2026-07-15 00:43:29 -07:00
.dockerignore feat: node-local host-introspection MCP (initial scaffold) 2026-07-03 13:08:30 -07:00
.gitignore feat: node-local host-introspection MCP (initial scaffold) 2026-07-03 13:08:30 -07:00
.pre-commit-config.yaml fix: align trifecta and rollout pins 2026-07-09 19:52:35 +00:00
AGENTS.md fix: align trifecta and rollout pins 2026-07-09 19:52:35 +00:00
CLAUDE.md feat: node-local host-introspection MCP (initial scaffold) 2026-07-03 13:08:30 -07:00
Dockerfile feat: node-local host-introspection MCP (initial scaffold) 2026-07-03 13:08:30 -07:00
Makefile feat: node-local host-introspection MCP (initial scaffold) 2026-07-03 13:08:30 -07:00
pyproject.toml fix: align trifecta and rollout pins 2026-07-09 19:52:35 +00:00
README.md feat: add disk pressure MCP tools 2026-07-15 00:43:29 -07:00
uv.lock ward-container: residual claude work on coilyco-flight-deck/node-stats-mcp 2026-07-10 21:59:47 +00:00

node-stats-mcp

A node-local MCP battery. It reads the node it runs on - CPU, memory, disk, disk-pressure runway, load, network, top processes, and bounded file metadata - and serves that over MCP (streamable-HTTP), so an agent can inspect a node without a host bind mount.

This is the generic node-introspection spine, first instance of the upstream pattern: a per-node MCP agent, the same shape as node-exporter (DaemonSet-or-node-pinned + hostPath + host namespaces), but exposing a tool surface instead of Prometheus metrics.

Node view, not pod view

True node stats need the pod to borrow the host's namespaces: hostPID so process listings see the node, hostNetwork so net counters are the node's, and a read-only hostPath of / at /host (with ROOTFS=/host) for disk. CPU and memory come from the non-namespaced /proc/{stat,meminfo} regardless. The deploy bundle wires all of this - see below.

k3s view

The server also exposes a read-only k3s inventory surface for host-to-pod attribution:

  • get_k3s_pods - namespace, pod, phase, node, restart count, container names/images, pod IP, age.
  • get_k3s_container_memory - per-container memory from metrics-server when available, else approximate RSS summed from host cgroups.
  • get_k3s_process_attribution - top host processes annotated with the owning pod/container when cgroup data and pod metadata line up.

The API read path prefers the host-mounted k3s admin kubeconfig at /host/etc/rancher/k3s/k3s.yaml and falls back to the pod's service account when needed. All three tools stay read-only.

Safety

Read-only by construction: every tool is a read, none mutate the host. File introspection (stat_path, read_text_head) is prefix-allowlisted via NODE_STATS_READABLE_ROOTS (empty by default = file reads denied) and size-capped, so a tool can never be walked into /host/root/.ssh. Disk pressure scans (get_pressure_path_usage) use a fixed configured path list, so callers cannot turn the MCP into a root filesystem browser. Reach is gated at the network layer (the tailnet / node), not by the tool.

Disk pressure

get_filesystem_pressure reports root filesystem capacity, available bytes, inode use, and byte runway to configurable warning and critical thresholds. get_pressure_path_usage scans a bounded set of node-pressure paths such as logs, journald, kubelet, k3s, and containerd storage, with a per-path entry cap. Each path result includes size, entries scanned, permission errors, scan errors, skipped different-filesystem entries, and truncation state.

Run it locally

ward sync
ward run     # streamable-HTTP MCP on :8080, endpoint at /mcp

Commands

Dev commands are declared in .ward/ward.yaml. Run them as ward <verb>.

See also

Cross-reference convention from features-release-tooling.md.