Summary
On Windows 11 (build 26200), the automata-agent MCP server's desktop tools silently return empty results — desktop list_windows → [], element_tree / find_elements → [] — while vision/screenshot tools work fine. No error is surfaced. The bundled standalone helpers (list-windows.exe, element-tree.exe) work perfectly on the same host, same session.
Environment
- Windows 11 Pro, build 10.0.26200
- automata-agent.exe dated 2026-04-05 (15,014,400 bytes), run via
--stdio as a Claude Code MCP server
- Same user session (session 1), no elevation mismatch
Empirical evidence chain
desktop list_windows via MCP → [] (no error). element_tree by hwnd → []. Title lookup → proper error (No window found with title=...), so the agent is alive and responding.
- UIA from .NET in the same session (PowerShell,
System.Windows.Automation) → enumerates 6 root children correctly, including the target window. The host UIA stack is healthy.
- The bundled
list-windows.exe helper run standalone → works, lists all windows.
- A process watcher confirmed the agent spawns no helper processes during
desktop calls — the enumeration runs in-process.
- A fresh agent instance driven directly over JSON-RPC stdio (initialize → tools/call
desktop list_windows) → also [] (agent log: automata_client: ← list_windows, result []). Not state corruption — systemic.
--self-test only validates ui-workflow.exe and ui-sight-windows.exe; the in-process UIA path is never covered.
Root-cause analysis (from the open automata-windows source)
The agent binary is closed-source, but the automata-windows crate shows the pattern:
desktop.rs:73 — Desktop::application_windows() uses UIAutomation::new_direct(), which skips COM initialization and assumes the calling thread already initialized COM correctly.
lib.rs:187-193 — init_com() calls CoInitializeEx(None, COINIT_MULTITHREADED) but discards the result (let _ =), and only affects the thread it happens to run on.
desktop.rs:446-470 (tooltip_windows) — UIA failures are swallowed into empty results: let Ok(auto) = UIAutomation::new_direct() else { return vec![]; } plus find_all(...).unwrap_or_default(). If the agent wraps application_windows() errors the same way, a COM/threading failure surfaces as a clean [] — exactly what we observe.
desktop.rs:290-293 — other code paths already anticipate RPC_E_CHANGED_MODE (0x80010106), i.e. threads in the process can disagree about their COM apartment.
If the MCP server dispatches tool calls on async runtime worker threads (tokio), those threads may never have run init_com() — or may have been initialized STA by clipboard/dialog code. Either way, in-process UIA calls then violate Microsoft's documented UIA threading contract, whose stated failure modes include empty/unresponsive results:
This also explains why the standalone helpers work: their main() initializes COM on the same thread that makes the UIA calls.
Suggested fix
- Route all in-process UIA client calls through a dedicated, window-less MTA worker thread (initialize COM once on that thread; marshal requests to it), per the MS threading guidance.
- Alternatively, call
CoInitializeEx + check RPC_E_CHANGED_MODE at every UIA entry point (the pattern resolve_lnk() already uses at desktop.rs:206-209) before UIAutomation::new_direct().
- Propagate errors instead of returning
vec![] — a silent empty list is indistinguishable from "no windows", which made this very hard to diagnose. Even a log line at warn level would have saved hours.
- Consider extending
--self-test to cover the in-process UIA path (list_windows returning ≥1 window on a desktop session would have caught this).
Happy to provide more diagnostics from this host if useful.
Summary
On Windows 11 (build 26200), the
automata-agentMCP server's desktop tools silently return empty results —desktop list_windows→[],element_tree/find_elements→[]— whilevision/screenshot tools work fine. No error is surfaced. The bundled standalone helpers (list-windows.exe,element-tree.exe) work perfectly on the same host, same session.Environment
--stdioas a Claude Code MCP serverEmpirical evidence chain
desktop list_windowsvia MCP →[](no error).element_treebyhwnd→[]. Title lookup → proper error (No window found with title=...), so the agent is alive and responding.System.Windows.Automation) → enumerates 6 root children correctly, including the target window. The host UIA stack is healthy.list-windows.exehelper run standalone → works, lists all windows.desktopcalls — the enumeration runs in-process.desktop list_windows) → also[](agent log:automata_client: ← list_windows, result[]). Not state corruption — systemic.--self-testonly validatesui-workflow.exeandui-sight-windows.exe; the in-process UIA path is never covered.Root-cause analysis (from the open
automata-windowssource)The agent binary is closed-source, but the
automata-windowscrate shows the pattern:desktop.rs:73—Desktop::application_windows()usesUIAutomation::new_direct(), which skips COM initialization and assumes the calling thread already initialized COM correctly.lib.rs:187-193—init_com()callsCoInitializeEx(None, COINIT_MULTITHREADED)but discards the result (let _ =), and only affects the thread it happens to run on.desktop.rs:446-470(tooltip_windows) — UIA failures are swallowed into empty results:let Ok(auto) = UIAutomation::new_direct() else { return vec![]; }plusfind_all(...).unwrap_or_default(). If the agent wrapsapplication_windows()errors the same way, a COM/threading failure surfaces as a clean[]— exactly what we observe.desktop.rs:290-293— other code paths already anticipateRPC_E_CHANGED_MODE(0x80010106), i.e. threads in the process can disagree about their COM apartment.If the MCP server dispatches tool calls on async runtime worker threads (tokio), those threads may never have run
init_com()— or may have been initialized STA by clipboard/dialog code. Either way, in-process UIA calls then violate Microsoft's documented UIA threading contract, whose stated failure modes include empty/unresponsive results:This also explains why the standalone helpers work: their
main()initializes COM on the same thread that makes the UIA calls.Suggested fix
CoInitializeEx+ checkRPC_E_CHANGED_MODEat every UIA entry point (the patternresolve_lnk()already uses atdesktop.rs:206-209) beforeUIAutomation::new_direct().vec![]— a silent empty list is indistinguishable from "no windows", which made this very hard to diagnose. Even a log line atwarnlevel would have saved hours.--self-testto cover the in-process UIA path (list_windowsreturning ≥1 window on a desktop session would have caught this).Happy to provide more diagnostics from this host if useful.