Windows GUI MCP for OpenCode — 8 tools, Win32/UIA/SendInput, no shell junk.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-29 21:55:48 +02:00
src feat: add kill switch and instance lease ownership 2026-09-29 21:55:48 +02:00
tests feat: add kill switch and instance lease ownership 2026-09-29 21:55:48 +02:00
.gitignore feat: add duh-wgmcp Windows GUI automation MCP server 2026-09-26 09:27:01 +03:00
Cargo.lock feat: add kill switch and instance lease ownership 2026-09-29 21:55:48 +02:00
Cargo.toml feat: add kill switch and instance lease ownership 2026-09-29 21:55:48 +02:00
README.md feat: add kill switch and instance lease ownership 2026-09-29 21:55:48 +02:00

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/call is refused with error code HALTED_BY_USER (a tool result with isError: true, so the agent sees the operator pressed the kill switch and ends the turn). initialize, ping, tools/list still respond.
  • In-flight drag, type and keys loops 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.