loading…
Search for a command to run...
loading…
A coordination layer for coding agents that provides identities, message threading, and searchable history. It features file reservation leases to prevent agent
A coordination layer for coding agents that provides identities, message threading, and searchable history. It features file reservation leases to prevent agents from overwriting each other's work in multi-agent environments.

"It's like gmail for your coding agents!"
A mail-like coordination layer for coding agents, exposed as an HTTP-only FastMCP server. It gives agents memorable identities, an inbox/outbox, searchable message history, and voluntary file reservation "leases" to avoid stepping on each other.
Think of it as asynchronous email + directory + change-intent signaling for your agents, backed by Git (for human-auditable artifacts) and SQLite (for indexing and queries).
Status: Under active development. The design is captured in detail in project_idea_and_guide.md (start with the original prompt at the top of that file).
Modern projects often run multiple coding agents at once (backend, frontend, scripts, infra). Without a shared coordination fabric, agents:
This project provides a lightweight, interoperable layer so agents can:
It's designed for: FastMCP clients and CLI tools (Claude Code, Codex, Gemini CLI, etc.) coordinating across one or more codebases.
If a blank repo feels daunting, follow the field-tested workflow we documented in project_idea_and_guide.md (“Appendix: From Blank Repo to Coordinated Swarm”):
AGENTS.md, add any tech-specific best-practice guides, and let Codex scaffold the repo plus Beads tasks straight from the plan.AGENTS.md, the plan document, and the Beads backlog before touching code.Watch the full 23-minute walkthrough (https://youtu.be/68VVcqMEDrs?si=pCm6AiJAndtZ6u7q) to see the loop in action.
One disciplined hour of GPT-5 Codex—when it isn’t waiting on human prompts—often produces 10–20 “human hours” of work because the agents reason and type at machine speed. Agent Mail multiplies that advantage in two layers:
Result: you invest 1–2 hours of human supervision, but dozens of agent-hours execute in parallel with clear audit trails and conflict-avoidance baked in.
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/mcp_agent_mail/main/scripts/install.sh?$(date +%s)" | bash -s -- --yes
What this does:
scripts/ (including run_server_with_token.sh)am shell alias to your .zshrc or .bashrc for quick server startup (just type am in a new terminal!)bd CLI into your PATH via its official curl installer so the task planner is ready out of the box (pass --skip-beads to opt out or install manually)bv TUI for interactive task browsing and AI-friendly robot commands (pass --skip-bv to opt out)Prefer a specific location or options? Add flags like --dir <path>, --project-dir <path>, --no-start, --start-only, --port <number>, or --token <hex>.
Already have Beads or Beads Viewer installed? Append --skip-beads and/or --skip-bv to bypass automatic installation.
After installation, you can start the MCP Agent Mail server from anywhere by simply typing:
am
That's it! The am alias (added to your .zshrc or .bashrc during installation) automatically:
uv run to handle the virtual environment).env and starts the HTTP serverNote: If you just ran the installer, open a new terminal or run source ~/.zshrc (or source ~/.bashrc) to load the alias.
Port conflicts? Use --port to specify a different port (default: 8765):
Install with custom port:
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/mcp_agent_mail/main/scripts/install.sh?$(date +%s)" | bash -s -- --port 9000 --yes
Or use the CLI command after installation: uv run python -m mcp_agent_mail.cli config set-port 9000
### If you want to do it yourself
Clone the repo, set up and install with uv in a python 3.14 venv (install uv if you don't have it already), and then run `scripts/automatically_detect_all_installed_coding_agents_and_install_mcp_agent_mail_in_all.sh`. This will automatically set things up for your various installed coding agent tools and start the MCP server on port 8765. If you want to run the MCP server again in the future, simply run `scripts/run_server_with_token.sh`:
Install uv (if you don't have it already):
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
# Clone the repo
git clone https://github.com/Dicklesworthstone/mcp_agent_mail
cd mcp_agent_mail
# Create a Python 3.14 virtual environment and install dependencies
# Note: If you have an older uv version, run `uv self update` first
uv python install 3.14
uv venv -p 3.14
source .venv/bin/activate
uv sync
# Detect installed coding agents, integrate, and start the MCP server on port 8765
scripts/automatically_detect_all_installed_coding_agents_and_install_mcp_agent_mail_in_all.sh
# Later, to run the MCP server again with the same token
scripts/run_server_with_token.sh
# Now, simply launch Codex-CLI or Claude Code or other agent tools in other consoles; they should have the mail tool available. See below for a ready-made chunk of text you can add to the end of your existing AGENTS.md or CLAUDE.md files to help your agents better utilize the new tools.
# Change port after installation
uv run python -m mcp_agent_mail.cli config set-port 9000
## MCP Agent Mail: coordination for multi-agent workflows
What it is
- A mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources.
- Provides identities, inbox/outbox, searchable threads, and advisory file reservations, with human-auditable artifacts in Git.
Why it's useful
- Prevents agents from stepping on each other with explicit file reservations (leases) for files/globs.
- Keeps communication out of your token budget by storing messages in a per-project archive.
- Offers quick reads (`resource://inbox/...`, `resource://thread/...`) and macros that bundle common flows.
How to use effectively
1) Same repository
- Register an identity: call `ensure_project`, then `register_agent` using this repo's absolute path as `project_key`.
- Reserve files before you edit: `file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true)` to signal intent and avoid conflict.
- Communicate with threads: use `send_message(..., thread_id="FEAT-123")`; check inbox with `fetch_inbox` and acknowledge with `acknowledge_message`.
- Read fast: `resource://inbox/{Agent}?project=<abs-path>&limit=20` or `resource://thread/{id}?project=<abs-path>&include_bodies=true`.
- Tip: set `AGENT_NAME` in your environment so the pre-commit guard can block commits that conflict with others' active exclusive file reservations.
2) Across different repos in one project (e.g., Next.js frontend + FastAPI backend)
- Option A (single project bus): register both sides under the same `project_key` (shared key/path). Keep reservation patterns specific (e.g., `frontend/**` vs `backend/**`).
- Option B (separate projects): each repo has its own `project_key`; use `macro_contact_handshake` or `request_contact`/`respond_contact` to link agents, then message directly. Keep a shared `thread_id` (e.g., ticket key) across repos for clean summaries/audits.
Macros vs granular tools
- Prefer macros when you want speed or are on a smaller model: `macro_start_session`, `macro_prepare_thread`, `macro_file_reservation_cycle`, `macro_contact_handshake`.
- Use granular tools when you need control: `register_agent`, `file_reservation_paths`, `send_message`, `fetch_inbox`, `acknowledge_message`.
Common pitfalls
- "from_agent not registered": always `register_agent` in the correct `project_key` first.
- "FILE_RESERVATION_CONFLICT": adjust patterns, wait for expiry, or use a non-exclusive reservation when appropriate.
- Auth errors: if JWT+JWKS is enabled, include a bearer token with a `kid` that matches server JWKS; static bearer is used only when JWT is disabled.
Beads is a lightweight task planner (bd CLI) that complements Agent Mail by keeping status and dependencies in one place while Mail handles messaging, file reservations, and audit trails. Project: steveyegge/beads
Highlights:
bd-123) keep Beads issues, Mail threads, and commits aligned.bd CLI via prebuilt release or Go build; see the repository for platform specifics.Copy/paste blurb for agent-facing docs (leave as-is for reuse):
## Integrating with Beads (dependency-aware task planning)
Beads provides a lightweight, dependency-aware issue database and a CLI (`bd`) for selecting "ready work," setting priorities, and tracking status. It complements MCP Agent Mail's messaging, audit trail, and file-reservation signals. Project: [steveyegge/beads](https://github.com/steveyegge/beads)
Recommended conventions
- **Single source of truth**: Use **Beads** for task status/priority/dependencies; use **Agent Mail** for conversation, decisions, and attachments (audit).
- **Shared identifiers**: Use the Beads issue id (e.g., `bd-123`) as the Mail `thread_id` and prefix message subjects with `[bd-123]`.
- **Reservations**: When starting a `bd-###` task, call `file_reservation_paths(...)` for the affected paths; include the issue id in the `reason` and release on completion.
Typical flow (agents)
1) **Pick ready work** (Beads)
- `bd ready --json` → choose one item (highest priority, no blockers)
2) **Reserve edit surface** (Mail)
- `file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true, reason="bd-123")`
3) **Announce start** (Mail)
- `send_message(..., thread_id="bd-123", subject="[bd-123] Start: <short title>", ack_required=true)`
4) **Work and update**
- Reply in-thread with progress and attach artifacts/images; keep the discussion in one thread per issue id
5) **Complete and release**
- `bd close bd-123 --reason "Completed"` (Beads is status authority)
- `release_file_reservations(project_key, agent_name, paths=["src/**"])`
- Final Mail reply: `[bd-123] Completed` with summary and links
Mapping cheat-sheet
- **Mail `thread_id`** ↔ `bd-###`
- **Mail subject**: `[bd-###] …`
- **File reservation `reason`**: `bd-###`
- **Commit messages (optional)**: include `bd-###` for traceability
Event mirroring (optional automation)
- On `bd update --status blocked`, send a high-importance Mail message in thread `bd-###` describing the blocker.
- On Mail "ACK overdue" for a critical decision, add a Beads label (e.g., `needs-ack`) or bump priority to surface it in `bd ready`.
Pitfalls to avoid
- Don't create or manage tasks in Mail; treat Beads as the single task queue.
- Always include `bd-###` in message `thread_id` to avoid ID drift across tools.
Prefer automation? Run uv run python -m mcp_agent_mail.cli docs insert-blurbs to scan your code directories for AGENTS.md/CLAUDE.md files and append the latest Agent Mail + Beads snippets with per-project confirmation. The installer also offers to launch this helper right after setup so you can take care of onboarding docs immediately.
The Beads Viewer (bv) is a fast terminal UI for Beads projects that also provides robot flags designed specifically for AI agent integration. Project: Dicklesworthstone/beads_viewer
While bd (Beads CLI) handles task CRUD operations, bv provides precomputed graph analytics that help agents make intelligent prioritization decisions:
Instead of agents parsing .beads/beads.jsonl directly or attempting to compute graph metrics (risking hallucinated results), they can call bv's deterministic robot flags and get JSON output they can trust.
| Flag | Output | Agent Use Case |
|---|---|---|
bv --robot-help |
All AI-facing commands | Discovery / capability check |
bv --robot-insights |
PageRank, betweenness, HITS, critical path, cycles | Quick triage: "What's most impactful?" |
bv --robot-plan |
Parallel tracks, items per track, unblocks lists | Execution planning: "What can run in parallel?" |
bv --robot-priority |
Priority recommendations with reasoning + confidence | Task selection: "What should I work on next?" |
bv --robot-recipes |
Available filter presets (actionable, blocked, etc.) | Workflow setup: "Show me ready work" |
bv --robot-diff --diff-since <ref> |
Changes since commit/date, new/closed items, cycles | Progress tracking: "What changed?" |
# 1. Get priority recommendations with reasoning
bv --robot-priority
# Returns JSON with ranked tasks, impact scores, and confidence levels
# 2. Check what completing a task would unblock
bv --robot-plan
# Returns parallel tracks showing dependency chains
# 3. After completing work, check what changed
bv --robot-diff --diff-since "1 hour ago"
# Returns new items, closed items, cycle changes
| Tool | Best For |
|---|---|
bd |
Creating, updating, closing tasks; bd ready for simple "what's next" |
bv |
Graph analysis, impact assessment, parallel planning, change tracking |
Rule of thumb: Use bd for task operations, use bv for task intelligence.
Combine bv insights with Agent Mail coordination:
bv --robot-priority → identifies bd-42 as highest-impactfile_reservation_paths(..., reason="bd-42")send_message(..., thread_id="bd-42", subject="[bd-42] Starting high-impact refactor")bv --robot-diff to report downstream unblocksThis creates a feedback loop where graph intelligence drives coordination.
resource://inbox/{agent})Do I still need the tmux broadcast script to “feed” every Codex pane?
No. The historical zsh loop from the tweet thread is still handy if you are running the OSS stack by itself, but the AgentMail Companion system now automates that cadence with Message Stacks. Once the companion host services are installed, you queue presets (builder loop, reviewer sweep, test focus, etc.) from the iOS app or CLI and the automation fans those instructions out to every enrolled agent—without touching tmux.
graph LR
A[Agents]
S[Server]
G[Git repo]
Q[SQLite FTS5]
A -->|HTTP tools/resources| S
S -->|writes/reads| G
S -->|indexes/queries| Q
subgraph GitTree["Git tree"]
GI1[agents/profile.json]
GI2[agents/mailboxes/...]
GI3[messages/YYYY/\nMM/\nid.md]
GI4[file_reservations/\nsha1.json]
GA[attachments/xx/\nsha1.webp]
end
G --- GI1
G --- GI2
G --- GI3
G --- GI4
G --- GA
The server ships a lightweight, server-rendered Web UI for humans. It lets you browse projects, agents, inboxes, single messages, attachments, file reservations, and perform full-text search with FTS5 when available (with an automatic LIKE fallback).
mcp_agent_mail.http under the /mail path.Recommended (simple):
scripts/run_server_with_token.sh
# then open http://127.0.0.1:8765/mail
Advanced (manual commands):
uv run python -m mcp_agent_mail.http --host 127.0.0.1 --port 8765
# or:
uv run uvicorn mcp_agent_mail.http:build_http_app --factory --host 127.0.0.1 --port 8765
Auth notes:
HTTP_ALLOW_LOCALHOST_UNAUTHENTICATED=true (and optionally HTTP_BEARER_TOKEN), so localhost can load the UI without headers./health/*./mail (Unified inbox + Projects + Related Projects Discovery)
LLM_ENABLED=true, an LLM pass across key docs (README.md, AGENTS.md, etc.).AgentLink approvals via request_contact/respond_contact./mail/projects (Projects index)
/mail/{project} (Project overview + search + agents)
subject:foo, body:"multi word", quoted phrases, and bare terms./mail/{project}/inbox/{agent} (Inbox for one agent)
?page=N&limit=M)./mail/{project}/message/{id} (Message detail)
/mail/{project}/search?q=... (Dedicated search page)
/mail/{project}/file_reservations (File Reservations list)
/mail/{project}/attachments (Messages with attachments)
/mail/unified-inbox (Cross-project activity)
Sometimes a human operator needs to guide or redirect agents directly, whether to handle an urgent issue, provide clarification, or adjust priorities. The Human Overseer feature provides a web-based message composer that lets humans send high-priority messages to any combination of agents in a project.
Access: Click the prominent "Send Message" button (with the Overseer badge) in the header of any project view (/mail/{project}), or navigate directly to /mail/{project}/overseer/compose.
Automatic Preamble: Every message includes a formatted preamble that clearly identifies it as coming from a human operator and instructs agents to:
High Priority: All overseer messages are automatically marked as high importance, ensuring they stand out in agent inboxes.
Policy Bypass: Overseer messages bypass normal contact policies, so humans can always reach any agent regardless of their contact settings.
Special Sender Identity: Messages come from a special agent named "HumanOverseer" (automatically created per project) with:
WebUIHumanopenEvery overseer message begins with this preamble (automatically prepended):
---
🚨 MESSAGE FROM HUMAN OVERSEER 🚨
This message is from a human operator overseeing this project. Please prioritize
the instructions below over your current tasks.
You should:
1. Temporarily pause your current work
2. Complete the request described below
3. Resume your original plans afterward (unless modified by these instructions)
The human's guidance supersedes all other priorities.
---
[Your message body follows here]
The composer interface provides:
Urgent Issue:
Subject: Urgent: Stop migration and revert changes
The database migration in PR #453 is causing data corruption in staging.
Please:
1. Immediately stop any migration-related work
2. Revert commits from the last 2 hours
3. Wait for my review before resuming
I'm investigating the root cause now.
Priority Adjustment:
Subject: New Priority: Security Vulnerability
A critical security vulnerability was just disclosed in our auth library.
Drop your current tasks and:
1. Update `auth-lib` to version 2.4.1 immediately
2. Review all usages in src/auth/
3. Run the full security test suite
4. Report status in thread #892
This takes precedence over the refactoring work.
Clarification:
Subject: Clarification on API design approach
I see you're debating REST vs. GraphQL in thread #234.
Go with REST for now because:
- Our frontend team has more REST experience
- GraphQL adds complexity we don't need yet
- We can always add GraphQL later if needed
Resume the API implementation with REST.
When agents check their inbox (via fetch_inbox or resource://inbox/{name}), overseer messages appear like any other message but with:
HumanOverseerhigh (displayed prominently)Agents can reply to overseer messages just like any other message, continuing the conversation thread.
messages/YYYY/MM/{id}.md with commit historyThe Human Overseer feature is designed to be:
This creates a clear hierarchy (human → agents) while maintaining the collaborative, respectful tone of the agent communication system.
The Projects index (/mail) features an AI-powered discovery system that intelligently suggests which projects should be linked together, such as frontend + backend or related microservices.
1. Smart Analysis The system uses multiple signals to identify relationships:
LLM_ENABLED=true): Reads README.md, AGENTS.md, and other docs to understand each project's purpose and detect natural relationships2. Beautiful Suggestions Related projects appear as polished cards on your dashboard with:
3. Quick Navigation Once confirmed, both projects display interactive badges for instant navigation between related codebases.
TL;DR: We keep you in control. Discovery helps you find relationships; explicit approvals control who can actually communicate.
Agent Mail uses agent-centric messaging: every message follows explicit permission chains:
Send Message → Find Recipient → Check AgentLink Approval → Deliver
This design ensures:
Why not auto-link with AI? If we let an LLM automatically authorize messaging between projects, we'd be:
Instead, we give you discovery + control:
request_contact / respond_contact for actual messaging permissions1. System suggests: "These projects look related" (AI analysis)
↓
2. You confirm: "Yes, link them" (updates UI badges)
↓
3. Agents request: request_contact(from_agent, to_agent, to_project)
↓
4. You approve: respond_contact(accept=true)
↓
5. Messages flow: Agents can now communicate across projects
Think of it like LinkedIn: The system suggests connections, but only you decide who gets to send messages.
The UI shares the same parsing as the API's _parse_fts_query:
subject:login, body:"api key""build plan"login AND security (FTS)The UI reads from the same SQLite + Git artifacts as the MCP tools. To populate content:
ensure_project(human_key)register_agent(project_key, program, model, name?)send_message(...) (attachments and inline images are supported; images may be converted to WebP).Once messages exist, visit /mail, click your project, then open an agent inbox or search.
src/mcp_agent_mail/templates/ and are rendered by Jinja2.markdown2 on the server where possible; HTML is sanitized with Bleach (with CSS sanitizer when available).base.html for a modern look without a frontend build step.HTTP_BEARER_TOKEN or set HTTP_ALLOW_LOCALHOST_UNAUTHENTICATED=true.ensure_project.The share command group exports a project’s mailbox into a portable, read‑only bundle that anyone can review in a browser. It’s designed for auditors, stakeholders, or teammates who need to browse threads, search history, or prove delivery timelines without spinning up the full MCP Agent Mail stack.
Compliance and audit trails: Deliver immutable snapshots of project communication to auditors or compliance officers. The static bundle includes cryptographic signatures for tamper-evident distribution.
Stakeholder review: Share conversation history with product managers, executives, or external consultants who don't need write access. They can browse messages, search threads, and view attachments in their browser without authentication.
Offline access: Create portable archives for air-gapped environments, disaster recovery backups, or situations where internet connectivity is unreliable.
Long-term archival: Preserve project communication in a format that will remain readable decades from now. Static HTML requires no database server, no runtime dependencies, and survives software obsolescence better than proprietary formats.
Secure distribution: Encrypt bundles with age for confidential projects. Only recipients with the private key can decrypt and view the contents.
Each bundle contains:
chunks.sha256 file lists digests for each chunk so clients can trust streamed blobs without recomputing hashes.archive commands)Use the archive subcommands when you need a restorable snapshot (not just a read-only share bundle). Each ZIP under ./archived_mailbox_states/ includes:
share, but using the archive scrub preset so ack/read state, recipients, attachments, and message bodies remain untouched.STORAGE_ROOT), preserving markdown artifacts, attachments, and hook scripts.# Save current state (defaults to the lossless preset)
uv run python -m mcp_agent_mail.cli archive save --label nightly
# List available restore points (JSON is handy for scripts)
uv run python -m mcp_agent_mail.cli archive list --json
# Restore after a disaster (backs up any existing DB/storage before overwriting)
uv run python -m mcp_agent_mail.cli archive restore archived_mailbox_states/<file>.zip --force
During restore the CLI:
storage.sqlite3, WAL/SHM siblings, and STORAGE_ROOT into timestamped .backup-<ts> folders so nothing is lost.Every archive writes a metadata.json manifest describing the projects captured, scrub preset, and a friendly reminder of the exact archive restore … command to run later.
clear-and-reset-everything now offers to create one of these archives before deleting anything. By default it prompts interactively; pass --archive/--no-archive to force a choice, and pair with --force --no-archive for non-interactive automation. When an archive is created successfully, the CLI prints both the path and the restore command so you can undo the reset later.
am doctorThe doctor command group provides comprehensive diagnostics and repair capabilities for maintaining mailbox health. It emphasizes data safety—creating backups before any destructive operation and using semi-automatic repair to prevent accidental data loss.
am doctor?Over time, mailbox state can drift:
.archive.lock or .commit.lock files that block operationsThe doctor commands detect these issues and offer safe, automatic repair.
Run comprehensive diagnostics on your mailbox:
# Check all projects
uv run python -m mcp_agent_mail.cli doctor check
# Check with verbose output
uv run python -m mcp_agent_mail.cli doctor check --verbose
# JSON output for automation
uv run python -m mcp_agent_mail.cli doctor check --json
What it checks:
| Check | Status | Description |
|---|---|---|
| Locks | OK/WARN | Detects stale archive and commit locks from crashed processes |
| Database | OK/ERROR | Runs PRAGMA integrity_check for SQLite corruption |
| Orphaned Records | OK/WARN | Finds message recipients without corresponding agents |
| FTS Index | OK/WARN | Compares message count vs FTS index entries |
| File Reservations | OK/INFO | Counts expired reservations pending cleanup |
| WAL Files | OK/INFO | Reports presence of SQLite WAL/SHM files |
Example output:
MCP Agent Mail Doctor - Diagnostic Report
==================================================
Check Status Details
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Locks OK No stale locks found
Database OK Database integrity check passed
Orphaned OK No orphaned records found
FTS Index OK FTS index synchronized (1,234 messages)
File Res. INFO 4 expired reservation(s) pending cleanup
WAL Files OK No orphan WAL/SHM files
All checks passed!
The repair command uses a semi-automatic approach:
# Preview what would be repaired (dry-run)
uv run python -m mcp_agent_mail.cli doctor repair --dry-run
# Run repairs with prompts for data changes
uv run python -m mcp_agent_mail.cli doctor repair
# Auto-confirm all repairs (for automation)
uv run python -m mcp_agent_mail.cli doctor repair --yes
# Specify custom backup location
uv run python -m mcp_agent_mail.cli doctor repair --backup-dir /path/to/backups
Repair workflow:
.archive.lock, .commit.lock files)released_ts in database)Doctor creates timestamped backups before repairs. You can also manage backups directly:
# List all available backups
uv run python -m mcp_agent_mail.cli doctor backups
# JSON output for scripting
uv run python -m mcp_agent_mail.cli doctor backups --json
Backup contents:
Each backup includes:
database.sqlite3 — Complete SQLite database copydatabase.sqlite3-wal, database.sqlite3-shm — WAL files if presentarchive.bundle or {project}.bundle — Git bundle of the archive repositorymanifest.json — Metadata: when created, why, what's included, restore instructionsDirectory structure:
{storage_root}/backups/
2026-01-06T12-30-45_doctor-repair/
manifest.json
database.sqlite3
archive.bundle
If something goes wrong, restore from any backup:
# Preview what would be restored
uv run python -m mcp_agent_mail.cli doctor restore /path/to/backup --dry-run
# Restore (prompts for confirmation)
uv run python -m mcp_agent_mail.cli doctor restore /path/to/backup
# Skip confirmation prompt
uv run python -m mcp_agent_mail.cli doctor restore /path/to/backup --yes
Restore process:
Safety features:
*.sqlite3.pre-restore before overwrite*.pre-restore directory before overwriteam doctor check is fast and non-destructive--dry-run first to see what would changeam doctor check --json in your CI/monitoring for early warningThe easiest way to export and deploy is the interactive wizard, which supports both GitHub Pages and Cloudflare Pages:
# Via CLI (recommended)
uv run python -m mcp_agent_mail.cli share wizard
# Or run the script directly
./scripts/share_to_github_pages.py
The wizard provides a fully automated end-to-end deployment experience:
gh for GitHub, wrangler for Cloudflare)all, single number (1), lists (1,3,5), or ranges (1-3, 2-5,8)standard (scrub secrets like API keys/tokens, keep agent names) or strict (redact all message bodies)If you interrupt the wizard (close terminal, Ctrl+C during preview, etc.), it saves your progress to ~/.mcp-agent-mail/wizard-session/. When you run the wizard again:
Incomplete session detected
Projects: 3 selected
Stage: preview
Workspace: ~/.mcp-agent-mail/wizard-session/bundle
Resume where you left off? (Y/n):
What gets saved:
Resume scenarios:
After successful deployment, the session state is automatically cleared. Sessions also clear if they become invalid (workspace deleted, projects removed, etc.).
The wizard saves your configuration to ~/.mcp-agent-mail/wizard-config.json after each successful deployment. On subsequent runs, it will show:
Previous Configuration Found
Projects: 3 selected
Redaction: standard
Target: github-new
Use these settings again? (Y/n):
This allows rapid re-deployment with the same settings. The saved configuration includes:
Configuration is project-agnostic: if you add or remove projects, the wizard validates saved indices and prompts for re-selection if needed.
Difference between session and config:
wizard-session/): Temporary, for resuming interrupted runs, includes exported bundlewizard-config.json): Persistent, for "use last settings" across fresh runs, no bundleThe project selector supports flexible selection syntax:
Available Projects:
# Slug Path
1 backend-abc123 /abs/path/backend
2 frontend-xyz789 /abs/path/frontend
3 infra-def456 /abs/path/infra
4 scripts-ghi789 /abs/path/scripts
Select projects to export (e.g., 'all', '1,3,5', or '1-3'):
Selection modes:
all: Export all projects (default)1: Export project #1 only1,3,5: Export projects #1, #3, and #51-3: Export projects #1, #2, and #3 (inclusive range)2-4,7: Export projects #2, #3, #4, and #7 (combined range and list)Invalid selections (out of range, malformed) are rejected with helpful error messages and the wizard prompts again.
The preview server automatically detects an available port in the range 9000-9100 instead of failing if port 9000 is in use. The actual port is displayed:
Launching preview server...
Using port 9001 (Ctrl+C to stop server)
Waiting for server to start...
✓ Server ready, opening browser at http://127.0.0.1:9001
This prevents port conflicts when multiple previews are running or when port 9000 is used by other services.
Before starting the export, the wizard shows a comprehensive summary:
═══ Deployment Summary ═══
Projects: 3 selected
Bundle size: ~32 MB
Redaction: standard
Target: GitHub Pages
Repository: mailbox-viewer-2024
Visibility: Private
Signing: Enabled (Ed25519)
Proceed with export and deployment? (Y/n):
This gives you a final chance to review all settings and cancel if needed. The bundle size is estimated based on ~10 MB per project plus ~2 MB for static assets.
Git operations and Cloudflare deployments stream output in real-time so you can see exactly what's happening:
Initializing git repository and pushing...
Initializing repository...
Initialized empty Git repository in /tmp/mailbox-preview-abc123/.git/
✓ Initializing repository complete
Adding files...
✓ Adding files complete
Creating commit...
[main (root-commit) 1a2b3c4] Initial mailbox export
425 files changed, 123456 insertions(+)
✓ Creating commit complete
Pushing to GitHub...
Enumerating objects: 430, done.
Counting objects: 100% (430/430), done.
Delta compression using up to 8 threads
Compressing objects: 100% (425/425), done.
Writing objects: 100% (430/430), 12.34 MiB | 5.67 MiB/s, done.
✓ Pushing to GitHub complete
✓ Successfully pushed to owner/mailbox-viewer-2024
This provides transparency and helps diagnose issues if deployment fails.
For GitHub Pages:
gh CLIgh auth login interactively to authenticate via browserFor Cloudflare Pages:
wrangler CLIwrangler login interactively to authenticate via browser.pages.dev URL immediately (site is live instantly)For local export:
The wizard includes comprehensive error handling:
If deployment fails after export, the bundle remains in the temp directory and can be deployed manually using the git commands shown in the manual deployment section below.
The wizard handles all operations automatically. For manual control or advanced options, see the detailed workflows below.
1. Export a bundle
# Export all projects to a directory
uv run python -m mcp_agent_mail.cli share export --output ./my-bundle
# Export specific projects only
uv run python -m mcp_agent_mail.cli share export \
--output ./my-bundle \
--project backend-abc123 \
--project frontend-xyz789
# Export with Ed25519 signing for tamper-evident distribution
uv run python -m mcp_agent_mail.cli share export \
--output ./my-bundle \
--signing-key ./keys/signing.key \
--signing-public-out ./keys/signing.pub
# Export and encrypt with age for secure distribution
uv run python -m mcp_agent_mail.cli share export \
--output ./my-bundle \
--age-recipient age1abc...xyz \
--age-recipient age1def...uvw
The export process:
standard)manifest.json with SHA-256 hashes for all assetsmanifest.sig.json)chunks.sha256 manifest so streamed pages can be verified cheaplybundle.zip.age)Once you have published a bundle you can refresh it in place without re-running the full wizard. Every export records the settings that were used (projects, scrub preset, attachment thresholds, chunking config) inside manifest.json. The new share update command reads those defaults, regenerates the SQLite snapshot and viewer assets in a temporary directory, and then replaces the bundle atomically—removing obsolete chunked files or attachments along the way.
# Refresh bundle using the originally recorded settings
uv run python -m mcp_agent_mail.cli share update ./my-bundle
# Override one or more export options while updating
uv run python -m mcp_agent_mail.cli share update ./my-bundle \
--project backend-abc123 \
--inline-threshold 16384 \
--chunk-threshold 104857600
# Re-sign and package the refreshed bundle
uv run python -m mcp_agent_mail.cli share update ./my-bundle \
--zip \
--signing-key ./keys/signing.key
When chunking was enabled previously but the refreshed snapshot no longer needs it, share update cleans up the chunks/ directory, chunks.sha256, and mailbox.sqlite3.config.json automatically, ensuring the bundle tree matches the new manifest. You can still tweak any setting at update time; overrides are written back into the export_config section of manifest.json for the next refresh.
2. Preview locally
# Serve the bundle on localhost:9000
uv run python -m mcp_agent_mail.cli share preview ./my-bundle
# Custom port and auto-open browser
uv run python -m mcp_agent_mail.cli share preview ./my-bundle \
--port 8080 \
--open-browser
This launches a lightweight HTTP server that serves the static files. Open http://127.0.0.1:9000/viewer/ in your browser to explore the archive.
Interactive preview controls:
The preview server automatically refreshes viewer assets from the source tree if available, ensuring you always see the latest HTML/JS/CSS during development.
3. Verify integrity
# Verify SRI hashes and signature
uv run python -m mcp_agent_mail.cli share verify ./my-bundle
# Verify with explicit public key (overrides manifest.sig.json)
uv run python -m mcp_agent_mail.cli share verify ./my-bundle \
--public-key AAAA...base64...
Verification checks:
4. Decrypt (if encrypted)
# Decrypt with age identity file (private key)
uv run python -m mcp_agent_mail.cli share decrypt bundle.zip.age \
--identity ~/.age/key.txt
# Decrypt with passphrase (interactive prompt)
uv run python -m mcp_agent_mail.cli share decrypt bundle.zip.age \
--passphrase
# Specify custom output path
uv run python -m mcp_agent_mail.cli share decrypt bundle.zip.age \
--output ./decrypted-bundle.zip \
--identity ~/.age/key.txt
After decryption, unzip the archive and use share preview to view it.
| Option | Type | Default | Description |
|---|---|---|---|
--output, -o |
Path | (required) | Directory where the static bundle will be written |
--project, -p |
List | All projects | Limit export to specific project slugs or human keys (repeatable) |
--inline-threshold |
Bytes | 65536 (64KB) | Inline attachments smaller than this as base64 data URIs |
--detach-threshold |
Bytes | 26214400 (25MB) | Mark attachments larger than this as external (not bundled) |
--scrub-preset |
String | standard |
Redaction preset: standard or strict (see Redaction presets section) |
--chunk-threshold |
Bytes | 20971520 (20MB) | Split SQLite database into chunks if it exceeds this size |
--chunk-size |
Bytes | 4194304 (4MB) | Chunk size when splitting large databases |
--dry-run |
Flag | false | Generate security summary and preview without writing files |
--zip / --no-zip |
Flag | true | Package the bundle into a ZIP archive |
--signing-key |
Path | None | Path to Ed25519 signing key (32-byte raw seed) |
--signing-public-out |
Path | None | Write the Ed25519 public key to this file after signing |
--age-recipient |
String | None | age public key for encryption (repeatable for multiple recipients) |
--interactive, -i |
Flag | false | Launch interactive wizard (prints guidance; full wizard TBD) |
XSS protection (DOMPurify + CSP)
Message bodies are rendered using a defense-in-depth pipeline:
This prevents malicious content in message bodies from executing JavaScript or exfiltrating data.
CSP configuration notes:
script-src: Allows self, CDNs (Tailwind, Alpine.js), and 'unsafe-eval' (required for SQL.js WebAssembly)connect-src: Allows * (all origins) to support preview mode polling and flexible deployment scenariosstyle-src: Allows self, inline styles (for Tailwind), and font CDNsCryptographic signing (Ed25519)
When you provide a signing key, the export process:
manifest.jsonmanifest.sig.jsonRecipients can verify the signature using share verify to ensure:
Requirements and fallback:
.gitignore (signing-*.key pattern)Encryption (age)
The age encryption tool (https://age-encryption.org/) provides modern, secure file encryption. When you provide recipient public keys, the export process encrypts the final ZIP archive. Only holders of the corresponding private keys can decrypt it.
Generate keys with:
# Install age (example for macOS)
brew install age
# Generate a new key pair
age-keygen -o key.txt
# Public key is printed to stdout; share it with exporters
# Private key is saved to key.txt; keep it secret
Redaction presets
The export pipeline supports configurable scrubbing to remove sensitive data:
standard: Clears acknowledgment/read state, removes file reservations and agent links, scrubs secrets (GitHub tokens, Slack tokens, OpenAI keys, bearer tokens, JWTs) from message bodies and attachment metadata. Retains agent names (which are already meaningless pseudonyms like "BlueMountain"), full message bodies, and attachments.
strict: All standard redactions plus replaces entire message bodies with "[Message body redacted]" placeholder and removes all attachments from the bundle.
All presets apply redaction to message subjects, bodies, and attachment metadata before the bundle is written.
The bundled HTML viewer provides:
Dashboard layout:
Advanced boolean search (new): Powered by SQLite FTS5 with LIKE fallback, supports complex queries:
(auth OR login) AND NOT admin"build plan" (exact match)(A OR B) AND (C OR D)A OR B AND C = A OR (B AND C))Lazy message loading (performance optimization):
Virtual scrolling (new): Clusterize.js-powered virtual list rendering:
Markdown rendering: Message bodies are rendered with GitHub-Flavored Markdown, supporting code blocks, tables, task lists, and inline images.
Opportunistic OPFS caching: The SQLite database is cached in Origin Private File System (OPFS) in the background:
Dark mode: Toggle between light and dark themes with localStorage persistence. Dark mode state is managed by the main viewer controller for consistency.
Attachment preview: Inline images render directly in message bodies. External attachments show file size and download links.
Message detail view: Click any message in the list to load its full body (lazy), view metadata (sender, recipients, timestamp, importance), and browse attachments.
No server required: After the initial HTTP serving (which can be a static file host like S3, GitHub Pages, or Netlify), all functionality runs client-side. No backend, no API calls, no authentication.
Browser compatibility: Works in all modern browsers (Chrome, Firefox, Safari, Edge) with graceful fallbacks for missing features (OPFS, FTS5).
Option 1: GitHub Pages (automated via wizard)
# Use the wizard for fully automated deployment
uv run python -m mcp_agent_mail.cli share wizard
# Select: GitHub Pages → provide repo name → wizard handles everything
Or manually:
# Export and unzip
uv run python -m mcp_agent_mail.cli share export --output ./bundle --no-zip
cd bundle
# Initialize git and push to GitHub
git init
git add .
git commit -m "Initial export"
git remote add origin [email protected]:yourorg/project-archive.git
git push -u origin main
# Enable GitHub Pages in repo settings (source: main branch, root directory)
Option 2: Cloudflare Pages (automated via wizard)
# Use the wizard for instant global CDN deployment
uv run python -m mcp_agent_mail.cli share wizard
# Select: Cloudflare Pages → provide project name → wizard deploys directly
Or manually with wrangler CLI:
# Export and deploy
uv run python -m mcp_agent_mail.cli share export --output ./bundle --no-zip
npx wrangler pages deploy ./bundle --project-name=project-archive
# Your site is live at: https://project-archive.pages.dev
Benefits of Cloudflare Pages:
Option 3: S3 + CloudFront
# Export and unzip
uv run python -m mcp_agent_mail.cli share export --output ./bundle --no-zip
# Upload to S3
aws s3 sync ./bundle s3://your-bucket/archives/project-2024/ --acl public-read
# Access via CloudFront
# https://d123abc.cloudfront.net/archives/project-2024/
Option 4: Nginx static site
server {
listen 443 ssl;
server_name archives.example.com;
ssl_certificate /etc/letsencrypt/live/archives.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/archives.example.com/privkey.pem;
root /var/www/archives/project-2024;
index index.html;
# Enable gzip for efficient transfer
gzip on;
gzip_types text/html text/css application/javascript application/json application/wasm;
# Cache static assets
location ~* \.(js|css|wasm|png|jpg|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# CSP headers are already in index.html meta tag
# Add HTTPS-only and frame protection
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
}
Option 5: Encrypted distribution via file sharing
For confidential archives:
# Export with age encryption
uv run python -m mcp_agent_mail.cli share export \
--output ./bundle \
--signing-key ./signing.key \
--age-recipient age1auditor... \
--age-recipient age1manager...
# This produces bundle.zip.age
# Upload to Dropbox, Google Drive, or send via secure file transfer
# Recipients decrypt locally
uv run python -m mcp_agent_mail.cli share decrypt bundle.zip.age \
--identity ~/.age/key.txt
# Verify integrity
unzip bundle.zip
uv run python -m mcp_agent_mail.cli share verify ./bundle
# Preview locally
uv run python -m mcp_agent_mail.cli share preview ./bundle
Quarterly audit package
# Export Q4 2024 communications for audit
uv run python -m mcp_agent_mail.cli share export \
--output ./audit-q4-2024 \
--scrub-preset strict \
--signing-key ./audit-signing.key \
--signing-public-out ./audit-signing.pub \
--age-recipient [email protected]
# Produces: audit-q4-2024.zip.age
# Send to auditor with audit-signing.pub
# Auditor verifies:
age -d -i auditor-key.txt audit-q4-2024.zip.age > audit-q4-2024.zip
unzip audit-q4-2024.zip
python -m mcp_agent_mail.cli share verify ./audit-q4-2024 \
--public-key $(cat audit-signing.pub)
Executive summary for stakeholders
# Export high-importance threads only
# (filter in UI after export, or use SQL to create filtered snapshot)
uv run python -m mcp_agent_mail.cli share export \
--output ./exec-summary \
--project backend-prod \
--scrub-preset standard
# Host on internal web server
cp -r ./exec-summary /var/www/exec-archives/2024-12/
# Share link: https://internal.example.com/exec-archives/2024-12/
Disaster recovery backup
# Monthly encrypted backup
uv run python -m mcp_agent_mail.cli share export \
--output ./backup-$(date +%Y-%m) \
--scrub-preset none \
--signing-key ./dr-signing.key \
--age-recipient [email protected]
# Store in off-site backup system
aws s3 cp backup-2024-12.zip.age s3://dr-backups/mcp-mail/ \
--storage-class GLACIER_IR
# Restore procedure documented in runbook
Export fails with "Database locked"
The export takes a snapshot using SQLite's Online Backup API. If the server is actively writing, wait a few seconds and retry. For large databases, consider temporarily stopping the server during export.
Bundle size is too large
Use --detach-threshold to mark large attachments as external references. These won't be included in the bundle but will show file metadata in the viewer.
# Bundle files under 1MB, mark larger files as external
uv run python -m mcp_agent_mail.cli share export \
--output ./bundle \
--detach-threshold 1048576
Alternatively, filter to specific projects with --project.
Encrypted bundle won't decrypt
Verify you're using the correct identity file:
# Get your public key from your identity file
age-keygen -y identity.txt
# Ensure this public key was included in the --age-recipient values during export
# If you have multiple identity files, try each one
age -d -i identity.txt bundle.zip.age > bundle.zip
Signature verification fails
Signature verification requires:
manifest.json (unmodified)manifest.sig.json file (contains signature and public key)If verification fails, the bundle may have been tampered with or corrupted during transfer. Re-export and re-transfer.
Viewer shows blank page or errors
Check browser console for errors. Common issues:
--chunk-threshold) for very large archives.index.html and should not be overridden.<store>/projects/<slug>/
agents/<AgentName>/profile.json
agents/<AgentName>/inbox/YYYY/MM/<msg-id>.md
agents/<AgentName>/outbox/YYYY/MM/<msg-id>.md
messages/YYYY/MM/<msg-id>.md
messages/threads/<thread-id>.md # optional human digest maintained by the server
file_reservations/<sha1-of-path>.json
attachments/<xx>/<sha1>.webp
Messages are GitHub-Flavored Markdown with JSON frontmatter (fenced by ---json). Attachments are either WebP files referenced by relative path or inline base64 WebP data URIs.
---json
{
"id": 1234,
"thread_id": "TKT-123",
"project": "/abs/path/backend",
"project_slug": "backend-abc123",
"from": "GreenCastle",
"to": ["BlueLake"],
"cc": [],
"created": "2025-10-23T15:22:14Z",
"importance": "high",
"ack_required": true,
"attachments": [
{"type": "file", "media_type": "image/webp", "path": "projects/backend-abc123/attachments/2a/2a6f.../diagram.webp"}
]
}
---
# Build plan for /api/users routes
... body markdown ...
projects(id, human_key, slug, created_at)agents(id, project_id, name, program, model, task_description, inception_ts, last_active_ts, attachments_policy, contact_policy)messages(id, project_id, sender_id, thread_id, subject, body_md, created_ts, importance, ack_required, attachments)message_recipients(message_id, agent_id, kind, read_ts, ack_ts)file_reservations(id, project_id, agent_id, path_pattern, exclusive, reason, created_ts, expires_ts, released_ts)agent_links(id, a_project_id, a_agent_id, b_project_id, b_agent_id, status, reason, created_ts, updated_ts, expires_ts)project_sibling_suggestions(id, project_a_id, project_b_id, score, status, rationale, created_ts, evaluated_ts, confirmed_ts, dismissed_ts)fts_messages(message_id UNINDEXED, subject, body) + triggers for incremental updates.archive.lock under projects/<slug>/.commit.lockregister_agent(project_key, program, model, name?, task_description?) → creates/updates a named identity, persists profile to Git, and commits.send_message(project_key, sender_name, to[], subject, body_md, cc?, bcc?, attachment_paths?, convert_images?, importance?, ack_required?, thread_id?, auto_contact_if_blocked?)messages/YYYY/MM/, an outbox copy for the sender, and inbox copies for each recipient; commits all artifacts.sequenceDiagram
participant Agent
participant Server
participant DB
participant Git
Agent->>Server: call send_message
Server->>DB: insert message and recipients
DB-->>Server: ok
Server->>Git: write canonical markdown
Server->>Git: write outbox copy
Server->>Git: write inbox copies
Server->>Git: commit
Server-->>Agent: result
fetch_inbox(project_key, agent_name, since_ts?, urgent_only?, include_bodies?, limit?) returns recent messages, preserving thread_id where available.acknowledge_message(project_key, agent_name, message_id) marks acknowledgements.file_reservation_paths(project_key, agent_name, paths[], ttl_seconds, exclusive, reason) records an advisory lease in DB and writes JSON reservation artifacts in Git; conflicts are reported if overlapping active exclusives exist (reservations are still granted; conflicts are returned alongside grants).release_file_reservations(project_key, agent_name, paths? | file_reservation_ids?) releases active leases (all if none specified). JSON artifacts remain for audit history.sequenceDiagram
participant Agent
participant Server
participant DB
participant Git
Agent->>Server: call file_reservation_paths
Server->>DB: expire old leases and check overlaps
DB-->>Server: conflicts or grants
Server->>DB: insert file reservation rows
Server->>Git: write file reservation JSON files
Server->>Git: commit
Server-->>Agent: granted paths and any conflicts
search_messages(project_key, query, limit?) uses FTS5 over subject and body.summarize_thread(project_key, thread_id, include_examples?) extracts key points, actions, and participants from the thread.reply_message(project_key, message_id, sender_name, body_md, ...) creates a subject-prefixed reply, preserving or creating a thread.name_hint is sanitized (alnum) and used if availablewhois returns the stored profile; list_agents can filter by recent activitylast_active_ts is bumped on relevant interactions (messages, inbox checks, etc.)thread_id from the original; if missing, the reply sets thread_id to the original message idRe:) for readability in mailboxesattachments/<xx>/<sha1>.webp and referenced by relative path in frontmattercreated_ts DESCGoal: make coordination "just work" without spam across unrelated agents. The server enforces per-project isolation by default and adds an optional consent layer within a project so agents only contact relevant peers.
project_key. Agents only see messages addressed to them within that project.open: accept any targeted messages in the project.auto (default): allow messages when there is obvious shared context (e.g., same thread participants; recent overlapping active file reservations; recent prior direct contact within a TTL); otherwise requires a contact request.contacts_only: require an approved contact link first.block_all: reject all new contacts (errors with CONTACT_BLOCKED).Use set_contact_policy(project_key, agent_name, policy) to update.
request_contact(project_key, from_agent, to_agent, reason?, ttl_seconds?) creates or refreshes a pending link and sends a small ack_required "intro" message to the recipient.respond_contact(project_key, to_agent, from_agent, accept, ttl_seconds?) approves or denies; approval grants messaging until expiry.list_contacts(project_key, agent_name) surfaces current links.These heuristics minimize friction while preventing cold spam.
When two repos represent the same underlying project (e.g., frontend and backend), you have two options:
Use the same project_key across both workspaces. Agents in both repos operate under one project namespace and benefit from full inbox/outbox coordination automatically.
Keep separate project_keys and establish explicit contact:
backend, agent GreenCastle calls:request_contact(project_key="/abs/path/backend", from_agent="GreenCastle", to_agent="BlueLake", reason="API contract changes")frontend, BlueLake calls:respond_contact(project_key="/abs/path/backend", to_agent="BlueLake", from_agent="GreenCastle", accept=true)auto policy the server allows follow-up threads/reservation-based coordination without re-requesting.Important: You can also create reciprocal links or set open policy for trusted pairs. The consent layer is on by default (CONTACT_ENFORCEMENT_ENABLED=true) but is designed to be non-blocking in obvious collaboration contexts.
Expose common reads as resources that clients can fetch. See API Quick Reference → Resources for the full list and parameters.
Example (conceptual) resource read:
{
"method": "resources/read",
"params": {"uri": "resource://inbox/BlueLake?project=/abs/path/backend&limit=20"}
}
sequenceDiagram
participant Client
participant Server
participant DB
Client->>Server: read inbox resource
Server->>DB: select messages for agent
DB-->>Server: rows
Server-->>Client: inbox data
mcp-agent-mail guard status /path/to/repomcp-agent-mail guard install <project_key> <repo_path> --prepushWORKTREES_ENABLED and AGENT_MAIL_GUARD_MODE (warn advisory).rev-list) and uses diff-tree with --no-ext-diff..git/hooks/pre-commit and .git/hooks/pre-push.hooks.d/<hook>/* in lexical order, then <hook>.orig if present (existing hooks are preserved, not overwritten).hooks.d/pre-commit/50-agent-mail.py and hooks.d/pre-push/50-agent-mail.py.pre-commit.cmd/.ps1, pre-push.cmd/.ps1) are written to invoke the Python chain-runner.git diff --cached --name-status -M -z).core.ignorecase is honored.AGENT_MAIL_BYPASS=1, or use native Git --no-verify. In warn mode the guard never blocks.Gate: WORKTREES_ENABLED=1 or GIT_IDENTITY_ENABLED=1 enables git-based identity features. Default off.
Identity modes (default dir): dir, git-remote, git-toplevel, git-common-dir.
Inspect identity for a path:
resource://identity/{/abs/path} (available when WORKTREES_ENABLED=1)mcp-agent-mail mail status /abs/pathPrecedence (when gate is on):
.agent-mail-project-id (recommended).agent-mail.yaml with project_uid:.git/agent-mail/project-idorigin URL + default branchgit-common-dir hash; else dir hashMigration helpers:
mcp-agent-mail projects mark-identity . --commitmcp-agent-mail projects discovery-init . --product <product_uid>Example identity payload (resource):
{
"project_uid": "c5b2c86b-7c36-4de6-9a0a-2c4e1c3a1c4a",
"slug": "repo-a1b2c3d4e5",
"identity_mode_used": "git-remote",
"canonical_path": "github.com/owner/repo",
"human_key": "/abs/worktree/path",
"repo_root": "/abs/repo",
"git_common_dir": "/abs/repo/.git",
"branch": "feature/x",
"worktree_name": "repo-wt-x",
"core_ignorecase": true,
"normalized_remote": "github.com/owner/repo"
}
Consolidate legacy per-worktree projects into a canonical one (safe, explicit, and auditable).
mcp-agent-mail projects adopt <from> <to> --dry-runmcp-agent-mail projects adopt <from> <to> --applygit-common-dir).projects/<old-slug>/… to projects/<new-slug>/… while preserving history.agents, messages, file_reservations) from source to target project.aliases.json under the target with "former_slugs": [...] for discoverability.amctl env prints helpful environment keys:
SLUG, PROJECT_UID, BRANCH, AGENT, CACHE_KEY, ARTIFACT_DIRmcp-agent-mail amctl env --path . --agent AliceDevam-run wraps a command with those keys set:
mcp-agent-mail am-run frontend-build -- npm run devBuild slots (advisory, per-project coarse locking):
--ttl-seconds: lease duration (default 3600)--shared/--exclusive: non-exclusive or exclusive lease (default exclusive)--block-on-conflicts: exit non-zero if exclusive conflicts are detected before startingacquire_build_slot(project_key, agent_name, slot, ttl_seconds=3600, exclusive=true)renew_build_slot(project_key, agent_name, slot, extend_seconds=1800)release_build_slot(project_key, agent_name, slot)build_slots/<slot>/<agent>__<branch>.jsonexclusive=true reports conflicts if another active exclusive holder existsam-run and amctl envGroup multiple repositories (e.g., frontend, backend, infra) under a single product for product‑wide inbox/search and shared threads.
mcp-agent-mail products ensure MyProduct --name "My Product"mcp-agent-mail products link MyProduct .mcp-agent-mail products status MyProductmcp-agent-mail products search MyProduct "urgent AND deploy" --limit 50mcp-agent-mail products inbox MyProduct Alice --limit 50 --urgent-only --include-bodies --since-ts "2025-11-01T00:00:00Z"mcp-agent-mail products summarize-thread MyProduct "bd-123" --per-thread-limit 100 --no-llmdocker build -t mcp-agent-mail .
docker run --rm -p 8765:8765 \
-e HTTP_HOST=0.0.0.0 \
-e STORAGE_ROOT=/data/mailbox \
-v agent_mail_data:/data \
mcp-agent-mail
docker compose up --build
appuser, uid 10001)./health/liveness..env via python-decouple. You can mount it read-only into the container at /app/.env.0.0.0.0 in the container; port 8765 is exposed./data/mailbox (mapped to the agent_mail_data volume by default).Notes
product_uid is stored for each product; you can reference a product by uid or name.ensure_product, products_link, search_messages_product, and resource://product/{key}.Exclusive file reservations are advisory but visible and auditable:
file_reservations/<sha1(path)>.json capturing holder, pattern, exclusivity, created/expiresAGENT_NAME so the guard knows who "owns" the commitforce_release_file_reservation tool uses the same heuristics and notifies the previous holder when another agent clears a stale leaseInstall the guard into a code repo (conceptual tool call):
{
"method": "tools/call",
"params": {
"name": "install_precommit_guard",
"arguments": {
"project_key": "/abs/path/backend",
"code_repo_path": "/abs/path/backend"
}
}
}
Configuration is loaded from an existing .env via python-decouple. Do not use os.getenv or auto-dotenv loaders.
If port 8765 is already in use (e.g., by Cursor's Python extension), you can change it:
Option 1: During installation One-liner with custom port:
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/mcp_agent_mail/main/scripts/install.sh?$(date +%s)" | bash -s -- --port 9000 --yes
Or with local script: ./scripts/install.sh --port 9000 --yes
**Option 2: After installation (CLI)**
```bash
# Change port using CLI command
uv run python -m mcp_agent_mail.cli config set-port 9000
# View current port configuration
uv run python -m mcp_agent_mail.cli config show-port
# Restart server for changes to take effect
scripts/run_server_with_token.sh
Option 3: Manual .env edit
# Edit .env file manually with your text editor (recommended)
nano .env # or vim, code, etc.
# Or append (⚠️ warning: will create duplicate if HTTP_PORT already exists)
echo "HTTP_PORT=9000" >> .env
Option 4: CLI server override
# Override port at server startup (doesn't modify .env)
uv run python -m mcp_agent_mail.cli serve-http --port 9000
from decouple import Config as DecoupleConfig, RepositoryEnv
decouple_config = DecoupleConfig(RepositoryEnv(".env"))
STORAGE_ROOT = decouple_config("STORAGE_ROOT", default="~/.mcp_agent_mail_git_mailbox_repo")
HTTP_HOST = decouple_config("HTTP_HOST", default="127.0.0.1")
HTTP_PORT = int(decouple_config("HTTP_PORT", default=8765))
HTTP_PATH = decouple_config("HTTP_PATH", default="/mcp/")
Common variables you may set:
| Name | Default | Description |
|---|---|---|
STORAGE_ROOT |
~/.mcp_agent_mail_git_mailbox_repo |
Root for per-project repos and SQLite DB |
HTTP_HOST |
127.0.0.1 |
Bind host for HTTP transport |
HTTP_PORT |
8765 |
Bind port for HTTP transport |
HTTP_PATH |
/mcp/ |
HTTP path where MCP endpoint is mounted |
HTTP_JWT_ENABLED |
false |
Enable JWT validation middleware |
HTTP_JWT_SECRET |
HMAC secret for HS* algorithms (dev) | |
HTTP_JWT_JWKS_URL |
JWKS URL for public key verification | |
HTTP_JWT_ALGORITHMS |
HS256 |
CSV of allowed algs |
HTTP_JWT_AUDIENCE |
Expected aud (optional) |
|
HTTP_JWT_ISSUER |
Expected iss (optional) |
|
HTTP_JWT_ROLE_CLAIM |
role |
JWT claim name containing role(s) |
HTTP_RBAC_ENABLED |
true |
Enforce read-only vs tools RBAC |
HTTP_RBAC_READER_ROLES |
reader,read,ro |
CSV of reader roles |
HTTP_RBAC_WRITER_ROLES |
writer,write,tools,rw |
CSV of writer roles |
HTTP_RBAC_DEFAULT_ROLE |
reader |
Role used when none present |
HTTP_RBAC_READONLY_TOOLS |
health_check,fetch_inbox,whois,search_messages,summarize_thread |
CSV of read-only tool names |
HTTP_RATE_LIMIT_ENABLED |
false |
Enable token-bucket limiter |
HTTP_RATE_LIMIT_BACKEND |
memory |
memory or redis |
HTTP_RATE_LIMIT_PER_MINUTE |
60 |
Legacy per-IP limit (fallback) |
HTTP_RATE_LIMIT_TOOLS_PER_MINUTE |
60 |
Per-minute for tools/call |
HTTP_RATE_LIMIT_TOOLS_BURST |
0 |
Optional burst for tools (0=auto=rpm) |
HTTP_RATE_LIMIT_RESOURCES_PER_MINUTE |
120 |
Per-minute for resources/read |
HTTP_RATE_LIMIT_RESOURCES_BURST |
0 |
Optional burst for resources (0=auto=rpm) |
HTTP_RATE_LIMIT_REDIS_URL |
Redis URL for multi-worker limits | |
HTTP_REQUEST_LOG_ENABLED |
false |
Print request logs (Rich + JSON) |
LOG_JSON_ENABLED |
false |
Output structlog JSON logs |
INLINE_IMAGE_MAX_BYTES |
65536 |
Threshold (bytes) for inlining WebP images during send_message |
CONVERT_IMAGES |
true |
Convert images to WebP (and optionally inline small ones) |
KEEP_ORIGINAL_IMAGES |
false |
Also store original image bytes alongside WebP (attachments/originals/) |
LOG_LEVEL |
INFO |
Server log level |
HTTP_CORS_ENABLED |
false |
Enable CORS middleware when true |
HTTP_CORS_ORIGINS |
CSV of allowed origins (e.g., https://app.example.com,https://ops.example.com) |
|
HTTP_CORS_ALLOW_CREDENTIALS |
false |
Allow credentials on CORS |
HTTP_CORS_ALLOW_METHODS |
* |
CSV of allowed methods or * |
HTTP_CORS_ALLOW_HEADERS |
* |
CSV of allowed headers or * |
HTTP_BEARER_TOKEN |
Static bearer token (only when JWT disabled) | |
HTTP_ALLOW_LOCALHOST_UNAUTHENTICATED |
true |
Allow localhost requests without auth (dev convenience) |
HTTP_OTEL_ENABLED |
false |
Enable OpenTelemetry instrumentation |
OTEL_SERVICE_NAME |
mcp-agent-mail |
Service name for telemetry |
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP exporter endpoint URL | |
APP_ENVIRONMENT |
development |
Environment name (development/production) |
DATABASE_URL |
sqlite+aiosqlite:///./storage.sqlite3 |
SQLAlchemy async database URL |
DATABASE_ECHO |
false |
Echo SQL statements for debugging |
GIT_AUTHOR_NAME |
mcp-agent |
Git commit author name |
GIT_AUTHOR_EMAIL |
[email protected] |
Git commit author email |
LLM_ENABLED |
true |
Enable LiteLLM for thread summaries and discovery |
LLM_DEFAULT_MODEL |
gpt-5-mini |
Default LiteLLM model identifier |
LLM_TEMPERATURE |
0.2 |
LLM temperature for text generation |
LLM_MAX_TOKENS |
512 |
Max tokens for LLM completions |
LLM_CACHE_ENABLED |
true |
Enable LLM response caching |
LLM_CACHE_BACKEND |
memory |
LLM cache backend (memory or redis) |
LLM_CACHE_REDIS_URL |
Redis URL for LLM cache (if backend=redis) | |
LLM_COST_LOGGING_ENABLED |
true |
Log LLM API costs and token usage |
FILE_RESERVATIONS_CLEANUP_ENABLED |
false |
Enable background cleanup of expired file reservations |
FILE_RESERVATIONS_CLEANUP_INTERVAL_SECONDS |
60 |
Interval for file reservations cleanup task |
FILE_RESERVATION_INACTIVITY_SECONDS |
1800 |
Inactivity threshold (seconds) before a reservation is considered stale |
FILE_RESERVATION_ACTIVITY_GRACE_SECONDS |
900 |
Grace window for recent mail/filesystem/git activity to keep a reservation active |
FILE_RESERVATIONS_ENFORCEMENT_ENABLED |
true |
Block message writes on conflicting file reservations |
ACK_TTL_ENABLED |
false |
Enable overdue ACK scanning (logs/panels; see views/resources) |
ACK_TTL_SECONDS |
1800 |
Age threshold (seconds) for overdue ACKs |
ACK_TTL_SCAN_INTERVAL_SECONDS |
60 |
Scan interval for overdue ACKs |
ACK_ESCALATION_ENABLED |
false |
Enable escalation for overdue ACKs |
ACK_ESCALATION_MODE |
log |
log or file_reservation escalation mode |
ACK_ESCALATION_CLAIM_TTL_SECONDS |
3600 |
TTL for escalation file reservations |
ACK_ESCALATION_CLAIM_EXCLUSIVE |
false |
Make escalation file reservation exclusive |
ACK_ESCALATION_CLAIM_HOLDER_NAME |
Ops agent name to own escalation file reservations | |
CONTACT_ENFORCEMENT_ENABLED |
true |
Enforce contact policy before messaging |
CONTACT_AUTO_TTL_SECONDS |
86400 |
TTL for auto-approved contacts (1 day) |
CONTACT_AUTO_RETRY_ENABLED |
true |
Auto-retry contact requests on policy violations |
MESSAGING_AUTO_REGISTER_RECIPIENTS |
true |
Automatically create missing local recipients during send_message and retry routing |
MESSAGING_AUTO_HANDSHAKE_ON_BLOCK |
true |
When contact policy blocks delivery, attempt a contact handshake (auto-accept) and retry |
TOOLS_LOG_ENABLED |
true |
Log tool invocations for debugging |
LOG_RICH_ENABLED |
true |
Enable Rich console logging |
LOG_INCLUDE_TRACE |
false |
Include trace-level logs |
TOOL_METRICS_EMIT_ENABLED |
false |
Emit periodic tool usage metrics |
TOOL_METRICS_EMIT_INTERVAL_SECONDS |
60 |
Interval for metrics emission |
RETENTION_REPORT_ENABLED |
false |
Enable retention/quota reporting |
RETENTION_REPORT_INTERVAL_SECONDS |
3600 |
Interval for retention reports (1 hour) |
RETENTION_MAX_AGE_DAYS |
180 |
Max age for retention policy reporting |
QUOTA_ENABLED |
false |
Enable quota enforcement |
QUOTA_ATTACHMENTS_LIMIT_BYTES |
0 |
Max attachment storage per project (0=unlimited) |
QUOTA_INBOX_LIMIT_COUNT |
0 |
Max inbox messages per agent (0=unlimited) |
RETENTION_IGNORE_PROJECT_PATTERNS |
demo,test*,testproj*,testproject,backendproj*,frontendproj* |
CSV of project patterns to ignore in retention/quota reports |
AGENT_NAME_ENFORCEMENT_MODE |
coerce |
Agent naming policy: strict (reject invalid adjective+noun names), coerce (auto-generate if invalid), always_auto (always auto-generate) |
Prerequisite: complete the setup above (Python 3.14 + uv venv + uv sync).
Dev helpers:
# Quick endpoint smoke test (server must be running locally)
bash scripts/test_endpoints.sh
# Pre-commit guard smoke test (no pytest)
bash scripts/test_guard.sh
Database schema (automatic):
# Tables are created from SQLModel definitions on first run.
# If models change, delete the SQLite DB (and WAL/SHM) and run migrate again.
uv run python -m mcp_agent_mail.cli migrate
Run the server (HTTP-only). Use the Typer CLI or module entry:
uv run python -m mcp_agent_mail.cli serve-http
uv run python -m mcp_agent_mail.http --host 127.0.0.1 --port 8765
Connect with your MCP client using the HTTP (Streamable HTTP) transport on the configured host/port. The endpoint tolerates both /mcp and /mcp/.
plan users"build plan"mig*plan AND users NOT legacyattachments/ for audit and reuseThis section has been removed to keep the README focused. See API Quick Reference below for canonical method signatures.
HTTP_JWT_ENABLED=trueHTTP_BEARER_TOKEN) is independent of JWT; when set, BearerAuth protects all routes (including UI). You may use it alone or together with JWT.HTTP_JWT_JWKS_URL), incoming JWTs must include a matching kid header; tokens without kid or with unknown kid are rejectedHTTP_RBAC_* settingsupstream mcp_mail { server 127.0.0.1:8765; }
server {
listen 443 ssl;
server_name mcp.example.com;
ssl_certificate /etc/letsencrypt/live/mcp.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.example.com/privkey.pem;
location /mcp/ { proxy_pass http://mcp_mail; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; }
}
STORAGE_ROOT; back them up together for consistencymcp.http_app() (Prometheus, OpenTelemetry).archive.lock prevents cross-project head-of-line blocking.commit.lock serializes Git index/commit to avoid races across projectsThis section has been removed to keep the README focused. Client code samples belong in examples/.
register_agent or create_agent_identity, or check the project_key you're using matches the sender's projectAGENT_NAME to your agent identity; release or wait for conflicting exclusive file reservations; inspect .git/hooks/pre-commitconvert_images=true; images are automatically inlined if the compressed WebP size is below the server's INLINE_IMAGE_MAX_BYTES threshold (default 64KB). Larger images are stored as attachments instead.project disambiguation when using resource://message/{id}; ids are unique per projectsince_ts, urgent_only, and limit; verify recipient names match exactly (case-sensitive)Why Git and SQLite together?
Are file reservations enforced?
FILE_RESERVATIONS_ENFORCEMENT_ENABLED=true, default). Reservations themselves are advisory and always return both granted and conflicts. The optional pre-commit hook adds local enforcement at commit time in your code repo.Why HTTP-only?
Why JSON-RPC instead of REST or gRPC?
Why separate "resources" (reads) from "tools" (mutations)?
Why canonical message storage in Git, not only in the database?
Why advisory file reservations instead of global locks?
Why are agent names adjective+noun?
Why is project_key an absolute path?
Why WebP attachments and optional inlining?
KEEP_ORIGINAL_IMAGES=true).Why both static bearer and JWT/JWKS support?
Why SQLite FTS5 instead of an external search service?
Why is LLM usage optional?
Tip: to see tools grouped by workflow with recommended playbooks, fetch
resource://tooling/directory.
| Name | Signature | Returns | Notes |
|---|---|---|---|
health_check |
health_check() |
{status, environment, http_host, http_port, database_url} |
Lightweight readiness probe |
ensure_project |
ensure_project(human_key: str) |
{id, slug, human_key, created_at} |
Idempotently creates/ensures project |
register_agent |
register_agent(project_key: str, program: str, model: str, name?: str, task_description?: str, attachments_policy?: str) |
Agent profile dict | Creates/updates agent; writes profile to Git |
whois |
whois(project_key: str, agent_name: str, include_recent_commits?: bool, commit_limit?: int) |
Agent profile dict | Enriched profile for one agent (optionally includes recent commits) |
create_agent_identity |
create_agent_identity(project_key: str, program: str, model: str, name_hint?: str, task_description?: str, attachments_policy?: str) |
Agent profile dict | Always creates a new unique agent |
send_message |
send_message(project_key: str, sender_name: str, to: list[str], subject: str, body_md: str, cc?: list[str], bcc?: list[str], attachment_paths?: list[str], convert_images?: bool, importance?: str, ack_required?: bool, thread_id?: str, auto_contact_if_blocked?: bool) |
{deliveries: list, count: int, attachments?} |
Writes canonical + inbox/outbox, converts images |
reply_message |
reply_message(project_key: str, message_id: int, sender_name: str, body_md: str, to?: list[str], cc?: list[str], bcc?: list[str], subject_prefix?: str) |
{thread_id, reply_to, deliveries: list, count: int, attachments?} |
Preserves/creates thread, inherits flags |
request_contact |
request_contact(project_key: str, from_agent: str, to_agent: str, to_project?: str, reason?: str, ttl_seconds?: int) |
Contact link dict | Request permission to message another agent |
respond_contact |
respond_contact(project_key: str, to_agent: str, from_agent: str, accept: bool, from_project?: str, ttl_seconds?: int) |
Contact link dict | Approve or deny a contact request |
list_contacts |
list_contacts(project_key: str, agent_name: str) |
list[dict] |
List contact links for an agent |
set_contact_policy |
set_contact_policy(project_key: str, agent_name: str, policy: str) |
Agent dict | Set policy: open, auto, contacts_only, block_all |
fetch_inbox |
fetch_inbox(project_key: str, agent_name: str, limit?: int, urgent_only?: bool, include_bodies?: bool, since_ts?: str) |
list[dict] |
Non-mutating inbox read |
mark_message_read |
mark_message_read(project_key: str, agent_name: str, message_id: int) |
{message_id, read, read_at} |
Per-recipient read receipt |
acknowledge_message |
acknowledge_message(project_key: str, agent_name: str, message_id: int) |
{message_id, acknowledged, acknowledged_at, read_at} |
Sets ack and read |
macro_start_session |
macro_start_session(human_key: str, program: str, model: str, task_description?: str, agent_name?: str, file_reservation_paths?: list[str], file_reservation_reason?: str, file_reservation_ttl_seconds?: int, inbox_limit?: int) |
{project, agent, file_reservations, inbox} |
Orchestrates ensure→register→optional file reservation→inbox fetch |
macro_prepare_thread |
macro_prepare_thread(project_key: str, thread_id: str, program: str, model: str, agent_name?: str, task_description?: str, register_if_missing?: bool, include_examples?: bool, inbox_limit?: int, include_inbox_bodies?: bool, llm_mode?: bool, llm_model?: str) |
{project, agent, thread, inbox} |
Bundles registration, thread summary, and inbox context |
macro_file_reservation_cycle |
macro_file_reservation_cycle(project_key: str, agent_name: str, paths: list[str], ttl_seconds?: int, exclusive?: bool, reason?: str, auto_release?: bool) |
{file_reservations, released} |
File Reservation + optionally release surfaces around a focused edit block |
macro_contact_handshake |
`macro_contact_handshake(project_key: str, requester | agent_name: str, target | to_agent: str, to_project?: str, reason?: str, ttl_seconds?: int, auto_accept?: bool, welcome_subject?: str, welcome_body?: str)` |
search_messages |
search_messages(project_key: str, query: str, limit?: int) |
list[dict] |
FTS5 search (bm25) |
summarize_thread |
summarize_thread(project_key: str, thread_id: str, include_examples?: bool, llm_mode?: bool, llm_model?: str, per_thread_limit?: int) |
Single: {thread_id, summary, examples} Multi (comma-sep): {threads[], aggregate} |
Extracts participants, key points, actions. Use comma-separated thread_id for multi-thread digest. |
install_precommit_guard |
install_precommit_guard(project_key: str, code_repo_path: str) |
{hook} |
Install a Git pre-commit guard in a target repo |
uninstall_precommit_guard |
uninstall_precommit_guard(code_repo_path: str) |
{removed} |
Remove the guard from a repo |
file_reservation_paths |
file_reservation_paths(project_key: str, agent_name: str, paths: list[str], ttl_seconds?: int, exclusive?: bool, reason?: str) |
{granted: list, conflicts: list} |
Advisory leases; Git artifact per path |
release_file_reservations |
release_file_reservations(project_key: str, agent_name: str, paths?: list[str], file_reservation_ids?: list[int]) |
{released, released_at} |
Releases agent's active file reservations |
force_release_file_reservation |
force_release_file_reservation(project_key: str, agent_name: str, file_reservation_id: int, notify_previous?: bool, note?: str) |
{released, released_at, reservation} |
Clears stale reservations using inactivity/mail/fs/git heuristics and notifies the previous holder |
renew_file_reservations |
renew_file_reservations(project_key: str, agent_name: str, extend_seconds?: int, paths?: list[str], file_reservation_ids?: list[int]) |
{renewed, file reservations[]} |
Extend TTL of existing file reservations |
| URI | Params | Returns | Notes |
|---|---|---|---|
resource://config/environment |
— | {environment, database_url, http} |
Inspect server settings |
resource://tooling/directory |
— | {generated_at, metrics_uri, clusters[], playbooks[]} |
Grouped tool directory + workflow playbooks |
resource://tooling/schemas |
— | {tools: {<name>: {required[], optional[], aliases{}}}} |
Argument hints for tools |
resource://tooling/metrics |
— | {generated_at, tools[]} |
Aggregated call/error counts per tool |
resource://tooling/locks |
— | {locks[], summary} |
Active locks and owners (debug only). Categories: archive (per-project .archive.lock) and custom (e.g., repo .commit.lock). |
resource://tooling/capabilities/{agent}{?project} |
listed | {generated_at, agent, project, capabilities[]} |
Capabilities assigned to the agent (see deploy/capabilities/agent_capabilities.json) |
resource://tooling/recent/{window_seconds}{?agent,project} |
listed | {generated_at, window_seconds, count, entries[]} |
Recent tool usage filtered by agent/project |
resource://projects |
— | list[project] |
All projects |
resource://project/{slug} |
slug |
{project..., agents[]} |
Project detail + agents |
resource://file_reservations/{slug}{?active_only} |
slug, active_only? |
list[file reservation] |
File reservations plus staleness metadata (heuristics, last activity timestamps) |
resource://message/{id}{?project} |
id, project |
message |
Single message with body |
resource://thread/{thread_id}{?project,include_bodies} |
thread_id, project, include_bodies? |
{project, thread_id, messages[]} |
Thread listing |
resource://inbox/{agent}{?project,since_ts,urgent_only,include_bodies,limit} |
listed | {project, agent, count, messages[]} |
Inbox listing |
resource://mailbox/{agent}{?project,limit} |
project, limit |
{project, agent, count, messages[]} |
Mailbox listing (recent messages with basic commit ref) |
resource://mailbox-with-commits/{agent}{?project,limit} |
project, limit |
{project, agent, count, messages[]} |
Mailbox listing enriched with commit metadata |
resource://outbox/{agent}{?project,limit,include_bodies,since_ts} |
listed | {project, agent, count, messages[]} |
Messages sent by the agent |
resource://views/acks-stale/{agent}{?project,ttl_seconds,limit} |
listed | {project, agent, ttl_seconds, count, messages[]} |
Ack-required older than TTL without ack |
resource://views/urgent-unread/{agent}{?project,limit} |
listed | {project, agent, count, messages[]} |
High/urgent importance messages not yet read |
resource://views/ack-required/{agent}{?project,limit} |
listed | {project, agent, count, messages[]} |
Pending acknowledgements for an agent |
resource://views/ack-overdue/{agent}{?project,ttl_minutes,limit} |
listed | {project, agent, ttl_minutes, count, messages[]} |
Ack-required older than TTL without ack |
resources/read for resource://tooling/directory (and optionally resource://tooling/metrics) before exposing tools to an agent. Use the returned clusters and playbooks to render a narrow tool palette for the current workflow rather than dumping every verb into the UI.tool_metrics_snapshot events emitted by the server (or query resource://tooling/metrics) so you can detect high-error-rate tools and decide whether to expose macros or extra guidance.macro_start_session, macro_prepare_thread, macro_file_reservation_cycle, macro_contact_handshake) and hide the granular verbs until the agent explicitly asks for them.resource://tooling/recent/60?agent=<name>&project=<slug> (adjust window as needed) to display the last few successful tool invocations relevant to the agent/project.See examples/client_bootstrap.py for a runnable reference implementation that applies the guidance above.
{
"steps": [
"resources/read -> resource://tooling/directory",
"select active cluster (e.g. messaging)",
"mount tools listed in cluster.tools plus macros if model size <= S",
"optional: resources/read -> resource://tooling/metrics for dashboard display",
"optional: resources/read -> resource://tooling/recent/60?agent=<name>&project=<slug> for UI hints"
]
}
TOOL_METRICS_EMIT_ENABLED=true and choose an interval (TOOL_METRICS_EMIT_INTERVAL_SECONDS=120 is a good starting point). The server will periodically emit a structured log entry such as:{
"event": "tool_metrics_snapshot",
"tools": [
{"name": "send_message", "cluster": "messaging", "calls": 42, "errors": 1},
{"name": "file_reservation_paths", "cluster": "file reservations", "calls": 11, "errors": 0}
]
}
tools[] array.errors / calls exceeds a threshold for any tool (for example 5% over a 5-minute window) so you can decide whether to expose a macro or improve documentation.cluster to see where agents are spending time and which workflows might warrant additional macros or guard-rails.See docs/observability.md for a step-by-step cookbook (Loki/Prometheus example pipelines included), and docs/GUIDE_TO_OPTIMAL_MCP_SERVER_DESIGN.md for a comprehensive design guide covering tool curation, capability gating, security, and observability best practices.
Operations teams can follow docs/operations_alignment_checklist.md, which links to the capability templates in deploy/capabilities/ and the sample Prometheus alert rules in deploy/observability/.
uvicorn mcp_agent_mail.http:build_http_app --factory --host 0.0.0.0 --port 8765python -m mcp_agent_mail.http --host 0.0.0.0 --port 8765gunicorn -c deploy/gunicorn.conf.py mcp_agent_mail.http:build_http_app --factorydocker compose up --buildv0.1.0 builds and pushes a multi-arch Docker image to GHCR under ghcr.io/<owner>/<repo> with latest and version tags.If not using journald, a sample logrotate config is provided at deploy/logrotate/mcp-agent-mail to rotate /var/log/mcp-agent-mail/*.log weekly, keeping 7 rotations.
deploy/systemd/mcp-agent-mail.service) is configured to send logs to journald (StandardOutput/StandardError=journal)./var/log/mcp-agent-mail/*.log and install the provided logrotate config./etc/mcp-agent-mail.env (see deploy/systemd/mcp-agent-mail.service).Use Docker Buildx for multi-arch images. Example flow:
# Create and select a builder (once)
docker buildx create --use --name mcp-builder || docker buildx use mcp-builder
# Build and test locally (linux/amd64)
docker buildx build --load -t your-registry/mcp-agent-mail:dev .
# Multi-arch build and push (amd64, arm64)
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t your-registry/mcp-agent-mail:latest \
-t your-registry/mcp-agent-mail:v0.1.0 \
--push .
Recommended tags: a moving latest and immutable version tags per release. Ensure your registry login is configured (docker login).
/opt/mcp-agent-mail and ensure permissions (owner appuser)./etc/mcp-agent-mail.env based on deploy/env/production.env.deploy/systemd/mcp-agent-mail.service to /etc/systemd/system/.sudo systemctl daemon-reload
sudo systemctl enable mcp-agent-mail
sudo systemctl start mcp-agent-mail
sudo systemctl status mcp-agent-mail
Optional (non-journald log rotation): install deploy/logrotate/mcp-agent-mail into /etc/logrotate.d/ and write logs to /var/log/mcp-agent-mail/*.log via your process manager or app config.
See deploy/gunicorn.conf.py for a starter configuration. For project direction and planned areas, read project_idea_and_guide.md.
The project exposes a developer CLI for common operations:
serve-http: run the HTTP transport (Streamable HTTP only)migrate: ensure schema and FTS structures existlint / typecheck: developer helperslist-projects [--include-agents]: enumerate projectsguard install <project_key> <code_repo_path>: install the pre-commit guard into a repoguard uninstall <code_repo_path>: remove the guard from a reposhare wizard: launch interactive deployment wizard (auto-installs CLIs, authenticates, exports, deploys to GitHub Pages or Cloudflare Pages)share export --output <path> [options]: export mailbox to a static HTML bundle (see Static Mailbox Export section for full options)share update <bundle_path> [options]: refresh an existing bundle using recorded (or overridden) export settingsshare preview <bundle_path> [--port N] [--open-browser]: serve a static bundle locally for inspectionshare verify <bundle_path> [--public-key <key>]: verify bundle integrity (SRI hashes and Ed25519 signature)share decrypt <encrypted_path> [--identity <file> | --passphrase]: decrypt an age-encrypted bundleconfig set-port <port>: change the HTTP server port (updates .env)config show-port: display the current configured HTTP portclear-and-reset-everything [--force] [--archive/--no-archive]: DELETE the SQLite database (incl. WAL/SHM) and WIPE all contents under STORAGE_ROOT after optionally saving a restore point. Without flags it prompts to create an archive first; --force --no-archive skips all prompts for automation.list-acks --project <key> --agent <name> [--limit N]: list messages requiring acknowledgement for an agent where ack is missingacks pending <project> <agent> [--limit N]: show pending acknowledgements for an agentacks remind <project> <agent> [--min-age-minutes N] [--limit N]: highlight pending ACKs older than a thresholdacks overdue <project> <agent> [--ttl-minutes N] [--limit N]: list overdue ACKs beyond TTLfile_reservations list <project> [--active-only/--no-active-only]: list file reservationsfile_reservations active <project> [--limit N]: list active file reservationsfile_reservations soon <project> [--minutes N]: show file reservations expiring soondoctor check [PROJECT] [--verbose] [--json]: run comprehensive diagnostics on mailbox healthdoctor repair [PROJECT] [--dry-run] [--yes] [--backup-dir PATH]: semi-automatic repair with backup before changesdoctor backups [--json]: list available diagnostic backupsdoctor restore <backup_path> [--dry-run] [--yes]: restore from a diagnostic backupExamples:
# Interactive wizard: export + deploy to GitHub Pages (easiest)
./scripts/share_to_github_pages.py
# Export a static bundle with signing and encryption
uv run python -m mcp_agent_mail.cli share export \
--output ./bundle \
--signing-key ./keys/signing.key \
--age-recipient age1abc...xyz
# Preview a bundle locally
uv run python -m mcp_agent_mail.cli share preview ./bundle --port 9000 --open-browser
# Verify bundle integrity
uv run python -m mcp_agent_mail.cli share verify ./bundle
# Refresh an existing bundle in place with recorded settings
uv run python -m mcp_agent_mail.cli share update ./bundle
# Change server port
uv run python -m mcp_agent_mail.cli config set-port 9000
# Install guard into a repo
uv run python -m mcp_agent_mail.cli guard install /abs/path/backend /abs/path/backend
# List pending acknowledgements for an agent
uv run python -m mcp_agent_mail.cli acks pending /abs/path/backend BlueLake --limit 10
# Run mailbox health diagnostics
uv run python -m mcp_agent_mail.cli doctor check
# Preview repairs without making changes
uv run python -m mcp_agent_mail.cli doctor repair --dry-run
# Run repairs (creates backup first, prompts for data changes)
uv run python -m mcp_agent_mail.cli doctor repair
# List available backups
uv run python -m mcp_agent_mail.cli doctor backups
# Restore from a backup
uv run python -m mcp_agent_mail.cli doctor restore /path/to/backup --dry-run
# WARNING: Destructive reset (clean slate)
uv run python -m mcp_agent_mail.cli clear-and-reset-everything --force
Use the automated installer to wire up supported tools automatically (e.g., Claude Code, Cline, Windsurf, OpenCode). Run scripts/automatically_detect_all_installed_coding_agents_and_install_mcp_agent_mail_in_all.sh or the one-liner in the Quickstart above.
For manual integration or customization, dedicated scripts are available:
| Tool | Script | What it configures |
|---|---|---|
| Claude Code | scripts/integrate_claude_code.sh |
.claude/settings.json, hooks, MCP server |
| Codex CLI | scripts/integrate_codex_cli.sh |
~/.codex/config.toml, MCP server, notify handler |
| Gemini CLI | scripts/integrate_gemini_cli.sh |
~/.gemini/settings.json, MCP server, hooks |
Each script:
Agents often get absorbed in their work and forget to check their mail. The integration scripts install lightweight hooks that periodically remind agents when they have unread messages.
How it works:
scripts/hooks/check_inbox.sh) runs after certain tool invocationsClaude Code / Gemini CLI:
The hook is configured as a PostToolUse hook that fires after Bash or shell tool invocations:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "...check_inbox.sh" }]
}
]
}
}
Codex CLI:
Uses the top-level notify configuration in config.toml (must appear before any [section] headers) to fire on agent-turn-complete events:
notify = ["/path/to/.codex/hooks/notify_wrapper.sh"]
Additional hooks (Claude Code only):
| Event | What it does |
|---|---|
SessionStart |
Shows active file reservations and pending acknowledgments |
PreToolUse (Edit) |
Warns about file reservations expiring within 10 minutes |
PostToolUse (send_message) |
Lists recent acknowledgment requests |
PostToolUse (file_reservation_paths) |
Shows current file reservations |
The inbox check hooks accept these environment variables (set automatically by the integration scripts):
| Variable | Description | Default |
|---|---|---|
AGENT_MAIL_PROJECT |
Project key (absolute path) | required |
AGENT_MAIL_AGENT |
Agent name | required |
AGENT_MAIL_URL |
Server URL | http://127.0.0.1:8765/mcp/ |
AGENT_MAIL_TOKEN |
Bearer token | none |
AGENT_MAIL_INTERVAL |
Seconds between checks | 120 |
About Contributions: Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via
ghand independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"mcp-agent-mail": {
"command": "npx",
"args": []
}
}
}