A read-only MCP server that gives AI agents direct evidence from a running KubeVirt guest's console — what a human would see over VNC or a serial terminal.
Kubernetes tells an agent a VM is running. This server lets the agent see what the guest is actually showing.
flowchart LR
Agents["AI agents<br/>"]
K8s["kubernetes-mcp-server<br/>control-plane view<br/>objects, status, events"]
Console["kubevirt-console-mcp<br/>guest-console view<br/>screenshot, console log"]
VM(["running KubeVirt VM"])
Agents --> K8s --> VM
Agents --> Console --> VM
It returns evidence only — a PNG image or console text. It performs no reasoning.
kubevirt-console-mcp-demo.mp4
Claude starts with Kubernetes-level troubleshooting — then uses the KubeVirt Console MCP to inspect the guest console and discover a kernel panic that the Kubernetes MCP alone cannot identify.
| Tool | What it does |
|---|---|
console_screenshot |
Captures the guest's graphical console as a PNG image (VNC framebuffer). |
console_log |
Reads persisted serial-console output from the guest-console-log sidecar. |
console_capture |
Captures live serial-console output for a bounded duration (1—30s). |
Build:
go build -o kubevirt-console-mcp ./cmd/kubevirt-console-mcpRun (requires a kubeconfig or in-cluster credentials):
./kubevirt-console-mcpThe binary speaks MCP over stdio. stdout is reserved for the protocol; logs go to stderr.
{
"mcpServers": {
"kubevirt-console-mcp": {
"command": "/path/to/kubevirt-console-mcp"
}
}
}Standard client-go conventions — no custom flags or credential formats:
KUBECONFIGenvironment variable- Default kubeconfig rules (
~/.kube/config) - In-cluster configuration (when running as a pod)
Console access is security-sensitive and granted separately from ordinary VM read access.
See deploy/rbac.yaml for a least-privilege example.
Required permissions:
| Resource | API group | Verbs | Why | Used by |
|---|---|---|---|---|
virtualmachineinstances |
kubevirt.io |
get |
Check VMI phase and device flags | all tools |
virtualmachineinstances/vnc/screenshot |
subresources.kubevirt.io |
get |
Graphical console screenshot | console_screenshot |
virtualmachineinstances/console |
subresources.kubevirt.io |
get |
Serial console access | console_capture |
pods |
core | get, list |
Find the virt-launcher pod for a VMI | console_log |
pods/log |
core | get |
Read the guest-console-log container |
console_log |
No create, update, delete, or patch verbs are needed. If you only use a subset of the
tools, you only need that subset of permissions (see the Used by column).
Whether you apply deploy/rbac.yaml depends on the identity the server authenticates as, which
follows the Kubernetes configuration resolution order:
- In-cluster (running as a pod): required. The pod's ServiceAccount has no permissions by
default, so without this Role/RoleBinding every call fails with
PERMISSION_DENIED(HTTP 403). Apply the manifest and set the Deployment'sserviceAccountNametokubevirt-console-mcp. - Out-of-cluster (kubeconfig), identity already has these verbs (e.g. a cluster-admin kubeconfig): not needed — it just works.
- Out-of-cluster (kubeconfig), restricted identity: apply the
Rolefrom the manifest but change theRoleBindingsubject from the ServiceAccount to yourUserorGroup.
Note that being able to read VMI objects does not imply console access — the screenshot and
serial console are separate subresources.kubevirt.io subresources with their own RBAC. An
identity that can already get virtualmachineinstances will still be denied on the console until
these subresource grants are added.
As written, deploy/rbac.yaml is namespaced (Role + RoleBinding in default) — apply one
copy per namespace you observe. For multi-namespace or cluster-wide use, swap Role/RoleBinding
for ClusterRole/ClusterRoleBinding.
Every tool response is bounded to prevent a guest from exhausting an agent's context:
| Limit | Value |
|---|---|
| Screenshot | 4 MiB max |
| Console log | 256 KiB max, 1—5000 tail lines (default 500) |
| Live capture | 128 KiB max, 1—30s duration (default 5s) |
Truncation is always explicit ([output truncated by kubevirt-console-mcp after N bytes]), never silent.
- Screenshots and serial output can contain credentials, IPs, hostnames, and application data. They are held in memory only for the duration of a tool call — never written to disk, never logged.
console_capturenever sends keystrokes to the guest. It supplies a synthetic empty reader as the connection's input side.wake_screendefaults tofalse— it causes a minor graphical interaction (cursor nudge) with the guest.- A live serial connection may contend with a human or another console client attached to the same VMI.
console_screenshotrequires a running VMI with graphics enabled. Use serial tools when graphics are disabled or unavailable (early boot,autoattachGraphicsDevice: false).- Serial output depends on the guest emitting it. Some guests need
console=ttyS0or similar kernel arguments. - The
guest-console-logsidecar can be disabled;console_logreports this clearly. - Pod logs are not permanent storage — if the virt-launcher pod is deleted, its log is gone.
- A quiet
console_capture(no output) is normal, not a failure — the guest was simply quiet.
go test ./...
go test -race ./...
go vet ./...Tests use Ginkgo / Gomega.
The unit suites under internal/ do not require a cluster. Functional (end-to-end) tests live
in tests/ and run with go test -tags=integration ./...; they require
KUBEVIRT_CONSOLE_MCP_TEST_NAMESPACE and KUBEVIRT_CONSOLE_MCP_TEST_VMI pointing at a running VMI.
cmd/kubevirt-console-mcp/ MCP server entry point (stdio transport, stderr logging)
internal/
config/ Kubernetes REST config loading
kubevirt/ KubeVirt API access: screenshot, log, capture, pod selection
mcp/ MCP tool registration and handlers
tests/ Functional (end-to-end) tests against a live cluster
deploy/rbac.yaml Least-privilege Role/RoleBinding
Released under the Apache License 2.0.