Created
December 1, 2025 17:17
-
-
Save joelhooks/fb7b5c23bcfb094b87be06bdf32b173e to your computer and use it in GitHub Desktop.
Installing MCP Agent Mail on macOS with Python 3.14 and launchd
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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