Registering DeskVNC with Codex CLI
Codex CLI is OpenAI's terminal agent, and it speaks the Model Context Protocol over stdio. Pairing it with dvv turns the agent into something that can see a real remote desktop, click buttons, and type, all through the VNC, RDP, or SSH protocol that the remote machine already speaks. No software is installed on the remote target, and the user can take the wheel back with one click. The DeskVNC documentation ships the exact ~/.codex/config.toml shape for this pairing, and dvv setup codex writes the same entry for you.
Prerequisite: the dvv binary
dvv is the executable that ships inside DeskVNCViewer. Install the viewer, open it once, and save a machine with its password so Codex can reach it. The binary sits next to the application:
- macOS:
/Applications/DeskVNCViewer.app/Contents/MacOS/dvv - Windows:
%LOCALAPPDATA%\DeskVNCViewer\dvv.exefor a per-user install, orC:\Program Files\DeskVNCViewer\dvv.exefor the all-users install - Linux:
/usr/bin/dvvon the standard.deb. The AppImage copiesdvvto~/.local/share/DeskVNCViewer/bin/dvv
The path you point Codex at is the path the viewer installed.
The registration step
Codex reads ~/.codex/config.toml. Add an [mcp_servers.deskvnc] table that names the dvv binary and the mcp --stdio arguments. The shape is documented in docs/AGENTS.md in the DeskVNC repository.
On macOS, append this to ~/.codex/config.toml:
[mcp_servers.deskvnc]
command = "/Applications/DeskVNCViewer.app/Contents/MacOS/dvv"
args = ["mcp", "--stdio"]On Linux, the same table with the system path:
[mcp_servers.deskvnc]
command = "/usr/bin/dvv"
args = ["mcp", "--stdio"]On Windows, use a TOML-safe path. The cleanest way is to add the binary's directory to the user's PATH once, then name the bare dvv.exe:
[mcp_servers.deskvnc]
command = "dvv.exe"
args = ["mcp", "--stdio"]If you would rather not edit the path, set command to the absolute path with forward slashes, which TOML handles cleanly:
[mcp_servers.deskvnc]
command = "C:/Program Files/DeskVNCViewer/dvv.exe"
args = ["mcp", "--stdio"]The shell form, for the case where you do not want to hand-edit TOML, is dvv setup codex. It writes the same [mcp_servers.deskvnc] table into ~/.codex/config.toml, keeps a backup of the existing file, drops the skill into ~/.codex/skills, and repoints any older entry that names a stale dvv.
A first agent session
A typical first prompt to Codex names a saved machine and asks the agent to do something visible. The agent drives the four-call loop:
dvv_hosts {}
dvv_open {"hostId": "ci-runner", "perceive": true}
dvv_control {"limbId": "limb-9b22", "action": "acquire"}
dvv_screen {"limbId": "limb-9b22", "form": "full", "scale": 0.25}The screenshot comes back with a geometry_generation. The agent decides where to click and sends:
dvv_click {"limbId": "limb-9b22", "x": 480, "y": 220, "generation": 8}
dvv_type {"limbId": "limb-9b22", "text": "tail -n 50 build.log", "wpm": 3000}
dvv_key {"limbId": "limb-9b22", "keys": "Return"}A second dvv_screen reads the damage region and the agent reports the last lines of the build log. Each observe-then-act cycle is short enough that the agent keeps up with a moving desktop, and a damage-crop form keeps the next observation small when only a corner of the screen changed.
Machines are independent. Each saved machine is its own limb with its own lease, so an agent runs as many loops in parallel as it has machines. The same action across a set of machines is one dvv_group_run call that reports each member's outcome separately, and any single-limb tool accepts a groupId plus a member to address one of them.
The coordinate generation fence
Every dvv_screen carries a geometry_generation and a content_generation. Clicks must echo the geometry generation they were computed against. A click computed against a stale generation is refused rather than landing in the wrong place after a window resize. Typing and keys are fenced by the content generation: dvv_type and dvv_key are refused with SCREEN_CHANGED when something window-sized has repainted since the last dvv_screen. The fence is the safety net that stops an agent from typing into a text editor that came up holding somebody's file with all of it selected. One dvv_screen clears the fence, dvv_status does not (it reads no pixels), and there is no override. Terminal limbs are not fenced, because a PTY echoes what it is sent into a stream the agent reads back.
Human takeover
The person at the remote machine can take control back at any moment. The viewer shows an "agent driving" badge while the agent holds the lease, and one click in the viewer hands the desktop back to the person. The plane releases every held key and every held button the moment a person takes over, so a half-finished drag cannot strand the desktop. The recipient can revoke the agent's control or end the session outright. This is attended automation by design: the agent never has a stronger position than the person at the keyboard.
Troubleshooting
| Symptom | Likely cause | Try this |
|---|---|---|
LIMB_GONE from any tool |
The viewer closed the connection or the last dvv_close detached the limb |
Run dvv limbs to list live limbs, then dvv open <host> again |
SCREEN_CHANGED on dvv_type or dvv_key |
A window-sized region repainted since the last dvv_screen |
Call dvv_screen once, then retry the action |
Codex does not list deskvnc in /mcp |
TOML parse error or the file is in the wrong place | Run tomlq over ~/.codex/config.toml to confirm it parses, then restart Codex |
dvv exits immediately on spawn |
The path in command is wrong or the file is not executable |
Run the command value in a shell and confirm dvv mcp --stdio starts |
| Click lands in the wrong place | Stale geometry_generation after a resize |
Take a fresh dvv_screen, then send the new generation with the click |
Links
- Project: <https://github.com/psmux/DeskVNC>
- Hub: <https://deskvnc-hub.pages.dev/>
- Raw agent skill: <https://github.com/psmux/DeskVNC/blob/main/skills/deskvnc/SKILL.md>
- Integration notes for every client: <https://github.com/psmux/DeskVNC/blob/main/docs/AGENTS.md>