Registering DeskVNC with OpenCode
OpenCode is the open source terminal agent that reads a project-scoped opencode.json. It speaks the Model Context Protocol over both stdio and Streamable HTTP, and DeskVNC's dvv answers on both. Pairing them turns OpenCode into an agent that can see and click a real remote desktop, with no software installed on the remote machine, and the user can take the wheel back with one click. The two transports are both verified end to end by the project's own workflow against the shipped build.
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. The binary lives 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 OpenCode at is the path the viewer installed.
The registration step
OpenCode reads opencode.json at the project root, and ~/.config/opencode/opencode.json for the user-level file. Both transports are documented and verified. On macOS:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"deskvnc": {
"type": "local",
"command": [
"/Applications/DeskVNCViewer.app/Contents/MacOS/dvv",
"mcp",
"--stdio"
],
"enabled": true
},
"deskvnc-http": {
"type": "remote",
"url": "http://127.0.0.1:7333/mcp",
"headers": { "Authorization": "Bearer <token>" },
"enabled": true
}
}
}On Linux and Windows, swap the command path to /usr/bin/dvv or %LOCALAPPDATA%\DeskVNCViewer\dvv.exe and use the platform's path separator. The deskvnc-http entry is identical across platforms once you substitute a token. Set the token yourself with DVV_MCP_TOKEN so it survives a restart, and dvv mints one and prints it once if you do not.
The same wiring from a terminal is dvv setup opencode. It writes the same MCP entries into opencode.json, keeping a backup and any comments, and repoints an older entry that names a stale dvv. The shell form covers the case where you do not want to hand-edit the JSON.
A first agent session
OpenCode's free vision models can drive a Windows desktop end to end. A typical opening prompt is short:
You: Open "build-server" and tell me the load average.The agent drives the four-call loop against the saved machine:
dvv_hosts {}
dvv_open {"hostId": "build-server", "perceive": true}
dvv_control {"limbId": "limb-2c4e", "action": "acquire"}
dvv_screen {"limbId": "limb-2c4e", "form": "full", "scale": 0.25}The screenshot comes back with a geometry_generation of, say, 17. The agent reads the picture, finds the terminal, and acts:
dvv_click {"limbId": "limb-2c4e", "x": 612, "y": 348, "generation": 17}
dvv_type {"limbId": "limb-2c4e", "text": "uptime", "wpm": 3000}
dvv_key {"limbId": "limb-2c4e", "keys": "Return"}
dvv_screen {"limbId": "limb-2c4e", "form": "damage-crop"}A second dvv_screen confirms the load average printed where the agent expected it. The whole observe-then-act cycle is well under fifty milliseconds on a LAN-attached machine, so the agent keeps up with a real human workflow.
The coordinate generation fence
dvv_screen reports two generations: a geometry_generation and a content_generation. Clicks must carry the geometry generation they were computed against. A click computed against a screen that has since resized is refused rather than landing somewhere unintended. Typing and keys carry no coordinate, so they need the content generation: a keystroke lands wherever focus is, and focus moves when a dialog or a new window appears. dvv_type and dvv_key are refused with SCREEN_CHANGED when something window-sized has repainted since the last dvv_screen.
The fence protects the user. An agent that read the screen three seconds ago and decides to type into a coordinate it computed then is exactly the agent that ends up typing into a different window. A single fresh dvv_screen clears the fence, 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 the wheel 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 when a person takes over, so a half-finished drag cannot strand the desktop. The recipient can also 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 |
OpenCode does not list deskvnc after editing opencode.json |
The JSON did not parse, or the file is in the wrong place | Validate the JSON, then restart OpenCode |
| HTTP transport returns 401 | The bearer token changed | Re-export DVV_MCP_TOKEN to the same value, then restart dvv |
| 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>