# Claude Desktop Automation with MCP Control Automatically send periodic messages to Claude Desktop on macOS, with **Claude controlling its own wake schedule** via MCP and **bidirectional Matrix integration** for AI-powered messaging. ## 🎯 How It Works **Three-part system:** 1. **Automation Daemon** - Python process that: - Runs continuously in the background - Sends messages to Claude Desktop on a timer (with CMD+R refresh) - Reads wake schedule from shared state file - Monitors for Matrix wake requests - Uses AppleScript for reliable message delivery 2. **Wakeup Control MCP** - MCP server that lets Claude: - Adjust when it wants to be woken next - Pause/resume automation - Check status 3. **Matrix Integration** (Optional) - Bidirectional messaging: - Monitor Matrix rooms for incoming messages - Wake Claude when messages arrive (2-min rate limit) - Claude can read messages and respond via MCP tools - Supports text + images with automatic compression **The magic:** Claude can call `next_wakeup(30)` to say "wake me in 30 minutes instead of the default 60", and Matrix messages automatically wake Claude when your friends message you! ## Architecture ``` ┌──────────────────────────────────────────────┐ │ Matrix Monitor (Python/matrix-nio) │ │ - Listens for Matrix messages │ │ - Queues messages to state file │ │ - Sets matrix_wake_requested flag │ │ - 2-minute rate limiting │ └──────────────┬───────────────────────────────┘ │ │ (writes Matrix messages) ▼ ~/.claude-automation-state.json ▲ │ (reads state + Matrix requests) │ ┌──────────────┴───────────────────────────────┐ │ Automation Daemon (Python) │ │ - Timer loop (handles own scheduling) │ │ - Monitors for Matrix wake requests │ │ - Sends messages via AppleScript │ └──────────────┬───────────────────────────────┘ │ │ (sends message) ▼ ┌──────────────────────┐ │ Claude Desktop UI │ └────────┬─────────────┘ │ │ (calls MCP tools) ▼ ┌─────────────────────────────────────────────┐ │ MCP Servers (FastMCP) │ │ │ │ Wakeup Control: │ │ - next_wakeup(minutes) │ │ - pause_automation() │ │ - resume_automation() │ │ - get_status() │ │ │ │ Matrix Control: │ │ - get_matrix_messages() │ │ - send_matrix_message(room_id, msg) │ │ - mark_messages_processed(event_ids) │ │ - list_matrix_rooms() │ │ - get_matrix_status() │ └──────────────┬──────────────────────────────┘ │ │ (writes state) ▼ ~/.claude-automation-state.json ``` ## Requirements **Core System:** - macOS (tested on macOS 13+) - Python 3 (built-in) - Claude Desktop - FastMCP (`pip install fastmcp`) - AppleScript (built-in to macOS) - Accessibility permissions (critical - see below) **Matrix Integration (Optional):** - Matrix account (matrix.org or self-hosted) - matrix-nio (`pip install matrix-nio`) - Pillow (`pip install Pillow`) ## Installation ### Quick Setup ```bash cd ~/scripts/claude-desktop-automation ./setup.sh ``` The setup script will: 1. Check dependencies 2. Guide you through permissions 3. Install daemon (launchd) 4. Configure MCP server 5. Start everything ### Manual Setup If you prefer manual installation: #### 1. Install Dependencies ```bash # Install all dependencies pip3 install -r requirements.txt # Or install individually: pip3 install fastmcp ``` #### 2. Grant Accessibility Permissions (CRITICAL) **This is required for AppleScript automation to work!** 1. Open **System Settings** → **Privacy & Security** → **Accessibility** 2. Click the **+** button 3. Add **Terminal** (or your Python interpreter) 4. Ensure the checkbox is enabled **Testing accessibility:** ```bash python3 send_to_claude.py "Test" --no-refresh ``` If you see "Failed to send message", accessibility permissions are not set correctly. #### 3. Configure MCP Server Add to `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json { "mcpServers": { "wakeup-control": { "command": "python3", "args": ["/Users/YOUR_USERNAME/scripts/claude-desktop-automation/wakeup_mcp.py"] } } } ``` #### 4. Install Daemon ```bash # Copy and customize plist cp ~/scripts/claude-desktop-automation/com.claude.monitor.plist ~/Library/LaunchAgents/ sed -i '' "s|/Users/alex|$HOME|g" ~/Library/LaunchAgents/com.claude.monitor.plist # Load daemon launchctl load ~/Library/LaunchAgents/com.claude.monitor.plist ``` #### 5. Restart Claude Desktop Close and reopen Claude Desktop to load the MCP server. ## 🔄 Chat Refresh Feature **All wake messages now include CMD+R refresh before sending!** This prevents messages from being overwritten when using Claude Desktop on multiple devices: - **Timed wakes**: CMD+R ensures chat is synced before system check message - **Matrix wakes**: CMD+R ensures you see the latest conversation context The daemon waits **15 seconds** after sending CMD+R to ensure the refresh completes before sending the message. **Why this matters:** - Multi-device sync: If you're active on mobile/web, desktop chat stays current - Prevents message overwrites: Chat refreshes before automation sends - Better context: Claude sees all recent messages, not stale state **Configurable delay:** Edit `REFRESH_DELAY_SECONDS` in `send_to_claude.py` line 31 if you need more/less time. ## Usage ### The Flow 1. **Daemon wakes** (default: every 60 minutes) 2. **Sends message** to Claude Desktop: "System check at [time] - use next_wakeup() if you want to change interval" 3. **Claude responds** and optionally calls MCP tools 4. **Daemon sleeps** until next wake time (uses MCP-set time or default) ### MCP Tools #### `next_wakeup(minutes)` Set when you want to be woken next. **Overrides default for one cycle only.** ``` Claude, use next_wakeup(15) to wake me in 15 minutes Claude, use next_wakeup(120) to wake me in 2 hours Claude, use next_wakeup(1440) to skip until tomorrow ``` **Use cases:** - High activity period → `next_wakeup(15)` for frequent checks - Low activity → `next_wakeup(180)` for infrequent checks - One-time skip → `next_wakeup(1440)` for 24 hours #### `pause_automation()` Stop wake messages completely. ``` Claude, pause the automation - I don't need monitoring right now ``` #### `resume_automation()` Resume wake messages. ``` Claude, resume the automation ``` #### `get_status()` Check current state. ``` Claude, what's the automation status? ``` Returns: - Running/paused state - Last wake time - Next wake time - Current interval ### Example Conversation **Daemon:** (sends at 9:00 AM) "System check at 2025-01-15 09:00:00 - please analyze recent activity and use next_wakeup() if you want to change the monitoring interval" **You:** "Claude, everything looks normal. Check back in 2 hours instead of 1 hour." **Claude:** "I'll adjust the wake schedule. Using next_wakeup(120)..." [calls `next_wakeup(120)`] **Claude:** "✓ Next wake scheduled for 2025-01-15 11:00:00 (in 120 minutes)" **Daemon:** (wakes at 11:00 AM instead of 10:00 AM) ## Configuration ### Change Default Interval Edit `automation_daemon.py` line 32: ```python DEFAULT_INTERVAL_MINUTES = 60 # Change to your preferred default ``` ### Change Message Text Edit `automation_daemon.py` line 166-169: ```python def generate_message() -> str: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return f"Your custom message template with {timestamp}" ``` ## Monitoring ### Logs ```bash # Daemon logs (timer, messages sent) tail -f /tmp/claude-automation-daemon.log # MCP logs (tool calls from Claude) tail -f /tmp/wakeup-mcp.log # launchd stdout/stderr tail -f /tmp/claude-monitor.out tail -f /tmp/claude-monitor.err ``` ### Check Daemon Status ```bash # Is it running? launchctl list | grep com.claude.monitor # View details launchctl print gui/$(id -u)/com.claude.monitor # Check process ps aux | grep automation_daemon.py ``` ### State File The shared state is stored in `~/.claude-automation-state.json`: ```bash cat ~/.claude-automation-state.json ``` Example: ```json { "interval_minutes": 60, "paused": false, "last_wake": "2025-01-15T09:00:00", "next_wake_timestamp": "2025-01-15T11:00:00" } ``` ## Troubleshooting ### Messages not sending **Check Claude Desktop is running:** ```bash ps aux | grep Claude ``` **Check daemon is running:** ```bash launchctl list | grep com.claude.monitor tail -20 /tmp/claude-automation-daemon.log ``` **Check Accessibility permissions:** System Settings → Privacy & Security → Accessibility → Terminal should be enabled **Test AppleScript accessibility:** ```bash python3 /path/to/send_to_claude.py "Test message" --no-refresh ``` If you see "Failed to send CMD+R" or "Failed to send message", check accessibility permissions: 1. System Settings → Privacy & Security → Accessibility 2. Find Terminal (or Python) in the list 3. Enable it (toggle on) 4. Restart Terminal 5. Test again **Refresh delay:** The default 10-second delay after CMD+R should be sufficient for most cases. If your Claude Desktop takes longer to refresh, edit `send_to_claude.py` line 31: ```python REFRESH_DELAY_SECONDS = 15 # Increase if needed ``` ### MCP tools not available **Restart Claude Desktop** after adding MCP server to config **Check MCP server config:** ```bash cat ~/Library/Application\ Support/Claude/claude_desktop_config.json ``` **Check MCP logs:** ```bash tail -f /tmp/wakeup-mcp.log ``` ### `next_wakeup()` not working **Check grace period** - Daemon waits 30 seconds after sending message before rescheduling **Check state file** - Should update when you call the tool: ```bash cat ~/.claude-automation-state.json ``` **Check MCP logs** - Confirms tool was called: ```bash tail /tmp/wakeup-mcp.log ``` ### Daemon keeps restarting **Check for errors:** ```bash tail -50 /tmp/claude-automation-daemon.log ``` **Temporarily stop it:** ```bash launchctl unload ~/Library/LaunchAgents/com.claude.monitor.plist ``` ### Timed wake-ups not appearing (but Matrix wakes work) This typically happens when the **screen saver is active**. Matrix wakes work because they're reactive (screen is likely active), but timed wakes happen on schedule when the screen saver is often running. **Solution:** The AppleScript now uses `caffeinate -u` to wake the screen before sending keystrokes: ```applescript -- Wake the screen if screen saver is active do shell script "caffeinate -u -t 1" delay 0.5 ``` This simulates user activity and dismisses the screen saver, allowing keyboard automation to work. **To verify it's working:** ```bash tail -f /tmp/claude-automation-daemon.log # Look for: "Waking screen if needed..." in AppleScript logs ``` **Alternative:** If you want to prevent screen saver entirely on the Claude Desktop machine: ```bash # Disable screen saver defaults -currentHost write com.apple.screensaver idleTime 0 ``` ## Matrix Integration The automation supports **bidirectional Matrix messaging** - Claude can receive messages from Matrix rooms and respond to them automatically. ### Setup ```bash cd ~/scripts/claude-desktop-automation ./setup_matrix.sh ``` The setup script will: 1. Install matrix-nio dependency 2. Authenticate with your Matrix homeserver 3. Save credentials securely (chmod 600) 4. Configure room whitelist (optional) 5. Add Matrix MCP to Claude config 6. Start Matrix monitor ### How It Works 1. **Matrix Monitor** runs in background, listening for messages 2. New messages are queued to `~/.claude-automation-state.json` 3. Monitor sets `matrix_wake_requested` flag (respects 2-min rate limit) 4. **Automation Daemon** detects flag and wakes Claude 5. Claude uses **Matrix MCP tools** to read and respond 6. **Messages auto-marked as processed** when retrieved (won't trigger duplicate wakes) ### Matrix MCP Tools #### `get_matrix_messages(limit=10, include_processed=False)` Retrieve queued Matrix messages (text + images). **✨ Messages are automatically marked as processed when retrieved!** ``` Claude, check my Matrix messages Claude, get the last 20 Matrix messages ``` Returns messages with: - Room name and sender - Message text or inline image - Timestamp and event ID - Auto-marked as processed (won't trigger future wakes) #### `send_matrix_message(room_id, message)` Send a message to a Matrix room. ``` Claude, send "Hello!" to room !abc123:matrix.org ``` #### `mark_messages_processed(event_ids)` [OPTIONAL] Manually mark messages as processed (usually not needed). **Note:** Messages are automatically marked when you call `get_matrix_messages()`, so you typically don't need to call this tool manually. ``` Claude, mark those messages as processed ``` #### `list_matrix_rooms()` List all Matrix rooms the bot is in. ``` Claude, show my Matrix rooms ``` #### `get_matrix_status()` Check Matrix integration status. ``` Claude, check Matrix status ``` Shows: - Connection status - Queued messages - Last wake time - Rate limit status ### Configuration Files **Credentials:** `~/.matrix-credentials.json` (chmod 600) ```json { "homeserver": "https://matrix.org", "user_id": "@user:matrix.org", "access_token": "...", "device_id": "...", "room_whitelist": ["!room1:matrix.org", "!room2:matrix.org"] } ``` **Room Whitelist:** (Optional) - Empty array = Monitor all rooms - Specific IDs = Monitor only those rooms ### Rate Limiting Matrix wakes respect a **2-minute minimum** between wakes to prevent spam: - Messages arriving within 2 minutes are queued - Claude processes them in batch at next wake - Reduces interruptions while ensuring responsiveness ### Image Support Matrix images are automatically: 1. Downloaded from homeserver 2. Compressed to fit 1MB limit (accounting for base64 overhead) 3. Displayed inline in Claude Desktop 4. Queued just like text messages ### Logs ```bash # Matrix monitor logs tail -f /tmp/matrix-integration.log # Matrix MCP logs tail -f /tmp/matrix-mcp.log # Check state file cat ~/.claude-automation-state.json | jq '.matrix_messages' ``` ### Troubleshooting **Messages not appearing:** - Check Matrix monitor is running: `ps aux | grep matrix_integration.py` - Check logs: `tail -20 /tmp/matrix-integration.log` - Verify credentials: `cat ~/.matrix-credentials.json` **Claude not waking for Matrix:** - Check state file has messages: `cat ~/.claude-automation-state.json` - Check `matrix_wake_requested` flag - Verify 2-minute rate limit hasn't blocked wake - Check daemon logs: `tail -20 /tmp/claude-automation-daemon.log` **Can't send messages:** - Verify room ID is correct (use `list_matrix_rooms()`) - Check MCP logs: `tail /tmp/matrix-mcp.log` - Test credentials with `get_matrix_status()` ### Example Workflow 1. **Friend sends Matrix message**: "Hey, how's it going?" 2. **Matrix Monitor** detects message: - Queues to state file - Sets wake flag (if rate limit OK) 3. **Automation Daemon** wakes Claude: - "Matrix wake at 2025-01-15 14:32:00 - you have 1 new Matrix message(s)" 4. **You tell Claude**: "Check my Matrix messages and respond" 5. **Claude**: - Calls `get_matrix_messages()` - Sees friend's message - Calls `send_matrix_message(room_id, "I'm doing great! Thanks for asking!")` - Calls `mark_messages_processed([event_id])` 6. **Friend receives** Claude's response in Matrix room ## Advanced Usage ### Multiple Messages Per Day Use `next_wakeup()` strategically: ``` Morning: next_wakeup(180) # 3 hours (light monitoring) Workday: next_wakeup(30) # 30 min (active monitoring) Evening: next_wakeup(360) # 6 hours (minimal monitoring) ``` ### Conditional Wake Logic Modify `generate_message()` to include context: ```python def generate_message() -> str: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Check system state disk_usage = get_disk_usage() cpu_load = get_cpu_load() return f"System check at {timestamp} - Disk: {disk_usage}%, CPU: {cpu_load}%" ``` ### Event-Driven Wakeup Have other scripts write to the state file to trigger immediate wake: ```bash # External script echo '{"next_wake_timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%S)'"}' > ~/.claude-automation-state.json ``` ## Uninstalling ```bash # Stop daemon launchctl unload ~/Library/LaunchAgents/com.claude.monitor.plist rm ~/Library/LaunchAgents/com.claude.monitor.plist # Stop Matrix monitor (if running) pkill -f matrix_integration.py # Remove MCP servers from Claude config # (Edit ~/Library/Application Support/Claude/claude_desktop_config.json) # Remove both "wakeup-control" and "matrix-control" entries # Remove scripts (optional) rm -rf ~/scripts/claude-desktop-automation # Remove state and credentials rm ~/.claude-automation-state.json rm ~/.matrix-credentials.json rm -rf ~/.matrix-data # Remove logs (optional) rm /tmp/claude-automation-daemon.log rm /tmp/wakeup-mcp.log rm /tmp/matrix-integration.log rm /tmp/matrix-mcp.log ``` ## Project Structure ``` claude-desktop-automation/ ├── automation_daemon.py # Main daemon with timer loop + Matrix integration ├── wakeup_mcp.py # MCP server for wake control ├── matrix_integration.py # Matrix monitor (listens for messages) ├── matrix_mcp.py # MCP server for Matrix control ├── send_to_claude.scpt # AppleScript for UI automation (with screen wake) ├── com.claude.monitor.plist # launchd config (keeps daemon alive) ├── setup.sh # Interactive installation ├── setup_matrix.sh # Matrix integration setup └── README.md # This file ``` ## Why This Architecture? **Before (launchd scheduling):** - Fixed intervals - No flexibility - launchd does timing **After (Python timer + MCP control):** - Dynamic intervals - Claude has agency - Can adjust based on activity - Bidirectional communication - Much more elegant! ## Limitations ⚠️ **Still fragile** - UI automation breaks if Claude Desktop UI changes significantly ⚠️ **Active chat only** - Sends to whichever chat is currently open (CMD+R refreshes it first) ⚠️ **macOS only** - Uses AppleScript and launchd (Linux port would require xdotool/ydotool) ⚠️ **Requires Accessibility** - Special macOS permissions for UI automation ⚠️ **Screen saver handled** - Uses `caffeinate` to wake screen automatically ⚠️ **Fixed refresh delay** - Waits 15 seconds after CMD+R (configurable if needed) ## Future Plans 🔮 **Linux support** - Port to Linux using xdotool/ydotool for UI automation 🔮 **Windows support** - Port to Windows using pyautogui or AutoIt 🔮 **Wayland support** - Use ydotool for newer Linux distros 🔮 **Smarter refresh** - Detect if refresh is needed before sending CMD+R ## License Public domain / Use at your own risk --- **Built with Python + FastMCP + matrix-nio + AppleScript for intelligent automation** 🤖