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:
-
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
-
Wakeup Control MCP - MCP server that lets Claude:
- Adjust when it wants to be woken next
- Pause/resume automation
- Check status
-
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:
- Check dependencies
- Guide you through permissions
- Install daemon (launchd)
- Configure MCP server
- 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!
- Open System Settings → Privacy & Security → Accessibility
- Click the + button
- Add Terminal (or your Python interpreter)
- 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
- Daemon wakes (default: every 60 minutes)
- Sends message to Claude Desktop: "System check at [time] - use next_wakeup() if you want to change interval"
- Claude responds and optionally calls MCP tools
- 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:
- System Settings → Privacy & Security → Accessibility
- Find Terminal (or Python) in the list
- Enable it (toggle on)
- Restart Terminal
- 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:
- Install matrix-nio dependency
- Authenticate with your Matrix homeserver
- Save credentials securely (chmod 600)
- Configure room whitelist (optional)
- Add Matrix MCP to Claude config
- Start Matrix monitor
How It Works
- Matrix Monitor runs in background, listening for messages
- New messages are queued to
~/.claude-automation-state.json - Monitor sets
matrix_wake_requestedflag (respects 2-min rate limit) - Automation Daemon detects flag and wakes Claude
- Claude uses Matrix MCP tools to read and respond
- 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:
- Downloaded from homeserver
- Compressed to fit 1MB limit (accounting for base64 overhead)
- Displayed inline in Claude Desktop
- 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_requestedflag - 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
-
Friend sends Matrix message: "Hey, how's it going?"
-
Matrix Monitor detects message:
- Queues to state file
- Sets wake flag (if rate limit OK)
-
Automation Daemon wakes Claude:
- "Matrix wake at 2025-01-15 14:32:00 - you have 1 new Matrix message(s)"
-
You tell Claude: "Check my Matrix messages and respond"
-
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])
- Calls
-
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 🤖