v1.15.0
Install
npx -y @harusame64/desktop-touch-mcpChanges
-
A window that closes stops catching your clicks. When an app you had been
working with went away, its last known rectangle stayed in the window cache
and kept answering "the click at this point belongs to me" — with no expiry
and nothing to remove it. Depending on where the point landed you would see
clicks refused with "coordinates are outside the target window", or worse, a
click quietly delivered to whatever now occupies that part of the screen.
Restarting the server was the only thing that reliably cleared it, which is
why reconnecting appeared to be the fix.Two things change. A cached window now stops being a click target once its
entry ages past the cache lifetime, instead of never. And when a window that
was already resolved stops reporting a position at all, its entry is dropped
at that moment, and the click is refused withTarget window rect could not be read — the window may have closedrather than being waved through unchecked.An expired entry means "check again", not "unreachable": when a click lands
somewhere the cache cannot currently account for, the window list is re-read
once and the click proceeds normally if the window is there. So a window that
is open and simply has not been looked at for a while stays clickable, and a
click that is refused has actually been checked rather than merely missing
from a cache.A click that names a window is now refused when the point turns out to be
inside a different application's window, and the response says which window is
actually there. It used to be delivered to that other window and reported as a
success, with the mismatch mentioned only in the server's own log. The same
applies when the named window is not open at all — the click is refused and
the response names the window that occupies the point, instead of quietly
clicking it. A click whose point lands in another window of the SAME
application still goes through — a dialog the application opened, but also a
second window of it — so this refusal is about hitting a different program,
not a different window. A browser window still matches its browser's name
("Google Chrome", "Microsoft Edge") even when the page title comes first.What you may notice: a click that used to land somewhere unintended now
fails, and tells you either that the target window was replaced or which
window your coordinates actually fall in. Take a fresh screenshot and carry
on. A window that is open and has not moved, addressed by a title it still
carries or by its handle, behaves exactly as before.Not covered by this change: the separate cache of screenshot-time window
positions, used to correct coordinates when a window moves between the
screenshot and the click. It keeps entries for 90 seconds and does not record
which window they belong to, so a window that closes and reopens can still
skew that correction until the entry expires. That one is a separate fix. -
The diagnostic log now records how a terminal window relates to the server
process itself. When a write lands in a terminal, it is worth knowing
whether that terminal is the one this server is running inside — the failure
mode where a command is typed into your own session instead of the window you
named. The log now answers that from data rather than from guesswork: at
startup atopology_snapshotrecord notes the console window this process is
attached to (if any), whether it owns a console host of its own, and the chain
of processes it was launched from; then akeyboardorterminalwrite that
resolves to a terminal window appends atopology_relationrecord saying who
owns that window, whether that owner is the server process or one of its
ancestors and how far up, and — for a classic console — whether the console
host's parent is still alive. Records are written for every such destination,
not only the suspicious ones, so ordinary use produces a usable baseline.
Reads are not recorded, so a longterminal(action='run')does not bury the
writes under its own polling.When a destination does turn out to be owned by this server's own process
tree, the tool adds a non-blocking warning to its response where it reports
warnings at all. Nothing is refused and nothing is retargeted — the check
is deliberately imprecise (Windows Terminal hosts several unrelated windows in
one process, so it can point at a window that is perfectly fine to write to),
and it exists to measure how often the situation arises, not to guard against
it. Turn the whole log off withDESKTOP_TOUCH_DIAGNOSTIC_LOG_DISABLE=1. -
The diagnostic log now records how a
windowTitlewas resolved, and where
the input actually went. When awindowTitlematches more than one window,
or matches none and a fallback picks a window by process name instead, the
tool response has never said so — the keystrokes simply arrive somewhere else.
Each resolution now appends aresolverecord to the diagnostic log with the
number of matching windows, the window that was chosen, the runners-up, and a
flag when the process-name fallback fired; each input dispatch —keyboard,
terminal,scrollanddesktop_act's background writes — appends a
matchingdispatch_sinkrecord naming the channel used, the window it was
addressed to, and the window that was in the foreground at that instant. The
record is written immediately before the write leaves the process, so a call
that is refused or fails first is not on record as having sent anything. Records from one tool call share a correlation id, so a
resolution can be matched to the write it produced even when calls overlap.
Behaviour is unchanged — nothing is refused or retargeted because of this —
with one addition:scrollwith awindowTitlethat matches several windows
now returns aN windows match "…"; using the frontmostwarning on the
response, the same wording other tools already use.Window titles are hashed by default. A title can carry a file name, a mail
subject, or a browser page title, and the diagnostic log is on by default, so
titles and queries are recorded as a short hash plus their length. Set
DESKTOP_TOUCH_RESOLVE_LOG_RAW=1to record the text in clear alongside the
hash — useful when reading your own log, and best left off otherwise. The log
lives at%USERPROFILE%\.desktop-touch-mcp\logs\diagnostic.log; set
DESKTOP_TOUCH_DIAGNOSTIC_LOG_DISABLE=1to turn the whole log off. -
keyboardwrite actions (type/press/sequence) now require a
destination. Calls withoutwindowTitleorhwndused to land on whatever
window happened to be foreground at that instant — including a window you had
just clicked into yourself. They now stop with a typed
code:"DestinationRequired"error and recovery suggestions before any key is
sent, so a missing target costs one retry instead of stray characters in
your own editor. Passinghwndon its own is enough, even for a window with
no title. Nothing changes for calls that already name a target, and
mouse_*/terminalare unaffected. To deliberately type into the current
foreground window, setDESKTOP_TOUCH_REQUIRE_DESTINATION=0: the stop becomes
a warning on the response instead — never a silent pass. Setting
DESKTOP_TOUCH_AUTO_GUARD=0still turns the whole guard layer off, this check
included. AwindowTitlethat is empty or only spaces counts as no target at
all. A window that has no title can be addressed byhwnd, but only while it
is already the foreground window — keyboard focus and guarding cannot target a
titleless window yet — and that call comes back with a warning saying the
input was delivered unguarded. -
The npm launcher no longer kills the runtime mid-startup when its own stdin
is already closed at launch. In closed-stdin environments — CI harnesses,
one-shot checks likenpx … --helpwith input redirected from an empty
source, or clients that close stdin immediately — the launcher treated the
end-of-input as a shutdown request and force-stopped the runtime after
1 second. The runtime needs longer than that just to load its native
components, so it was terminated before it could even parse its command
line and the command exited with no output. The launcher now always
allows the runtime a 10-second startup window measured from launch:
end-of-input never forces a stop before that window has passed, and
after it has passed the previous 1-second limit applies as before.
Interactive use and normal MCP clients (which hold stdin open and shut
down promptly when their input ends) are unaffected.