Files
claude-automation/README.md
2026-02-08 17:47:49 -06:00

20 KiB
Executable File

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

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

# 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 SettingsPrivacy & SecurityAccessibility
  2. Click the + button
  3. Add Terminal (or your Python interpreter)
  4. Ensure the checkbox is enabled

Testing accessibility:

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:

{
  "mcpServers": {
    "wakeup-control": {
      "command": "python3",
      "args": ["/Users/YOUR_USERNAME/scripts/claude-desktop-automation/wakeup_mcp.py"]
    }
  }
}

4. Install Daemon

# 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:

DEFAULT_INTERVAL_MINUTES = 60  # Change to your preferred default

Change Message Text

Edit automation_daemon.py line 166-169:

def generate_message() -> str:
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"Your custom message template with {timestamp}"

Monitoring

Logs

# 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

# 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:

cat ~/.claude-automation-state.json

Example:

{
  "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:

ps aux | grep Claude

Check daemon is running:

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:

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:

REFRESH_DELAY_SECONDS = 15  # Increase if needed

MCP tools not available

Restart Claude Desktop after adding MCP server to config

Check MCP server config:

cat ~/Library/Application\ Support/Claude/claude_desktop_config.json

Check MCP logs:

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:

cat ~/.claude-automation-state.json

Check MCP logs - Confirms tool was called:

tail /tmp/wakeup-mcp.log

Daemon keeps restarting

Check for errors:

tail -50 /tmp/claude-automation-daemon.log

Temporarily stop it:

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:

-- 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:

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:

# 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

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)

{
  "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

# 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:

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:

# External script
echo '{"next_wake_timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%S)'"}' > ~/.claude-automation-state.json

Uninstalling

# 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 🤖