-
Notifications
You must be signed in to change notification settings - Fork 0
mcp server
A local stdio MCP server that exposes a KVM device to MCP-capable agents
(Claude Desktop, Claude Code, other agent hosts). It ships in the wheel —
pip install --pre kvm-pilot installs it and the kvm-pilot-mcp launcher, pulling the
mcp SDK (mcp>=1.17,<3) as a base dependency —
either SDK major works, 1.x or 2.x (#241). The client/driver code stays
stdlib-only; mcp is imported only in this subpackage, through one shim
(_sdk.py) that resolves whichever major is installed.
⚠️ Beta. The core read paths are live-verified on real hardware (GL-RM1PE, beta maturity in the support matrix), but many device+capability combos are still tested against mocks and emulators only (see the Hardware-Compatibility list for what actually has; issue #7). Treat every result as unverified, and strongly consider running with dry-run enabled (below).
One name per concept, used consistently across the docs, the skill, and the code (#229):
-
effect gate — the
ALLOW_*operator opt-in that enables an effect on this server (opened out-of-band, in the server's own environment; never from within an agent session). -
effect class — the
EffectClasstaxonomy that classifies what an operation does (a Ctrl+Alt+Del chord is a power effect, not HID), and so which effect gate it needs. - act tools — all gated state-changers, reversible or not (HID, media, power, boot config, SSH exec, appliance reset, external write).
-
destructive — the subset of act tools annotated
destructiveHint=true(irreversible); the reversible media/calibration/external-write tools are act tools but not destructive. -
playbook — the operating guidance bundled with the skill
(
skill/references/*.md), re-served at runtime by thedoctrinetool; topic is only thedoctrineparameter naming one.
| Tool | Annotations | What it does |
|---|---|---|
info |
readOnlyHint |
Device / system info |
healthcheck |
readOnlyHint |
Preflight audit of the KVM itself — readiness/recovery, security posture, firmware currency (issue #80). Run this first, on connecting to any device, before trusting it for real work; a CRITICAL (e.g. no out-of-band recovery path) should gate any subsequent destructive op. Includes a support-evidence finding naming what has (and has NOT) been live-verified on this exact device+firmware (#102) |
capabilities |
readOnlyHint |
Which capabilities the driver supports — structural/offline (no network, no preflight). Answers "which tools/actions can this device serve?" so you can pick the right interface up front. Also carries live_evidence: which device+firmware combos this driver has real-hardware run evidence for |
support_matrix |
readOnlyHint |
What has actually been exercised on real hardware, per device+firmware+capability — aggregated from the run ledger shipped in the package (the wiki Hardware-Compatibility data) plus each combo's derived maturity level (#98), offline, no device call. Anything in never_exercised (or a combo with no row) is unverified: confirm destructive steps with the user |
power_state |
readOnlyHint |
powered_on plus ATX detail where the driver has it |
logs |
readOnlyHint |
Device/host event log as text (seek = seconds of lookback). The text diagnostic when video/streamer/power looks wrong — it names a fault (e.g. a stuck encoder behind a snapshot 503) a screenshot can't |
snapshot |
readOnlyHint |
Current screen, returned as a real JPEG image content block the model can see. The JSON payload carries the live signal state (hdmi_signal = the authoritative picture-present flag, plus online/resolution/fps/streamer_idle) and unchanged_since_last_snapshot — byte-identical pixels across an expected screen change are stale/cached: verify via signal + logs before acting (#141/#143) |
classify_screen |
readOnlyHint (open-world) |
Boot/run phase. Uses the server-side vision backend (Anthropic or a local VLM, see below) when configured; if the server has no vision key it falls back to caller-side — returning the screenshot + prompt/schema for a vision-capable agent to classify. Cheap on-device gates (power-off/no-signal/boot-progress/OCR) resolve with no key at all. Result is a mode="server" dict, or a [json, image] list to classify yourself |
wait_for_state |
readOnlyHint (open-world) |
Block (bounded) until the screen reaches a named boot/run phase — the MCP twin of CLI watch. Validates the phase token up front, polls the cheap gates + server-side vision with backoff, emits MCP progress per poll, and returns the reached phase + confidence + a frame_ref to anchor a follow-up mouse click; a timeout comes back as a same-path reached: false result with the last observed state. timeout is capped server-side at 300 s — chain calls for longer waits. Needs server-side vision for phases the cheap gates can't resolve: with no vision key it fails fast pointing you at classify_screen polling (caller-side classification) instead of burning the timeout |
ssh_reachable |
readOnlyHint |
Is the managed host's OS reachable over SSH (in-band)? Targets the host behind the KVM (its own ssh_host), not the appliance — use it to prefer remote recovery before physical intervention. Pass host= to override the target at runtime (e.g. an install-time DHCP address the profile can't know); also surfaced as an ssh-reachable healthcheck when ssh_host is configured |
list_virtual_media |
readOnlyHint |
Inventory the KVM's virtual-media (MSD) storage: stored images (name/size/completeness), the selected drive image, and whether media is attached. Check this before asking the operator to download or upload an ISO — the image may already be on the device from an earlier job; on brands with a known host-visible gadget name it also returns host_visible_as — the device name the target's boot menu shows when the medium is truly presented (e.g. 'Glinet Optical Drive' on GLKVM, #78); its absence next to a generic CD/DVD entry means the media is not really inserted |
boot_options |
readOnlyHint |
Current boot-source override (target/enabled/mode) plus the device's allowable boot targets and whether the mode is settable — feature-detected from the BMC (Redfish AllowableValues / IPMI), never assumed. Read this before set_boot_device so you offer only targets the device accepts |
power |
destructiveHint |
on / off / off-hard / reset — disabled unless the operator opts in (KVM_PILOT_MCP_ALLOW_POWER) |
wake |
destructiveHint |
Send a Wake-on-LAN magic packet to the managed host (its wired NIC's MAC, from the profile or mac=) — the out-of-band power-on path when ATX isn't wired, and the first thing to try when a host stopped answering (suspended hosts wake in seconds). A state change, so it shares the power gate (KVM_PILOT_MCP_ALLOW_POWER) |
set_boot_device |
destructiveHint |
Boot-source override on Redfish/IPMI/AMT devices (pxe/cd/hdd/usb/bios/…, one-time by default or persistent=true, optional UEFI/legacy mode) — a config mutation, its own effect class: needs KVM_PILOT_MCP_ALLOW_CONFIG + approval. Check boot_options first for what this device accepts (AMT is single-use only; its source override is write-only) |
amt_enable |
destructiveHint |
Intel AMT only. Open the SOL/IDE-R (feature='sol') or KVM 5900 (feature='kvm') redirection listener over WS-Man — a config mutation (KVM_PILOT_MCP_ALLOW_CONFIG + confirm=true). consent_off=true (KVM) DISABLES the on-screen user-consent prompt (silent screen access) and needs a second dedicated gate, KVM_PILOT_MCP_ALLOW_CONSENT_OFF — an agent can't disable consent through ALLOW_CONFIG alone |
type_text |
destructiveHint |
Type text on the host console (HID keyboard) — needs KVM_PILOT_MCP_ALLOW_HID + per-invocation approval |
press_key |
destructiveHint |
Press one key (e.g. Enter, F2) — needs KVM_PILOT_MCP_ALLOW_HID + approval |
send_shortcut |
destructiveHint |
Send a key chord (e.g. ControlLeft,AltLeft,F2). Gated by effect: a reboot/power chord (Ctrl+Alt+Del, Magic SysRq) needs ALLOW_POWER; an ordinary session chord needs ALLOW_HID
|
ctrl_alt_delete |
destructiveHint |
Send Ctrl+Alt+Del (a reboot) — classified power_soft, so it needs KVM_PILOT_MCP_ALLOW_POWER, not the HID gate |
mouse |
destructiveHint |
Move (and optionally click) the mouse. Coords in percent (0.0-1.0, default), pixel, or raw kvmd. A click must carry observed_frame_ref from a prior snapshot; it's refused if the host rebooted/swapped media since (generation changed) so it can't land on a stale screen. Needs KVM_PILOT_MCP_ALLOW_HID
|
calibrate_mouse |
reversible write | Measure & store this host's mouse commanded→observed correction (#128): park → 5-point grid → fit → held-out verify. Afterwards mouse percent coords apply it transparently and report calibrated: true. Moves the live cursor ~10-30s on a static screen; pointer moves only, but gated like HID input (KVM_PILOT_MCP_ALLOW_HID + one approval for the whole run). Needs Pillow on the server (pip install 'kvm-pilot[calibrate]'); stored per (host, capture resolution) — a resolution change makes it stale, never applied |
mount_iso |
reversible write | Mount an ISO (local path or URL; usb=true for a flash drive) as virtual media — needs KVM_PILOT_MCP_ALLOW_MEDIA + approval |
eject |
reversible write | Detach virtual media (inverse of mount_iso) — needs KVM_PILOT_MCP_ALLOW_MEDIA + approval |
ssh_exec |
destructiveHint |
Run a command on the managed host's OS over SSH — disabled unless the operator opts in (KVM_PILOT_MCP_ALLOW_SSH). host= overrides the target at runtime |
file_firmware_report |
reversible write (external) | Reconcile the device's firmware currency against the registry SSoT and, when the registry is behind, file the "Latest known release" report as a GitHub issue via the gh CLI (the MCP twin of CLI firmware-check, #189/#190). An external write (its own effect class): disabled unless KVM_PILOT_MCP_ALLOW_EXTERNAL_WRITE + per-invocation approval; validated and deduped exactly like the CLI path; dry_run=true previews the issue body; a missing/unauthenticated gh is a graceful filed=false reason |
ssh_discover |
readOnlyHint |
Scan a CIDR for open SSH — RISKY/opt-in (active network scan; confirm=true required). Only to help find a target the user can't address, on networks they own |
appliance_status |
readOnlyHint |
Read-only diagnostics from the KVM appliance's own OS over appliance-SSH (load, D-state video threads). Note: load is ~10 even when idle on these units, so it is not a health signal — use healthcheck's encoder-wedge finding |
appliance_reboot |
destructiveHint |
Reboot the KVM appliance (not the target) to clear a wedged encoder — disabled unless the operator opts in (KVM_PILOT_MCP_ALLOW_APPLIANCE) + confirm=true. Drops KVM control ~60s; target power untouched. Never automate it |
access_paths |
readOnlyHint |
Which independent recovery paths are live for the device — the lockout-exposure view (#162): kvmd-REST / appliance-SSH / target-SSH / out-of-band power / console-HID, each labeled by failure domain so redundancy isn't oversold. summary.out_of_band_live=false means every path shares the appliance's fate — a fully hung appliance can't be recovered remotely |
events |
readOnlyHint |
Bounded collect of typed device events from the kvmd stream (#233): up to count events or duration seconds (≤30), whichever first — the MCP twin of CLI events minus follow mode (an endless stream doesn't fit stdio). Cross-checks a vision wait with a text signal |
firmware_check |
readOnlyHint |
The read half of file_firmware_report (#233): reconciles device-reported firmware/update state against the registry SSoT and reports registry_behind — nothing filed, no gate consulted |
doctrine |
readOnlyHint |
Re-serve the bundled operating doctrine from the installed package (#222) — offline, no device I/O. No topic lists the topics; topic="recovery" etc. returns that playbook's full text. For sessions that never loaded the skill file or compacted past it: re-read recovery when a host goes dark, interfaces before picking how to do an unfamiliar action. The same playbooks are also exposed as MCP resources (kvm-pilot://doctrine/<topic>, #231) for resource-capable clients |
session |
readOnlyHint |
This server's current operating posture (#223) — offline, no device I/O, never refuses. Reports read-only/dry-run state, approval posture, the profile allowlist, which effect gates are open by class name (never the enabling env vars — opening one stays operator-only, out of band), the recent act journal (bounded, in-memory: a restart empties it and voids receipts), the target's frame generation, and the last wait_for_state breadcrumb. The re-anchor call for a compacted or resumed session |
Every tool declares all four MCP hints explicitly (a CI test enforces it —
the spec defaults are punitive: an unset destructiveHint reads as true and
an unset openWorldHint reads as "reaches the internet"). Clients build real
policy from these bits (auto-approval and parallel dispatch of read-only tools,
confirmation UI on destructive ones), so precision matters. Hints are
advisory, never a security boundary — the ALLOW_* effect gates and
per-invocation approvals below apply regardless of annotation.
| Profile | readOnly | destructive | idempotent | openWorld | Tools |
|---|---|---|---|---|---|
| read | ✅ | — | ✅ | — |
info healthcheck capabilities support_matrix power_state boot_options logs snapshot list_virtual_media ssh_reachable ssh_discover appliance_status access_paths doctrine session events firmware_check
|
| read, open-world | ✅ | — | ✅ |
classify_screen — the server-side vision backend may be a cloud VLM (a local backend never leaves your network) |
|
| read, open-world, timed | ✅ | — | — |
wait_for_state — same vision caveat, and a timed wait is not idempotent |
|
| destructive | — | — | — |
power wake set_boot_device amt_enable type_text press_key send_shortcut ctrl_alt_delete mouse ssh_exec appliance_reboot — not safely repeatable (a second reset reboots again) |
|
| reversible write | — | — | ✅ | — |
eject (undoable and convergent; still MEDIA-gated), calibrate_mouse (pointer moves only, re-run converges; still HID-gated) |
| reversible write, open-world | — | — | ✅ |
mount_iso (may fetch an ISO by URL), file_firmware_report (files a GitHub issue; dedupes against existing ones, hence idempotent) — still gated by MEDIA / EXTERNAL_WRITE |
Every tool result names the host and driver it acted on, and read-only tools always run with a deny-all confirm callback, so a bug in a read path can never trip a destructive operation. Drivers are built per call and closed afterwards — important for Redfish BMCs, which cap concurrent sessions device-side (a leaked session can lock operators out of the BMC).
Every act tool — the gated state-changers, from HID (type_text, press_key,
send_shortcut, ctrl_alt_delete, mouse, calibrate_mouse) through media
(mount_iso, eject), power (power, wake), config (set_boot_device,
amt_enable), in-band exec (ssh_exec), and the appliance
(appliance_reboot) to the external write (file_firmware_report) —
requires two things, not one (#234 unified the last five onto this
pipeline: same receipts, same audit terminals, same typed same-path denials):
-
Allowed — the operator opened the tool's effect gate in
the server's own environment, and the target profile is on
KVM_PILOT_MCP_PROFILES(if set). Tools are classified by effect class, not transport:ctrl_alt_deleteand a Ctrl+Alt+Del / Magic-SysRq chord are reboots, so they needKVM_PILOT_MCP_ALLOW_POWER— an agent can't reboot the box through the weaker HID gate by picking a different actuator. -
Approved at run time — a per-invocation human approval via MCP elicitation
when the client supports it (interactive posture); otherwise an explicit
confirm=trueunder the operator's standing policy (pre-authorized posture — what an unattended install loop uses, since no human is present to answer).
Denials (gate closed, declined/cancelled, not confirmed, invalidated mid-approval)
come back as a normal result with approved: false, a typed outcome
(cancelled is a benign client-side interruption; denied is an explicit no —
branch on outcome, not on the human-facing strings, #149) and a reason — the
agent can re-plan, never left hanging.
Approval receipts (#72): an approval is a signed, expiring, single-use
receipt bound to the exact invocation (host, tool, effect, args-hash, dry-run,
approver). It is re-verified immediately before dispatch and consumed on use:
any bound field changing after approval, an expired receipt
(KVM_PILOT_MCP_RECEIPT_TTL, default 60 s), or a replay of a consumed receipt
fails closed as a denial-shaped result. Approved results carry
receipt: {id, state} and a real approval.expires. Every destructive
invocation terminal — approved, denied, consumed, expired, mismatched,
replayed, dispatch-exception — emits one JSON audit record on the
kvm_pilot.mcp.audit logger (capture the server's stderr/logging to retain the
trail; receipts are per-process, a server restart voids them).
Standing approvals (#192, opt-in). When per-invocation prompts are too
noisy for an interactive flow (arrow-keying an installer re-prompts per
keystroke), the operator can set KVM_PILOT_MCP_STANDING_TTL=<minutes> (0/unset
= off). The elicitation form then offers standing_minutes: approve once
for a (host, effect class) scope and that many minutes; invocations in scope
consume the grant instead of re-prompting. It is not the ELICIT=off
sledgehammer — a human still signs off, just once per window, and only if they
choose (standing_minutes=0 stays one-shot). A grant is not bound to args
(that is the point) but is signed with the per-process key, capped at 8 h, and
revoked the instant the operator flips the effect gate or dry-run — the same
_bound_state re-check that guards a pending approval. Each consumption still
mints its own single-use receipt and audits with the grant id (via: standing);
opening a window emits a standing-granted record. Live grants show in the
session tool's standing_approvals (scope + time left). Never spans a restart.
Symptom: act tools return approved: false, approver: null, and
denied_reason: "approval cancel" (or "denied by approver"), while
read-only tools keep working. Cause: the chat client cancelled the pending
approval prompt — sending a new message kills the in-flight elicitation; the
action never reached the target, and the result's remediation field says so.
Fix: answer the prompt before typing the next message; if a client keeps
killing approvals, the operator may set KVM_PILOT_MCP_ELICIT=off (trade-off:
no per-call human approval — the remedy advertises this escape hatch only
after ≥2 consecutive client-side kills). Full narrative:
Troubleshooting & FAQ.
The duration-scoped standing approval that removes the per-keystroke re-prompt
without giving up human sign-off shipped as KVM_PILOT_MCP_STANDING_TTL
(#192) — see Standing approvals above.
| Driver kind | info |
healthcheck |
capabilities |
power_state |
logs |
snapshot |
classify_screen / wait_for_state
|
power |
|---|---|---|---|---|---|---|---|---|
pikvm / glkvm / blikvm
|
✅ | ✅ | ✅ | ✅ (with ATX detail) | ✅ | ✅ | ✅ | ✅ |
redfish (iDRAC, iLO, XCC, OpenBMC, …) |
✅ | ✅ (checks it can't serve are omitted) | ✅ | ✅ | ✅ | ❌ no video capability | ❌ no video capability | ✅ |
ipmi (pre-Redfish BMCs over ipmitool) |
✅ | ✅ | ✅ | ✅ | ✅ (SEL) | ❌ no video capability | ❌ no video capability | ✅ |
amt (Intel AMT/vPro) |
✅ | ✅ | ✅ | ✅ | ❌ not implemented | ✅ firmware BIOS/POST/GRUB | ✅ | ✅ |
fake (in-process test double) |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
A tool the active driver cannot serve returns a clean MCP tool error naming
the driver kind and the missing capability (it never AttributeErrors).
support_matrix takes no profile and contacts no device — it works identically
for every driver.
The MCP surface is deliberately small. For anything outside the table, pick the right interface (the skill's Choosing an interface matrix is the full guide):
-
firmware-update→ the CLI (kvm-pilot firmware-update); no MCP tool. (Boundedeventsand the read-onlyfirmware_checkcurrency readout ARE tools since #233; only follow-mode streaming stays CLI.) - MSD mode switching → the Python library.
-
Serial console (SOL) on AMT/IPMI → the CLI (
kvm-pilot console); no MCP serial tool — an interactive byte stream doesn't fit the synchronous stdio transport, and won't (documented boundary, not a gap). AMT/IPMI feature enablement other than the redirection listeners (amt_enable) — e.g. re-provisioning — stays out of band. -
Disabling AMT KVM user-consent → possible via
amt_enable(consent_off=true)but only behind the dedicatedKVM_PILOT_MCP_ALLOW_CONSENT_OFFgate; the operator-present CLI path iskvm-pilot amt enable-kvm --no-consent. -
Restart
kvmd/ inspect/etc/kvmdon the appliance → out-of-band SSH to the appliance. (A full appliance reboot does have a tool now —appliance_reboot,ALLOW_APPLIANCE-gated;powerstill acts on the managed host, never the appliance.) -
View the screen when
snapshotfails (503, or a tiny frame while a signal is present — typically H.264 at the panel's native resolution) → the WebRTC/Janus stream or the vendor web UI.
Preflight first. On connecting to any device, call healthcheck before you
trust it for real work (issue #80) — it surfaces readiness/recovery, security,
and firmware risks up front (most importantly, whether there is any out-of-band
recovery path if the guest hangs). A CRITICAL finding should gate the
destructive power tool below. Note: the server does not yet auto-run this on
connect, so the agent must call it as the first step.
The power gate is layered; no single layer is trusted on its own:
-
Operator opt-in (the real gate). The
powertool is disabled by default. It only works when the human operator setsKVM_PILOT_MCP_ALLOW_POWER=1in the server's own environment (e.g. theenvblock of the MCP host config) before starting the server. The tool's refusal message deliberately does not spell out the variable assignment — an agent must not be able to relay the incantation; a human has to read this README and decide. -
confirm=truesecond factor. Even with the gate open,powerrequires an explicitconfirm=trueargument. This flag is model-supplied — it is not human approval and must not be treated as such. -
Client-side human approval. MCP hosts should require per-call human approval for
power(in Claude Desktop / Claude Code, do not put this tool on an "always allow" list). ThedestructiveHint/readOnlyHintannotations are published so hosts can render a differentiated approval UI — but annotations are hints, never a security boundary. -
Read-only launch mode (#196). With
KVM_PILOT_MCP_READ_ONLY=1the state-changing tools are not registered at all (tools/listshows only the read-only surface, minusssh_discover— an active scan has no place in a least-privilege posture), and two independent layers back the filter up: every effect gate is force-closed regardless ofALLOW_*, and every driver is built with a deny-all confirm — so even a bypassed registration cannot mutate a target (tool filtering alone is not enforcement; that lesson is CVE-2026-46519). Results carryread_only: true. The trust ladder for a new fleet: startREAD_ONLY(intake, status reports, support evidence) → graduate toDRY_RUN(rehearse destructive flows, calls logged not sent) → openALLOW_*effect gates one at a time as the matrix verifies your hardware. -
Dry-run. With
KVM_PILOT_MCP_DRY_RUN=1every driver is built withdry_run=True: destructive commands are logged and skipped, and every tool result says it ran in dry-run mode. Recommended default until your own device+capability combos are verified in the matrix. -
Untrusted screen content.
snapshotandclassify_screenfeed target-controlled console output into the agent's context. A compromised or hostile host can render text designed to steer the agent (prompt injection) — one more reason to keep layer 3 on.
Every tool accepts an optional profile argument, and a model-supplied value
takes precedence over the KVM_PILOT_PROFILE pin. ~/.config/kvm-pilot/config.toml
therefore acts as a credential store the agent can roam: any profile the
file defines can be targeted by the agent, not just the pinned one. Keep only
hosts you are willing to expose in the config file this server can read (or
point KVM_PILOT_CONFIG at a dedicated file). Pinning via
KVM_PILOT_HOST/KVM_PILOT_USER/KVM_PILOT_PASSWD env vars instead is
immune to retargeting — env values beat file-profile values. Result
provenance (host / driver in every result) makes the acted-on target
visible in transcripts.
pip install --pre kvm-pilot # installs the CLI, the MCP server, and the `mcp` SDK
kvm-pilot-mcp # start the stdio server (or: python -m kvm_pilot.mcp.server)| Variable | Meaning |
|---|---|
KVM_PILOT_PROFILE |
Default profile from ~/.config/kvm-pilot/config.toml
|
KVM_PILOT_HOST / KVM_PILOT_USER / KVM_PILOT_PASSWD / … |
Direct device config (see kvm_pilot.config); beats file-profile values |
KVM_PILOT_CONFIG |
Path of the config file (default ~/.config/kvm-pilot/config.toml) |
KVM_PILOT_MCP_ALLOW_POWER |
Operator-only opt-in enabling the power tool and power-effect HID (ctrl_alt_delete, reboot chords) (1/true/yes) |
KVM_PILOT_MCP_ALLOW_HID |
Operator-only opt-in enabling HID input (type_text, press_key, mouse, ordinary send_shortcut) |
KVM_PILOT_MCP_ALLOW_MEDIA |
Operator-only opt-in enabling virtual media (mount_iso, eject) |
KVM_PILOT_MCP_ALLOW_SSH |
Operator-only opt-in that enables the ssh_exec tool (1/true/yes) |
KVM_PILOT_MCP_ALLOW_CONFIG |
Operator-only opt-in enabling boot-configuration mutation (set_boot_device) (1/true/yes) |
KVM_PILOT_MCP_ALLOW_APPLIANCE |
Operator-only opt-in that enables appliance_reboot (rebooting the KVM appliance itself) (1/true/yes) |
KVM_PILOT_MCP_ALLOW_EXTERNAL_WRITE |
Operator-only opt-in enabling writes to systems outside the managed device — currently file_firmware_report (files a GitHub issue via the gh CLI, which must be installed + authed in the server's environment) (#190) |
KVM_PILOT_MCP_PROFILES |
Fail-closed allowlist of profile names the server may target (comma-separated). Unset = no allowlist; set-but-empty = allow nothing; a target not on the list is refused (never a fall-back to all configured hosts) |
KVM_PILOT_MCP_ELICIT |
Set to off to force the pre-authorized posture (the ALLOW_* effect gate + per-call confirm=true become the standing authorization) even for elicitation-capable clients; otherwise a per-invocation human approval is requested when the client supports it. The escape hatch when a chat client keeps cancelling pending approvals (denied_reason: "approval cancel", see troubleshooting above) — trade-off: off disables per-call human approval, an operator decision |
KVM_PILOT_MCP_STANDING_TTL |
Minutes ceiling for opt-in duration-scoped standing approvals (#192); 0/unset disables the feature. A human approves a (host, effect class) scope once for up to N minutes (chosen via the form's standing_minutes, clamped here, 8 h hard cap); in-scope invocations then skip the prompt. Grants are per-process, not bound to args, and revoked on any effect-gate/dry-run flip. Unlike ELICIT=off, a human still signs off — just once per window. Live grants appear in session.standing_approvals
|
KVM_PILOT_SSH_HOST / KVM_PILOT_SSH_USER / KVM_PILOT_SSH_PORT / KVM_PILOT_SSH_KEY
|
The managed host's SSH target (a different machine from the KVM) for ssh_reachable / ssh_exec; also settable per-profile as ssh_host etc. |
KVM_PILOT_MCP_READ_ONLY |
Least-privilege launch posture (#196): only read-only tools are registered (minus ssh_discover), every effect gate is force-closed regardless of ALLOW_*, and drivers are built deny-all. Wins over ALLOW_* and DRY_RUN. The recommended first rung of the trust ladder (READ_ONLY → DRY_RUN → per-effect ALLOW_*) |
KVM_PILOT_MCP_DRY_RUN |
Build every driver with dry_run=True; destructive calls are logged, never sent |
KVM_PILOT_MCP_RECEIPT_TTL |
Lifetime (seconds) of a per-invocation approval receipt (#72); default 60, clamped to [1, 3600]. A dispatch presented with an expired receipt fails closed and must be re-approved |
KVM_PILOT_VISION_BACKEND |
Vision backend for classify_screen / wait_for_state: anthropic (default) or local (any OpenAI-compatible VLM: LM Studio, Ollama, vLLM) |
KVM_PILOT_VISION_URL |
Base URL of the local VLM (required for local) |
KVM_PILOT_VISION_MODEL |
Vision model id — required for local; optional pin for anthropic (otherwise the newest model is auto-resolved once per process) |
ANTHROPIC_API_KEY |
The anthropic vision backend uses it when set; not required — without any vision key, classify_screen falls back to caller-side classification (returns the screenshot + prompt for a vision-capable agent); wait_for_state needs server-side vision for non-cheap-gate phases and errors fast otherwise (pointing at classify_screen polling) |
Add to your MCP config (claude_desktop_config.json). Dry-run is enabled here
on purpose — remove it only once your hardware is verified in the matrix (or
you accept the risk), and add
KVM_PILOT_MCP_ALLOW_POWER only if you want the power tool live:
{
"mcpServers": {
"kvm-pilot": {
"command": "kvm-pilot-mcp",
"env": {
"KVM_PILOT_PROFILE": "homelab",
"KVM_PILOT_MCP_DRY_RUN": "1",
"ANTHROPIC_API_KEY": "sk-..."
}
}
}
}Keep per-call approval enabled for power in the host UI.
A single-file .mcpb bundle (zero-toolchain install) is tracked in issue #7.
- CLI reference
- Configuration
- Driver features
- Architecture
- Redfish reference
- Intel AMT vPro reference
- Firmware registry
- Claude skill
- Skill playbook: interfaces
- Skill playbook: recovery
- Skill playbook: setup & gates
- Skill playbook: Linux installs
- Skill playbook: target context
- Skill playbook: Python library
- MCP server
- Hardware compatibility