# Desktop control — drive ANY app (Teams/WhatsApp/Xcode), not just Chrome

Jarvis has a "desktop" MCP = **macos-mcp** (pip `macos-mcp`, tools: App, Snapshot
[accessibility tree + screenshot], Click, Type, Move, Scroll, Shortcut, Shell,
Scrape, Notification). Chosen over pixel-only tools (automation-mcp) because it
acts on real UI ELEMENTS via the accessibility tree — far more reliable.

**Critical wiring (voice/desktop_server.py):** it runs as an HTTP server that the
ENGINE launches (`macos_mcp serve --transport streamable-http --port 8765`), NOT
as a normal stdio MCP. Reason: a stdio MCP server is spawned by the Claude CLI,
which macOS attributes TCC to the CLI helper (NOT Jarvis.app) → macos-mcp exits
"Missing permissions: Accessibility". Launched by the engine, it inherits
Jarvis.app's grant. Jarvis connects over loopback HTTP (llm.py "desktop" =
type:http, url 127.0.0.1:8765/mcp, bearer key). Same responsible-process wall as
[[screen-recording-fix]].

**Needs Accessibility (mouse/keyboard) — and that grant goes STALE the same way
Screen Recording did.** If desktop tools fail or macos-mcp won't start, the fix
is identical: `tccutil reset Accessibility com.ahmed.jarvis`, grant Jarvis in
System Settings > Privacy > Accessibility, then FULLY relaunch. main.py requests
Accessibility on startup so a relaunch self-registers Jarvis in the list.

Toggle with MCP_DESKTOP=0. Vision stays on snap.sh (engine-side, Tahoe-safe) —
macos-mcp's own screenshot uses a Tahoe-blank API, so prefer Snapshot for the AX
tree but snap.sh for actually seeing pixels.
