- Rust 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
duh-wgmcp
Windows GUI automation MCP server. Speaks JSON-RPC over stdio. Tools: windows, focus, screenshot, uia_find, click, type, keys, scroll.
Instance ownership
Only one duh-wgmcp process operates at a time, so a single kill-switch hotkey and a single cursor owner exist. Ownership is a lease, not a startup lock: a process claims it on its first tools/call, keeps it while it keeps calling, and releases it after DUH_WGMCP_LEASE_SECS idle. Another opencode instance that calls a tool after that takes over automatically, so a read-only instance that never calls wgmcp never holds the lease. A call from a non-owner returns error code OWNER_CONFLICT naming the owner's pid, how long it has held the lease, and how long since its last call; a process that holds it and stays active is never preempted. A Local\duh-wgmcp-owner-lock mutex guards the lease file (%LOCALAPPDATA%\duh-wgmcp\owner), and a dead owner's pid is detected immediately.
Set DUH_WGMCP_NO_SINGLETON=1 to bypass (the test harness does this to run many servers in parallel).
Kill switch
Global hotkey Ctrl+Alt+Shift+X (left-hand reachable, MOD_NOREPEAT). Pressing it halts the server mid-turn.
When tripped:
- Every subsequent
tools/callis refused with error codeHALTED_BY_USER(a tool result withisError: true, so the agent sees the operator pressed the kill switch and ends the turn).initialize,ping,tools/liststill respond. - In-flight
drag,typeandkeysloops abort at the next iteration. Any held mouse button is released immediately. - Mouse movement and clicks already delivered are not undone.
Re-arm
MCP-only turn detection, no plugin. The halt clears on a tools/call that arrives after DUH_WGMCP_HALT_GAP_MS (default 3000) of quiet: the operator stops the turn, then their next message begins a new turn whose first call arrives after the gap and runs normally. Calls that keep arriving inside the gap stay denied, and DUH_WGMCP_HALT_MAX_SECS (default 300) caps the halt in case a turn never pauses. A model that pauses longer than the gap mid-turn also resumes — the gap is the only turn signal an MCP server has on its own.
Environment
| Variable | Default | Meaning |
|---|---|---|
DUH_WGMCP_HALT_GAP_MS |
3000 |
Quiet time between calls that counts as the turn boundary. |
DUH_WGMCP_HALT_MAX_SECS |
300 |
Hard ceiling on an unbroken halt. |
DUH_WGMCP_LEASE_SECS |
15 |
Idle time after which an owner hands the lease to another instance. |
DUH_WGMCP_BUSY_SECS |
120 |
Lease grace covering the duration of one in-flight tool call. |
DUH_WGMCP_NO_SINGLETON |
unset | Bypass instance ownership. |
DUH_WGMCP_OWNER_FILE |
%LOCALAPPDATA%\duh-wgmcp\owner |
Lease file; set empty to disable. |
Single-owner caveat
RegisterHotKey is system-wide and single-owner: only one process can hold Ctrl+Alt+Shift+X. This only bites if some unrelated application has taken the chord; then the server logs kill switch hotkey Ctrl+Alt+Shift+X is already registered; kill switch disabled and runs without a kill switch.
Build
cargo build --release
Binary: target\release\duh-wgmcp.exe.