DeskVNC dvv MCP tool reference
The dvv server speaks the Model Context Protocol and exposes exactly 25 tools, asserted as TOOL_COUNT: usize = 25 in the manifest at crates/dvv/src/mcp/manifest.rs. Argument names, types, enums, and required fields below are quoted verbatim from that file. Any field not listed under "Required" is optional.
Two design choices appear on every tool. The selector trio (limbId, groupId, member) lets a tool stay the same whether the agent drives one machine or a hundred. The generation argument on every coordinate tool proves the coordinate was computed against the geometry on the wire; a click without it is refused.
Four codes come up on every agent loop: LIMB_GONE, GEOMETRY_CHANGED, SCREEN_CHANGED, and LEASE_REVOKED. Other codes are named next to the tool that raises them.
dvv_hosts
List saved machines and the machines discovery has found, with the protocol and whether a credential is stored. The reply never carries a secret.
discovered(boolean): true to list what discovery has seen.
Raises POLICY_DENIED without the hosts.read capability. Shell equivalent: dvv hosts.
dvv_limbs
Every limb this attachment can see: id, protocol, address, state, size, capabilities, and who holds the lease. A machine DeskVNCViewer already has open but this attachment has not attached is listed under available; passing one of those limbIds attaches it on the way in. No arguments. Shell equivalent: dvv limbs.
dvv_open
Opens a limb against a saved host (by hostId) or an endpoint (by address and protocol). The credential is read from the keychain inside the application. Returns as soon as the session task spawns; poll dvv_status or call dvv_wait with until: "connected".
hostId(string): a saved machine, fromdvv_hosts.address(string): hostname or IP, for an unsaved machine.port(number): defaults to 5900 forvnc, 3389 forrdp, 22 forssh.protocol(string, enumvnc/rdp/ssh): required withaddress. Refused withBAD_REQUESTwhen passed withhostId.slot(number): 0 attaches to whatever is already live; above 0 opens a fresh session. Refused withSLOT_REFUSEDon protocols that do not give concurrent sessions.perceive(boolean): pay for a framebuffer mirror sodvv_screenhas something to read.
Shell equivalent: dvv open <name or hostId> --perceive.
dvv_close
Closes a limb and releases anything it held. Idempotent: closing a limb that is already gone returns an ordinary success. Every intent in flight is withdrawn and settles first.
limbId(string, required).
Shell equivalent: dvv close <limbId>.
dvv_reconnect
Drop the connection and dial the machine again, then wait for it to come back. The person at the remote end does nothing; the credential is applied as for any open.
limbId,groupId,member(selector trio).timeoutMs(number): default 20000.
Shell equivalent: dvv reconnect <limbId>.
dvv_status
State, protocol, size, geometry generation, lease holder, and the negotiated signals for one limb, as the full dvv.observation.v1 object. The cheapest call in the manifest, safe to call constantly. Reads no pixels, so it does not clear the typing fence. After a LEASE_REVOKED, read lease.human_took_over from this object: true means a person is driving and the right move is to stop.
limbId,groupId,member(selector trio).
Shell equivalent: dvv status <limbId>.
dvv_control
Acquire, release, or check the control lease. This is what lets an agent act at all when a person might be present.
limbId,groupId,member(selector trio).action(string, required, enumacquire/release/status/yield/yield_status):yieldandreleaseare the same act;yieldrecords a reason.yield_statusreads who holds the lease without changing anything.reason(string): shown to the person onacquire, recorded onyield.waitMs(number,acquireonly): 0 fails immediately.
Raises LEASE_NOT_HELD when an act needs the lease and the agent does not have it, and LEASE_REVOKED when the lease went away under the call. Shell equivalent: dvv control acquire <limbId>.
dvv_click
A pointer event at framebuffer pixel coordinates. Desktop limbs only; terminal limbs return a clean WRONG_PROTOCOL error.
limbId,groupId,member(selector trio).generation(number): thegeometry.generationfrom the observation this coordinate was computed against.action(string, required, enummove/click/double/right/middle/drag/scroll).x(number): framebuffer pixels from the left.y(number): framebuffer pixels from the top.toX,toY(number): drag only, where the button is released.button(string, enumleft/middle/right): drag only.direction(string, enumup/down/left/right): scroll only, required.clicks(number): scroll only, wheel clicks. Default 1.modifiers(array of strings): held for the duration.
Raises GEOMETRY_CHANGED when the screen resized since the coordinate was computed, UNFENCED when no generation is supplied, OUT_OF_BOUNDS for coordinates outside the framebuffer, and WRONG_PROTOCOL on a terminal limb. Shell equivalent: dvv click <limbId> <x> <y> and dvv click <limbId> <x> <y> --action double.
dvv_type
Types a string into whatever has focus. On a desktop limb this is refused with SCREEN_CHANGED unless the agent has called dvv_screen since the last large repaint, and refused outright on a limb that has never been read. Terminal limbs are not fenced, because a PTY echoes what it is sent into a stream you can read back.
limbId,groupId,member(selector trio).text(string, required).wpm(number): throttle in words per minute.
Raises SCREEN_CHANGED and WRONG_PROTOCOL on a desktop limb. Shell equivalent: dvv type <limbId> "text to type".
dvv_key
One named key or a chord. Fenced exactly like dvv_type: on a screen holding a selection, Enter, Delete, and ctrl+v are as destructive as a letter, so this is refused with SCREEN_CHANGED until the agent has read the screen since the last repaint.
limbId,groupId,member(selector trio).keys(string, required): one name, or a chord joined with+, for exampleEnter,Escape,Tab,ctrl+c,alt+F4,ctrl+alt+Delete,super+r.
Raises SCREEN_CHANGED and UNKNOWN_KEY when the key name is not in the fixed table. Shell equivalent: dvv key <limbId> super+r.
dvv_screen
What the limb looks like now, with size, geometry generation, and coverage beside it. The call that clears the typing fence: only a call that comes back with pixels counts. form: "damage-crop" after a settled action is the cheapest useful answer on a desktop limb.
limbId,groupId,member(selector trio).form(string, enumfull/region/damage-crop).region(object):x,y,w,hin native resolution. Used withform: "region"only.scale(number): whole frame only. Refused beside a region.
A desktop limb returns an image content block with an imageSpace line beside it giving the region, both dimensions, and the scale: read that line to turn a point on the picture back into a coordinate on the remote. Without perceive: true on dvv_open, a desktop limb refuses rather than hand back a blank picture. Shell equivalent: dvv screen <limbId> --scale 0.5 --out ./dvv-screen.png.
dvv_wait
Blocks server side until something happens, up to timeoutMs, clamped to 25000 so a wait can never outlive the agent's own call timeout. A timeout is an ordinary success with settled: false and the observation, never an error.
limbId,groupId,member(selector trio).until(string, required, enumconnected/screen-stable/screen-changed/text/text-gone/idle/exit).text(string): required fortextandtext-gone.quietMs(number): default 750.timeoutMs(number): default 8000, clamped to 25000.
Raises NOT_READY or NOT_CONNECTED when the limb has not reached the state asked for, and a plain TIMEOUT when the deadline passed without settling. Shell equivalent: dvv wait <limbId> --until connected and dvv wait <limbId> --until screen-stable.
dvv_clipboard
Reads or writes the remote clipboard. get reads whatever the person at the machine last copied, which is a password more often than anyone would like, and set puts text on the remote clipboard.
limbId,groupId,member(selector trio).action(string, required, enumget/set).text(string): set only.
get is not served on this build and returns NOT_IMPLEMENTED; the plane does not subscribe to the event stream that carries the answer. set works. Raises POLICY_DENIED without the corresponding capability.
dvv_run
Runs one command on a terminal limb and returns stdout, stderr, and the exit code. The exit status is never invented: a command killed by a signal reports the signal and no code, a command still running when the deadline passes reports no status at all and says the deadline was what ended it. The channel starts in the home directory with a fresh environment.
limbId,groupId,member(selector trio).command(string, required): a single string, not an argv vector.cwd(string): stated rather than assumed.timeoutMs(number, required): no default.maxOutputBytes(number): above this the output is truncated and the response says how much went.
Raises POLICY_DENIED without the exec capability, which is in no role bundle and must be named on the token. Shell equivalent: dvv run <limbId> --timeoutMs 5000 "command".
dvv_term_read
Terminal output since a cursor, or since the limb opened, plus a new cursor. Not served on this build, because the plane keeps no scrollback ring: inventing one from the visible grid would silently lose whatever scrolled off. Returns NOT_IMPLEMENTED; use dvv_run instead.
limbId,groupId,member(selector trio).since(string): a cursor from an earlier call.
dvv_term_send
Raw bytes to the terminal, for the cases a command cannot express: answering a prompt, sending Ctrl+C, driving a full screen program. This is the terminal path that works in this build. Does not wait and does not know whether what was sent worked: pair it with dvv_wait.
limbId,groupId,member(selector trio).text(string): sent as UTF-8.bytesHex(string): hex, two characters per byte, no separators.03isCtrl+C.
dvv_files
File transfer over the machine's own SFTP sidecar, a second SSH connection alongside the screen. Reading needs files.read and writing needs files.write; neither implies the other, because holding the keyboard on a machine is not authority over its disk.
limbId,groupId,member(selector trio).action(string, required, enumlist/get/put/mkdir/remove/rename/home).path(string): the remote path.~and~/thingresolve against the remote user's home directory. Forrenamethis is the existing name.to(string):renameis the new remote path.getis an absolute local path to save the file to.from(string):putonly, an absolute local path.contentBase64(string):putonly, the bytes to write.mode(number):putonly, permission bits.recursive(boolean):removeonly.
Raises FILES_UNAVAILABLE for a machine with no SSH server, for one the application cannot authenticate to, and for a host key that has not been trusted yet. Transfers are synchronous: a get or a put moves the whole file in windows inside this one call and answers when it is done.
dvv_transfer
Nothing to report, by design, and not a build that is missing something. dvv_files does not queue anything on this surface: a get or a put runs to completion inside its own tool call and answers with what it moved, so there is no transfer id to look up with status and nothing in flight for cancel to stop. The tool exists so a name mentioned in a document does not leave an agent guessing.
limbId,groupId,member(selector trio).action(string, required, enumstatus/cancel).transferId(string).
dvv_group_open
Opens several limbs at once and returns a groupId to address them together (dvv_group_run) or one at a time (any tool's groupId plus member). Every member is a real connection that stays open until dvv_group_close. If any member fails, the ones this call opened are closed again.
hostIds(array of strings): saved machines, fromdvv_hosts.addresses(array of strings): endpoints, for unsaved machines.protocol(string, enumvnc/rdp/ssh): required withaddresses.perceive(boolean): attach a framebuffer mirror to every member.
dvv_group_list
Open groups, or one group's members with their index, limbId, host, and state. Cheap and local: reads the server's own registry, no round trip.
groupId(string).
dvv_group_grow
Opens more limbs and appends them to a group. New members get the next index; existing members are untouched.
groupId(string, required).hostIds(array of strings).addresses(array of strings).protocol(string).perceive(boolean).
dvv_group_shrink
Closes the n most recently added members and drops them from the group. Fails rather than clamping when n is larger than the group holds, because a clamp turns close three into close everything.
groupId(string, required).n(number, required).
dvv_group_close
Closes every limb in a group and forgets it. A member that has already gone is not an error.
groupId(string, required).
dvv_group_run
Runs one action on every member of a group, concurrently and not in a loop: every member starts before any finishes. One member failing is reported for that member and never stops the others.
groupId(string, required).action(string, required, enumwait/screen/status/signals/type/key/click/run).arguments(object): the arguments the single limb tool of that name takes, minus the selector trio.
dvv_signals
Which negotiated signals this session has, and what each absence means. Every entry is live, absent, or unknown with a reason, and never a default: absent means the far side does not do it and it is permanent for this session, unknown means nothing has arrived yet and may resolve.
limbId,groupId,member(selector trio).
Read led_state before typing a password: a defaulted Caps Lock of false is a lie that costs an account lockout. window_structure is always absent on every protocol this build speaks, and that entry exists so the negative is stated rather than inferred from a missing field. Shell equivalent: dvv signals <limbId>.
How the four codes fit the loop
LIMB_GONE: the limb id resolved to nothing or the selector named no limb. Calldvv_limbsfor the current ids; a limb id is reproducible, so the same machine at the same slot has the same id when it comes back.GEOMETRY_CHANGED: the screen resized under the action and nothing was delivered. Calldvv_screenagain and recompute the coordinate against the new generation.SCREEN_CHANGED: something large repainted since the last pixel read, so nothing was typed. Calldvv_screen, read what has focus and what is selected, and only then type.LEASE_REVOKED: the lease went away underneath a call that had it. Calldvv_controlwithaction: "yield_status"before anything else. Iflease.human_took_overistrue, a person is driving and the right move is to stop and tell the user, not to reacquire and retry.