Skip to content

Instantly share code, notes, and snippets.

@joelhooks
Created December 1, 2025 17:17
Show Gist options
  • Select an option

  • Save joelhooks/fb7b5c23bcfb094b87be06bdf32b173e to your computer and use it in GitHub Desktop.

Select an option

Save joelhooks/fb7b5c23bcfb094b87be06bdf32b173e to your computer and use it in GitHub Desktop.
Installing MCP Agent Mail on macOS with Python 3.14 and launchd
# Installing MCP Agent Mail on macOS (Python 3.14 + launchd)
A chronicle of the fuckery involved in getting [mcp_agent_mail](https://github.com/Dicklesworthstone/mcp_agent_mail) running as a launchd service on macOS with Python 3.14.
## The Goal
Run Agent Mail as a persistent service for multi-agent beads coordination. Prevents collision when multiple AI agents (Claude, Cursor, OpenCode, etc.) work the same repo.
## The Fuckery
### 1. Python 3.14 + grpcio = Pain
The package requires `Python >= 3.14` (bleeding edge). But `litellm` pins `grpcio<1.68.0`, and grpcio 1.67.x has no wheels for Python 3.14. It tries to build from source and fails spectacularly.
**Fix:** Install grpcio 1.76.0 first (has py3.14 wheels), then install the package with `--no-deps`:
```bash
pip install grpcio==1.76.0 --only-binary=:all:
pip install -e . --no-deps
pip install <all the other deps manually>
pip install 'authlib>=1.5.2,<1.6' 'litellm<2,>=1.40.0' --no-deps
```
### 2. Missing greenlet
Server crashes on startup with:
```
ValueError: the greenlet library is required to use this function
```
SQLAlchemy async needs greenlet but it wasn't in the dep list.
**Fix:**
```bash
pip install greenlet
```
### 3. Missing litellm deps
```
ModuleNotFoundError: No module named 'aiohttp'
```
litellm needs a bunch of deps that weren't pulled in:
```bash
pip install aiohttp openai tokenizers tqdm sniffio fastuuid importlib-metadata
```
### 4. launchd + Python venv = More Pain
Created a plist pointing directly to the venv python:
```xml
<string>/path/to/.venv/bin/python</string>
```
Process starts but uses system python, not venv. The venv's `python` is a symlink to system python - the venv activation sets `VIRTUAL_ENV` and modifies `sys.path`, but launchd doesn't source anything.
**Fix:** Create a wrapper script:
```bash
#!/bin/bash
export VIRTUAL_ENV="/path/to/.venv"
export PATH="$VIRTUAL_ENV/bin:$PATH"
export PYTHONHOME=""
cd /path/to/mcp_agent_mail
exec "$VIRTUAL_ENV/bin/python3.14" -m mcp_agent_mail.cli serve-http
```
### 5. launchd Caching
Even after updating the plist, launchd kept running the old config.
**Fix:** Full kill cycle:
```bash
launchctl unload ~/Library/LaunchAgents/com.beads.agentmail.plist
pkill -9 -f mcp_agent_mail
sleep 1
launchctl load ~/Library/LaunchAgents/com.beads.agentmail.plist
```
### 6. Server Startup Time
curl kept returning empty. Turned out the server just needs ~5-6 seconds to fully start (uvicorn + SQLAlchemy schema init + MCP setup).
**Fix:** Just wait longer before health checks.
## Final Working Setup
### Directory Structure
```
~/Code/Dicklesworthstone/mcp_agent_mail/
├── .venv/
├── run-server.sh
└── ...
```
### run-server.sh
```bash
#!/bin/bash
export VIRTUAL_ENV="/Users/you/Code/Dicklesworthstone/mcp_agent_mail/.venv"
export PATH="$VIRTUAL_ENV/bin:$PATH"
export PYTHONHOME=""
cd /Users/you/Code/Dicklesworthstone/mcp_agent_mail
exec "$VIRTUAL_ENV/bin/python3.14" -m mcp_agent_mail.cli serve-http
```
### ~/Library/LaunchAgents/com.beads.agentmail.plist
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.beads.agentmail</string>
<key>ProgramArguments</key>
<array>
<string>/Users/you/Code/Dicklesworthstone/mcp_agent_mail/run-server.sh</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/you/Code/Dicklesworthstone/mcp_agent_mail</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/agentmail.log</string>
<key>StandardErrorPath</key>
<string>/tmp/agentmail.err</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
</dict>
</dict>
</plist>
```
### Shell Config (~/.zshrc)
```bash
export BEADS_AGENT_MAIL_URL="http://127.0.0.1:8765"
# Set per-agent:
# export BEADS_AGENT_NAME=opencode
# export BEADS_AGENT_NAME=cursor
# export BEADS_PROJECT_ID=my-project # or auto-derives from pwd
```
### Management Commands
```bash
# Load service
launchctl load ~/Library/LaunchAgents/com.beads.agentmail.plist
# Check status
launchctl list | grep beads
# View logs
tail -f /tmp/agentmail.err
# Restart
launchctl unload ~/Library/LaunchAgents/com.beads.agentmail.plist
launchctl load ~/Library/LaunchAgents/com.beads.agentmail.plist
# Web UI
open http://127.0.0.1:8765/mail
```
## Verification
```bash
# Check port
lsof -i :8765
# Health check
curl http://127.0.0.1:8765/mail | head -5
# Should see HTML response with "Unified Inbox — Agent Mail"
```
## Why Bother?
When multiple AI agents work the same beads repo:
- Without Agent Mail: 2-5s latency for status sync via git, collision risk
- With Agent Mail: <100ms via HTTP, collision prevention via reservations
Agent A claims `bd-42` → Agent B gets 409 Conflict. No duplicate work.
Git remains source of truth. Agent Mail is ephemeral coordination. If it dies, beads gracefully falls back to git-only mode.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment