Skip to main content
Glama
rzippert
by rzippert

libvirt-mcp

A Model Context Protocol (MCP) server for managing KVM/QEMU virtual machines on remote libvirt hosts. Talks libvirt RPC over SSH — no agent on the hypervisor, no need to install anything on the target host beyond the standard libvirtd.

Functionally a "virt-manager for LLMs": inspect, start/stop, snapshot, clone, and delete KVM/QEMU domains through 13 MCP tools.

Tools

Group

Tools

Inspection

list_domains, domain_info, snapshot_list, screenshot

Lifecycle

start_domain, shutdown_domain (ACPI + poll), reboot_domain, force_destroy_domain

Snapshots

snapshot_create, snapshot_revert, snapshot_delete

Management

clone_from_template (linked clone), delete_domain (with optional wipe_disks)

Destructive operations (force_destroy_domain, snapshot_revert, snapshot_delete, delete_domain) require an explicit confirm=True argument so an LLM can't fire them by accident.

Related MCP server: libvirt-mcp-server

Architecture

┌──────────────┐                          ┌──────────────────────┐
│ MCP client   │  ── stdio (JSON-RPC) ──▶ │ libvirt-mcp          │
│ Claude Code, │                          │ (Docker container)   │
│ Desktop, …   │                          │  • libvirt-python    │
└──────────────┘                          │  • mcp SDK           │
                                          └──────────┬───────────┘
                                                     │
                                                     │ qemu+ssh://user@host/system
                                                     │ (uses host's ~/.ssh/)
                                                     ▼
                                          ┌──────────────────────┐
                                          │ Hypervisor           │
                                          │  • libvirtd          │
                                          │  • qemu-kvm          │
                                          └──────────────────────┘

The MCP server runs inside a Docker container (so libvirt-dev headers don't need to be installed on your machine). It bind-mounts your ~/.ssh read-only and uses standard libvirt SSH transport, inheriting ~/.ssh/config, agent forwarding, jump hosts, etc.

Setup

1. Pull the image

The container image is published as ghcr.io/rzippert/libvirt-mcp (public — no auth required, even though the source repo is private):

docker pull ghcr.io/rzippert/libvirt-mcp:latest

Tags published:

  • latest — head of main

  • X.Y.Z and X.Y — from git tags vX.Y.Z (the v prefix is dropped per Docker convention)

  • main, sha-<short> — for traceability

2. Create your config

mkdir -p ~/.config/libvirt-mcp
cat > ~/.config/libvirt-mcp/config.toml <<'EOF'
default_profile = "primary"

[profiles.primary]
uri = "qemu+ssh://user@hypervisor.example.com/system"
description = "Primary KVM hypervisor"
EOF
$EDITOR ~/.config/libvirt-mcp/config.toml

The libvirt URI is standard. SSH keys come from your host's ~/.ssh/. Make sure you can already connect:

ssh user@hypervisor.example.com virsh -c qemu:///system list --all

3. Wire it up to an MCP client

Claude Code

claude mcp add libvirt --scope user -- \
  docker run -i --rm \
    -v "$HOME/.ssh:/home/ubuntu/.ssh:ro" \
    -v "$HOME/.config/libvirt-mcp/config.toml:/etc/libvirt-mcp/config.toml:ro" \
    --network host \
    -e LIBVIRT_MCP_CONFIG=/etc/libvirt-mcp/config.toml \
    ghcr.io/rzippert/libvirt-mcp:latest

Verify with claude mcp list. Remove with claude mcp remove libvirt --scope user.

Claude Desktop

Edit claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "libvirt": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-v", "/Users/youruser/.ssh:/home/ubuntu/.ssh:ro",
        "-v", "/Users/youruser/.config/libvirt-mcp/config.toml:/etc/libvirt-mcp/config.toml:ro",
        "--network", "host",
        "-e", "LIBVIRT_MCP_CONFIG=/etc/libvirt-mcp/config.toml",
        "ghcr.io/rzippert/libvirt-mcp:latest"
      ]
    }
  }
}

Replace /Users/youruser with your home directory. On Windows use forward slashes or double-escape backslashes. Restart Claude Desktop after editing.

VS Code (GitHub Copilot Chat MCP)

Create .vscode/mcp.json in your workspace (project-scoped) or add to user settings:

{
  "servers": {
    "libvirt": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-v", "${userHome}/.ssh:/home/ubuntu/.ssh:ro",
        "-v", "${userHome}/.config/libvirt-mcp/config.toml:/etc/libvirt-mcp/config.toml:ro",
        "--network", "host",
        "-e", "LIBVIRT_MCP_CONFIG=/etc/libvirt-mcp/config.toml",
        "ghcr.io/rzippert/libvirt-mcp:latest"
      ]
    }
  }
}

VS Code expands ${userHome} and ${workspaceFolder}. Open the Copilot Chat panel → MCP servers → enable libvirt.

Configuration reference

~/.config/libvirt-mcp/config.toml (or override with LIBVIRT_MCP_CONFIG):

default_profile = "primary"   # used when a tool is called without `profile=`

[profiles.<name>]
uri = "qemu+ssh://user@host/system"   # standard libvirt URI
description = "Free-form description for humans"

Config search order:

  1. $LIBVIRT_MCP_CONFIG env var

  2. ~/.config/libvirt-mcp/config.toml

  3. ./config.dev.toml

  4. ./config.toml

Safety model

  • Allow-list validation on domain names (^[a-z0-9][a-z0-9-]{0,30}$), bridge names, and absolute paths (no .. segments).

  • Inspection tools open read-only libvirt connections; state-changing tools open read-write connections only when invoked.

  • Confirmation gates on destructive operations: force_destroy_domain, snapshot_revert, snapshot_delete, delete_domain all require confirm=True.

  • No subprocess on the hypervisor: every operation is pure libvirt RPC (no qemu-img, no virt-install, no virsh shell-out). Disk volumes are created and deleted through libvirt's storage volume API, reusing any active dir-pool that covers the target path or transparently creating a transient one.

Development

git clone git@github.com:rzippert/libvirt-mcp.git
cd libvirt-mcp
cp config.example.toml config.dev.toml && $EDITOR config.dev.toml
docker compose build
docker compose run --rm dev bash

Inside the dev container, source on the host (bind-mounted at /workspace) shadows the installed package via PYTHONPATH, so edits take effect immediately. Exercise tools directly:

python -c "from libvirt_mcp.tools.readonly import list_domains; \
           import json; print(json.dumps(list_domains(), indent=2))"

Project layout:

libvirt_mcp/
├── __main__.py        # python -m libvirt_mcp → mcp.run() (stdio)
├── server.py          # FastMCP instance
├── connections.py     # config loader + connect() context manager
├── states.py          # VIR_DOMAIN_* → string
└── tools/
    ├── readonly.py    # inspection
    ├── lifecycle.py   # start/shutdown/reboot/force_destroy
    ├── snapshot.py    # snapshot CRUD (revert/delete gated)
    └── management.py  # clone_from_template, delete_domain

Publishing happens automatically via .github/workflows/publish.yml on push to main and on v* tags (multi-arch: linux/amd64, linux/arm64).

License

GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI models to securely query and manage virtual machines and virtualized resources via the libvirt API through the Model Context Protocol.
    1
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables management of GNOME Boxes virtual machines through libvirt/virsh with lifecycle and snapshot operations.
    13
    9 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables managing SSH-managed clusters with tools for executing commands, file operations, and tmux sessions across multiple nodes.
    1
    MIT