Remote control of a real, physical retro or legacy machine: keyboard, mouse, screen, audio, power and file transfer, driven by a CLI, an MCP server, or a browser KVM. It exists to let automated tests run against hardware that predates automation — DOS boxes, classic Macs, anything you can wire a capture device and an input path to.
This project was built agentically using Claude Code.
SKILL.md files any agent can pick up. docs/SKILLS.md┌─────────────────────┐ ┌────────────────┐ ┌─────────────┐
│ Control Host │ │ Daemon Host │ │ Target │
├─────────────────────┤ ├────────────────┤ ├─────────────┤
│ - vcctrl CLI │ ssh, │ vcctrld.py │ PS/2, Serial, │ - DOS │
│ - AI Agents │ ◄──► │ - Input/output │ ADB, USB, │ - Macintosh │
│ - Skills/MCP │ http │ - Video │ ◄──► │ - Linux │
│ - Web Browser (KVM) │ │ - Power │ VGA, HDMI │ │
│ │ │ - Audio │ │ │
│ │ │ - Files │ │ │
└─────────────────────┘ └────────────────┘ └─────────────┘
vcctrl ships three templates, one per profile-kinds/*.yaml.
Scaffold one with
tools/new-profile.py --kind <kind> --name <yours>
and fill in the REPLACE_ME placeholders, using the machines below as a
reference.
What this project actually runs on, across all three configurations:
vga-ps2A DOS/Windows-era PC with PS/2 keyboard/mouse and analog VGA out.
Hardware:
Distinguishing settings, not a complete config — full example: examples/vcctrl.example.yaml.
capabilities:
input:
backend: usb4vc-uinput
video:
backend: v4l2-ffmpeg
settings:
device: /dev/v4l/by-id/usb-MACROSILICON_xxxx-video-index0
camera:
backend: none # or v4l2-ffmpeg, if you have the second camera
rgb2hdmi-usb4vcADB keyboard/mouse, capture via an RGB2HDMI board. Scaffolded but unverified/untested — no such hardware has run against this project's own build yet, so treat the template's values as a documented guess.
Hardware:
Distinguishing settings, not a complete config — full example: examples/vcctrl-macintosh.example.yaml.
capabilities:
input:
backend: usb4vc-uinput # same backend as Retro PC -- the board swap
# is what changes, not this setting
video:
backend: v4l2-ffmpeg
settings:
device: /dev/v4l/by-id/usb-xxxx-video-index0 # the RGB2HDMI dongle
hdmi-usbAny machine with HDMI out and a spare USB port. The Pi's own USB-C port presents itself as a USB keyboard and mouse straight to the target.
Hardware:
Driving capture, keyboard/mouse emulation and encoding together can throttle a Pi 5 — one capture device per Pi for this configuration.
Distinguishing settings, not a complete config — full example: examples/vcctrl-modernpc.example.yaml.
capabilities:
input:
backend: hid-gadget
settings:
hid_keyboard_device: /dev/hidg0
hid_mouse_device: /dev/hidg1
video:
backend: v4l2-ffmpeg
settings:
device: /dev/v4l/by-id/usb-xxxx-video-index0
analog: false
Needs dtoverlay=dwc2,dr_mode=peripheral under /boot/firmware/config.txt's
[pi5] section.
capabilities:
power:
backend: kasa # kasa | kasa-klap | wemo | shell | none
settings:
host: 192.0.2.20
TP-Link Kasa, Wemo, and shell-scripted relays/PDUs/GPIO are all supported — see examples/vcctrl.example.yaml for backend-specific settings.
A Linux daemon host (/dev/uinput for USB4VC, or a peripheral-capable USB
port for hid-gadget), root and systemd; a capture device that emits
MJPEG natively over V4L2; ffmpeg (with libx264/libopus for the optional
H.264 transport and Opus audio); Python ≥ 3.7 (≥ 3.10 for the MCP server).
1. Flash the daemon host. Raspberry Pi OS or Debian, arm64, Trixie (13)+, SSH enabled.
2. Install USB4VC, then this repo's two local patches:
python3 tools/patch-usb4vc-64bit.py --check # then without --check to apply
python3 tools/patch-usb4vc-board.py --check
3. Clone and configure, from the control host:
git clone <this repo's URL> && cd vcctrl
cp examples/vcctrl.example.yaml vcctrl.yaml # untracked, never commit this
$EDITOR vcctrl.yaml # daemon_host, plug, devices
4. Deploy:
VCCTRL_HOST=<pi-hostname-or-ip> pi/deploy.sh
(control.daemon_host in vcctrl.yaml works instead of the env var;
pi/deploy.sh refuses clearly if neither is set.)
5. Install the CLI and confirm the daemon answers:
sudo cp bin/vcctrl-client /usr/local/bin/vcctrl # or: pi/deploy.sh --client
vcctrl status
vcctrl preflight # one gate: caps, board, power, video, input
6. Drive it:
vcctrl type 'CD \'
vcctrl key enter
vcctrl shot # a frame from the capture device, as a file
7. Connect an agent instead of typing verbs by hand — see below.
8. Scaffold your own profile once the above works:
tools/new-profile.py --kind <vga-ps2|hdmi-usb|rgb2hdmi-usb4vc> --name <yours>.
See docs/PROFILES.md for running more than one target off one daemon.
Skills are portable knowledge — free to copy, grant nothing. MCP is real control of physical hardware — registering it is a hardware-access decision, not a documentation one; never commit a real hostname to get it working.
Skills (Claude Code, Codex, Cursor — no checkout needed):
npx skills add <this repo's URL> \
--full-depth -a claude-code -y \
-s vcctrl-mcp-workflows -s vcctrl-common-workflows \
-s vcctrl-rig-hazards -s vcctrl-camera # --agent codex for Codex
Installs the four hardware-portable skills. Two more
(vcctrl-repo-conventions, vcctrl-webkvm-copy) describe this repo's own
conventions and are left out on purpose — see docs/SKILLS.md
if you want them anyway.
MCP (drives the real hardware):
# daemon mode -- once deployed (docs/MCP-SERVER.md sec. 4), no local checkout
claude mcp add --transport http vcctrl-mcp-daemon https://<your-daemon-host>/mcp
# control mode -- needs a local clone + venv
cd vcctrl
python3 -m venv agent/.venv && agent/.venv/bin/pip install -r agent/requirements.txt
claude mcp add vcctrl-mcp -- "$(pwd)/agent/.venv/bin/python3" "$(pwd)/agent/vcctrl_mcp.py"
Restart the session after registering — /mcp doesn't pick up a fresh
server live. Full reasoning and the safety model: docs/MCP-SERVER.md.
bin/vcctrl control-host CLI -- ssh's to the daemon, no logic itself
bin/vcctrl-client the real CLI; installed on the daemon host as /usr/local/bin/vcctrl
bin/vcctrl-* one-off diagnostics run from the control host (audio, capture, card ID)
daemon/vcctrld.py input/video/audio/power/file server; owns the devices
daemon/vcweb.py the control web KVM; daemon/vcweb_public.py is the read-only mirror
common/vcconfig.py shared config loader (both hosts import it)
agent/vcctrl_mcp.py MCP server exposing the CLI's tools
harness/vcctrl-* cell/sweep/collect -- automated test runs against a target
profile-kinds/*.yaml hardware-configuration templates (tools/new-profile.py reads these)
pi/install.sh systemd units and setup on the daemon host
pi/deploy.sh push + install from the control host
vendor/ third-party code (see THIRD-PARTY.md)
tests/ pytest tests/ -- no hardware required
| configuration | proven | notes |
|---|---|---|
Retro PC (vga-ps2) | working end to end | keyboard, mouse, video, audio, power, file transfer, the web KVM — all on real hardware; docs/lab/FINDINGS.md |
Modern PC (hdmi-usb) | working end to end | no USB4VC needed; multi-profile (one daemon, several targets) proven the same way |
Classic Macintosh (rgb2hdmi-usb4vc) | scaffolded, unverified/untested | templates exist; no such hardware has run against this project's own build yet |
Known gaps: no hardware reset line for a target that swallows Ctrl-Alt-Del (GPIO to the reset header is planned, not built); H.264 transport and full on-screen-keyboard coverage are measured on one board so far. docs/lab/OPEN-FAULTS.md has the complete list of what's broken; docs/KNOWN-LIMITATIONS.md has what doesn't yet adapt to different hardware at all — the DOS-side boot contract, timing constants, install paths, and similar still-hardcoded pieces.
See CONTRIBUTING.md for this repo's conventions. Run
the tests with pytest tests/ — no hardware required; needs Python,
PyYAML, node, a Chromium/Chrome binary, and ffmpeg.
MIT — see LICENSE. Third-party code under vendor/ keeps its own licence; see THIRD-PARTY.md.
626 commits
Hacker News (1)
Python
71.3%
HTML
23.2%
Shell
3.8%
Remote control of a real, physical retro or legacy machine: keyboard, mouse, screen, audio, power and file transfer, driven by a CLI, an MCP server, or a browser KVM. It exists to let automated tests run against hardware that predates automation — DOS boxes, classic Macs, anything you can wire a capture device and an input path to.
This project was built agentically using Claude Code.
SKILL.md files any agent can pick up. docs/SKILLS.md┌─────────────────────┐ ┌────────────────┐ ┌─────────────┐
│ Control Host │ │ Daemon Host │ │ Target │
├─────────────────────┤ ├────────────────┤ ├─────────────┤
│ - vcctrl CLI │ ssh, │ vcctrld.py │ PS/2, Serial, │ - DOS │
│ - AI Agents │ ◄──► │ - Input/output │ ADB, USB, │ - Macintosh │
│ - Skills/MCP │ http │ - Video │ ◄──► │ - Linux │
│ - Web Browser (KVM) │ │ - Power │ VGA, HDMI │ │
│ │ │ - Audio │ │ │
│ │ │ - Files │ │ │
└─────────────────────┘ └────────────────┘ └─────────────┘
vcctrl ships three templates, one per profile-kinds/*.yaml.
Scaffold one with
tools/new-profile.py --kind <kind> --name <yours>
and fill in the REPLACE_ME placeholders, using the machines below as a
reference.
What this project actually runs on, across all three configurations:
vga-ps2A DOS/Windows-era PC with PS/2 keyboard/mouse and analog VGA out.
Hardware:
Distinguishing settings, not a complete config — full example: examples/vcctrl.example.yaml.
capabilities:
input:
backend: usb4vc-uinput
video:
backend: v4l2-ffmpeg
settings:
device: /dev/v4l/by-id/usb-MACROSILICON_xxxx-video-index0
camera:
backend: none # or v4l2-ffmpeg, if you have the second camera
rgb2hdmi-usb4vcADB keyboard/mouse, capture via an RGB2HDMI board. Scaffolded but unverified/untested — no such hardware has run against this project's own build yet, so treat the template's values as a documented guess.
Hardware:
Distinguishing settings, not a complete config — full example: examples/vcctrl-macintosh.example.yaml.
capabilities:
input:
backend: usb4vc-uinput # same backend as Retro PC -- the board swap
# is what changes, not this setting
video:
backend: v4l2-ffmpeg
settings:
device: /dev/v4l/by-id/usb-xxxx-video-index0 # the RGB2HDMI dongle
hdmi-usbAny machine with HDMI out and a spare USB port. The Pi's own USB-C port presents itself as a USB keyboard and mouse straight to the target.
Hardware:
Driving capture, keyboard/mouse emulation and encoding together can throttle a Pi 5 — one capture device per Pi for this configuration.
Distinguishing settings, not a complete config — full example: examples/vcctrl-modernpc.example.yaml.
capabilities:
input:
backend: hid-gadget
settings:
hid_keyboard_device: /dev/hidg0
hid_mouse_device: /dev/hidg1
video:
backend: v4l2-ffmpeg
settings:
device: /dev/v4l/by-id/usb-xxxx-video-index0
analog: false
Needs dtoverlay=dwc2,dr_mode=peripheral under /boot/firmware/config.txt's
[pi5] section.
capabilities:
power:
backend: kasa # kasa | kasa-klap | wemo | shell | none
settings:
host: 192.0.2.20
TP-Link Kasa, Wemo, and shell-scripted relays/PDUs/GPIO are all supported — see examples/vcctrl.example.yaml for backend-specific settings.
A Linux daemon host (/dev/uinput for USB4VC, or a peripheral-capable USB
port for hid-gadget), root and systemd; a capture device that emits
MJPEG natively over V4L2; ffmpeg (with libx264/libopus for the optional
H.264 transport and Opus audio); Python ≥ 3.7 (≥ 3.10 for the MCP server).
1. Flash the daemon host. Raspberry Pi OS or Debian, arm64, Trixie (13)+, SSH enabled.
2. Install USB4VC, then this repo's two local patches:
python3 tools/patch-usb4vc-64bit.py --check # then without --check to apply
python3 tools/patch-usb4vc-board.py --check
3. Clone and configure, from the control host:
git clone <this repo's URL> && cd vcctrl
cp examples/vcctrl.example.yaml vcctrl.yaml # untracked, never commit this
$EDITOR vcctrl.yaml # daemon_host, plug, devices
4. Deploy:
VCCTRL_HOST=<pi-hostname-or-ip> pi/deploy.sh
(control.daemon_host in vcctrl.yaml works instead of the env var;
pi/deploy.sh refuses clearly if neither is set.)
5. Install the CLI and confirm the daemon answers:
sudo cp bin/vcctrl-client /usr/local/bin/vcctrl # or: pi/deploy.sh --client
vcctrl status
vcctrl preflight # one gate: caps, board, power, video, input
6. Drive it:
vcctrl type 'CD \'
vcctrl key enter
vcctrl shot # a frame from the capture device, as a file
7. Connect an agent instead of typing verbs by hand — see below.
8. Scaffold your own profile once the above works:
tools/new-profile.py --kind <vga-ps2|hdmi-usb|rgb2hdmi-usb4vc> --name <yours>.
See docs/PROFILES.md for running more than one target off one daemon.
Skills are portable knowledge — free to copy, grant nothing. MCP is real control of physical hardware — registering it is a hardware-access decision, not a documentation one; never commit a real hostname to get it working.
Skills (Claude Code, Codex, Cursor — no checkout needed):
npx skills add <this repo's URL> \
--full-depth -a claude-code -y \
-s vcctrl-mcp-workflows -s vcctrl-common-workflows \
-s vcctrl-rig-hazards -s vcctrl-camera # --agent codex for Codex
Installs the four hardware-portable skills. Two more
(vcctrl-repo-conventions, vcctrl-webkvm-copy) describe this repo's own
conventions and are left out on purpose — see docs/SKILLS.md
if you want them anyway.
MCP (drives the real hardware):
# daemon mode -- once deployed (docs/MCP-SERVER.md sec. 4), no local checkout
claude mcp add --transport http vcctrl-mcp-daemon https://<your-daemon-host>/mcp
# control mode -- needs a local clone + venv
cd vcctrl
python3 -m venv agent/.venv && agent/.venv/bin/pip install -r agent/requirements.txt
claude mcp add vcctrl-mcp -- "$(pwd)/agent/.venv/bin/python3" "$(pwd)/agent/vcctrl_mcp.py"
Restart the session after registering — /mcp doesn't pick up a fresh
server live. Full reasoning and the safety model: docs/MCP-SERVER.md.
bin/vcctrl control-host CLI -- ssh's to the daemon, no logic itself
bin/vcctrl-client the real CLI; installed on the daemon host as /usr/local/bin/vcctrl
bin/vcctrl-* one-off diagnostics run from the control host (audio, capture, card ID)
daemon/vcctrld.py input/video/audio/power/file server; owns the devices
daemon/vcweb.py the control web KVM; daemon/vcweb_public.py is the read-only mirror
common/vcconfig.py shared config loader (both hosts import it)
agent/vcctrl_mcp.py MCP server exposing the CLI's tools
harness/vcctrl-* cell/sweep/collect -- automated test runs against a target
profile-kinds/*.yaml hardware-configuration templates (tools/new-profile.py reads these)
pi/install.sh systemd units and setup on the daemon host
pi/deploy.sh push + install from the control host
vendor/ third-party code (see THIRD-PARTY.md)
tests/ pytest tests/ -- no hardware required
| configuration | proven | notes |
|---|---|---|
Retro PC (vga-ps2) | working end to end | keyboard, mouse, video, audio, power, file transfer, the web KVM — all on real hardware; docs/lab/FINDINGS.md |
Modern PC (hdmi-usb) | working end to end | no USB4VC needed; multi-profile (one daemon, several targets) proven the same way |
Classic Macintosh (rgb2hdmi-usb4vc) | scaffolded, unverified/untested | templates exist; no such hardware has run against this project's own build yet |
Known gaps: no hardware reset line for a target that swallows Ctrl-Alt-Del (GPIO to the reset header is planned, not built); H.264 transport and full on-screen-keyboard coverage are measured on one board so far. docs/lab/OPEN-FAULTS.md has the complete list of what's broken; docs/KNOWN-LIMITATIONS.md has what doesn't yet adapt to different hardware at all — the DOS-side boot contract, timing constants, install paths, and similar still-hardcoded pieces.
See CONTRIBUTING.md for this repo's conventions. Run
the tests with pytest tests/ — no hardware required; needs Python,
PyYAML, node, a Chromium/Chrome binary, and ffmpeg.
MIT — see LICENSE. Third-party code under vendor/ keeps its own licence; see THIRD-PARTY.md.
Hacker News (1)
626 commits
Python
71.3%
HTML
23.2%
Shell
3.8%