https://github.com/dicklesworthstone/mcp_agent_mail
Asynchronous coordination layer for AI coding agents: identities, inboxes, searchable threads, and advisory file leases over FastMCP + Git + SQLite
https://github.com/dicklesworthstone/mcp_agent_mail
ai-agents coordination fastmcp mcp python
Last synced: about 2 months ago
JSON representation
Asynchronous coordination layer for AI coding agents: identities, inboxes, searchable threads, and advisory file leases over FastMCP + Git + SQLite
- Host: GitHub
- URL: https://github.com/dicklesworthstone/mcp_agent_mail
- Owner: Dicklesworthstone
- License: other
- Created: 2025-10-23T03:42:36.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2026-04-14T17:56:54.000Z (about 2 months ago)
- Last Synced: 2026-04-14T19:29:18.906Z (about 2 months ago)
- Topics: ai-agents, coordination, fastmcp, mcp, python
- Language: Python
- Size: 10.1 MB
- Stars: 1,880
- Watchers: 17
- Forks: 196
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# MCP Agent Mail

> "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).
## Why this exists
Modern projects often run multiple coding agents at once (backend, frontend, scripts, infra). Without a shared coordination fabric, agents:
- Overwrite each other's edits or panic on unexpected diffs
- Miss critical context from parallel workstreams
- Require humans to "liaison" messages across tools and teams
This project provides a lightweight, interoperable layer so agents can:
- Register a temporary-but-persistent identity (e.g., GreenCastle)
- Send/receive GitHub-Flavored Markdown messages with images
- Search, summarize, and thread conversations
- Declare advisory file reservations (leases) on files/globs to signal intent
- Inspect a directory of active agents, programs/models, and activity
It's designed for: FastMCP clients and CLI tools (Claude Code, Codex, Gemini CLI, Factory Droid, etc.) coordinating across one or more codebases.
## From Idea Spark to Shipping Swarm
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”):
- **Ideate fast:** Write a scrappy email-style blurb about the problem, desired UX, and any must-have stack picks (≈15 minutes).
- **Promote it to a plan:** Feed that blurb to GPT-5 Pro (and optionally Grok4 Heavy / Opus 4.1) until you get a granular Markdown plan, then iterate on the plan file while it’s still cheap to change. The Markdown Web Browser sample plan shows the level of detail to aim for.
- **Codify the rules:** Clone a tuned `AGENTS.md`, add any tech-specific best-practice guides, and let Codex scaffold the repo plus Beads tasks straight from the plan.
- **Spin up the swarm:** Launch multiple Codex panes (or any agent mix), register each identity with Agent Mail, and have them acknowledge `AGENTS.md`, the plan document, and the Beads backlog before touching code.
- **Keep everyone fed:** Reuse the canned instruction cadence from the tweet thread or, better yet, let the commercial Companion app’s Message Stacks broadcast those prompts automatically so you never hand-feed panes again.
Watch the full 23-minute walkthrough (https://youtu.be/68VVcqMEDrs?si=pCm6AiJAndtZ6u7q) to see the loop in action.
## Productivity Math & Automation Loop
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:
1. **Base OSS server:** Git-backed mailboxes, advisory file reservations, Typer CLI helpers, and searchable archives keep independent agents aligned without babysitting. Every instruction, lease, and attachment is auditable.
2. **Companion stack (commercial):** The iOS app + host automation can provision, pair, and steer heterogeneous fleets (Claude Code, Codex, Gemini CLI, Factory Droid, etc.) from your phone using customizable Message Stacks, Human Overseer broadcasts, Beads awareness, and plan editing tools—no manual tmux choreography required. The automation closes the loop by scheduling prompts, honoring Limited Mode, and enforcing Double-Arm confirmations for destructive work.
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.
## TLDR Quickstart
### One-line installer
```bash
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/mcp_agent_mail/main/scripts/install.sh?$(date +%s)" | bash -s -- --yes
```
What this does:
- Installs uv if missing and updates your PATH for this session
- Installs jq if missing (needed for safe config merging; auto-detects your package manager)
- Creates a Python 3.14 virtual environment and installs dependencies with uv
- Runs the auto-detect integration to wire up supported agent tools
- Starts the MCP HTTP server on port 8765 and prints a masked bearer token
- Creates helper scripts under `scripts/` (including `run_server_with_token.sh`)
- Adds an `am` shell alias to your `.zshrc` or `.bashrc` for quick server startup (just type `am` in a new terminal!)
- Installs **Beads Rust (`br`)**, a Rust reimplementation of the Beads task tracker, and creates a `bd` shell alias pointing to `br` for backwards compatibility. This replaces any existing `bd` (Go) installation. Pass `--skip-beads` to opt out. See [beads_rust](https://github.com/Dicklesworthstone/beads_rust) for details on CLI differences.
- Installs/updates the Beads Viewer `bv` TUI for interactive task browsing and AI-friendly robot commands (pass `--skip-bv` to opt out)
- Prints a short on-exit summary of each setup step so you immediately know what changed
Prefer a specific location or options? Add flags like `--dir `, `--project-dir `, `--no-start`, `--start-only`, `--port `, or `--token `.
Already have Beads or Beads Viewer installed? Append `--skip-beads` and/or `--skip-bv` to bypass automatic installation.
### Important: Beads Rust (br) Replaces Beads Go (bd)
The installer automatically replaces `bd` (the original Go-based Beads CLI) with `br` (Beads Rust):
1. **`br` is the actively maintained version.** Beads Rust is a complete reimplementation with ongoing development, while the original Go version is no longer actively maintained.
2. **Existing `bd` users get automatic aliasing.** The installer creates a shell alias so that `bd` commands continue to work by redirecting to `br`. Your existing workflows and muscle memory are preserved.
3. **A migration skill is installed for agents.** AI coding agents receive a `bd-br-migration` skill that helps them adapt to any CLI differences between the two implementations.
4. **Same data format, compatible workflows.** Both implementations use the same `.beads/issues.jsonl` format, so your existing Beads data remains fully compatible.
To opt out of this replacement, pass `--skip-beads` to the installer. See the [beads_rust repository](https://github.com/Dicklesworthstone/beads_rust) for detailed documentation on CLI differences.
### Starting the server in the future
After installation, you can start the MCP Agent Mail server from **anywhere** by simply typing:
```bash
am
```
That's it! The `am` alias (added to your `.zshrc` or `.bashrc` during installation) automatically:
1. Changes to the MCP Agent Mail directory
2. Runs the server startup script (which uses `uv run` to handle the virtual environment)
3. Loads your saved bearer token from `.env` and starts the HTTP server
**Note:** 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:
```bash
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
```
## Ready-Made Blurb to Add to Your AGENTS.md or CLAUDE.md Files:
```
## 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=&limit=20&agent_token=` or `resource://thread/{id}?project=&agent=&agent_token=&include_bodies=true` unless the current MCP session already authenticated as that agent.
- 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.
```
## Integrating with Beads (dependency-aware task planning)
Beads is a lightweight task planner that complements Agent Mail by keeping status and dependencies in one place while Mail handles messaging, file reservations, and audit trails.
**Note on implementations:** The MCP Agent Mail installer installs [Beads Rust (`br`)](https://github.com/Dicklesworthstone/beads_rust), a Rust reimplementation, and creates a `bd` alias for backwards compatibility. The original Go implementation is at [steveyegge/beads](https://github.com/steveyegge/beads). Both share the same data format (`.beads/issues.jsonl`) but have some CLI differences. Use `--skip-beads` during installation if you prefer to manage this yourself.
Highlights:
- Beads owns task prioritization; Agent Mail carries the conversations and artifacts.
- Shared identifiers (e.g., `bd-123`) keep Beads issues, Mail threads, and commits aligned.
- The `br` CLI (aliased as `bd`) provides similar functionality to the original with some enhancements.
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: ", 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.
## Beads Viewer (bv) — AI-Friendly Task Analysis
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](https://github.com/Dicklesworthstone/beads_viewer)
### Why bv for Agents?
While `bd` (Beads CLI) handles task CRUD operations, `bv` provides **precomputed graph analytics** that help agents make intelligent prioritization decisions:
- **PageRank scores**: Identify high-impact tasks that unblock the most downstream work
- **Critical path analysis**: Find the longest dependency chain to completion
- **Cycle detection**: Spot circular dependencies before they cause deadlocks
- **Parallel track planning**: Determine which tasks can run concurrently
Instead of agents parsing `.beads/issues.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. Legacy `.beads/beads.jsonl` is deprecated in this repo.
### Robot Flags for AI Integration
| 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 ` | Changes since commit/date, new/closed items, cycles | Progress tracking: "What changed?" |
### Example: Agent Task Selection Workflow
```bash
# 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
```
### When to Use bv vs bd
| 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.
### Integration with Agent Mail
Combine bv insights with Agent Mail coordination:
1. **Agent A** runs `bv --robot-priority` → identifies `bd-42` as highest-impact
2. **Agent A** reserves files: `file_reservation_paths(..., reason="bd-42")`
3. **Agent A** announces: `send_message(..., thread_id="bd-42", subject="[bd-42] Starting high-impact refactor")`
4. **Other agents** see the reservation and Mail announcement, pick different tasks
5. **Agent A** completes, runs `bv --robot-diff` to report downstream unblocks
This creates a feedback loop where graph intelligence drives coordination.
## Core ideas (at a glance)
- HTTP-only FastMCP server (Streamable HTTP). No SSE, no STDIO.
- Dual persistence model:
- Human-readable markdown in a per-project Git repo for every canonical message and per-recipient inbox/outbox copy
- SQLite with FTS5 for fast search, directory queries, and file reservations/leases
- "Directory/LDAP" style queries for agents; memorable adjective+noun names
- Advisory file reservations for editing surfaces; optional pre-commit guard
- Resource layer for convenient reads (e.g., `resource://inbox/{agent}`)
## Typical use cases
- Multiple agents splitting a large refactor across services while staying in sync
- Frontend and backend teams of agents coordinating thread-by-thread
- Protecting critical migrations with exclusive file reservations and a pre-commit guard
- Searching and summarizing long technical discussions as threads evolve
- Discovering and linking related projects (e.g., frontend/backend) through AI-powered suggestions
## Workflow FAQ
**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.
## Architecture
```mermaid
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
```
## Web UI (human-facing mail viewer)
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).
- Where it lives: built into the HTTP server in `mcp_agent_mail.http` under the `/mail` path.
- Who it's for: humans reviewing activity; agents should continue to use the MCP tools/resources API.
### Launching the Web UI
Recommended (simple):
```bash
scripts/run_server_with_token.sh
# then open http://127.0.0.1:8765/mail
```
Advanced (manual commands):
```bash
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:
- GET pages in the UI are not gated by the RBAC middleware (it classifies POSTed MCP calls only), but if you set a bearer token the separate BearerAuth middleware protects all routes by default.
- For local dev, set `HTTP_ALLOW_LOCALHOST_UNAUTHENTICATED=true` (and optionally `HTTP_BEARER_TOKEN`), so localhost can load the UI without headers.
- Health endpoints are always open at `/health/*`.
### Routes and what you can do
- `/mail` (Unified inbox + Projects + Related Projects Discovery)
- Shows a unified, reverse-chronological inbox of recent messages across all projects with excerpts, relative timestamps, sender/recipients, and project badges.
- Below the inbox, lists all projects (slug, human name, created time) with sibling suggestions.
- Suggests **likely sibling projects** when two slugs appear to be parts of the same product (e.g., backend vs. frontend). Suggestions are ranked with heuristics and, when `LLM_ENABLED=true`, an LLM pass across key docs (`README.md`, `AGENTS.md`, etc.).
- Humans can **Confirm Link** or **Dismiss** suggestions from the dashboard. Confirmed siblings become highlighted badges but *do not* automatically authorize cross-project messaging; agents must still establish `AgentLink` approvals via `request_contact`/`respond_contact`.
- `/mail/projects` (Projects index)
- Dedicated projects list view; click a project to drill in.
- `/mail/{project}` (Project overview + search + agents)
- Rich search form with filters:
- Scope: subject/body/both, Order: relevance or time, optional "boost subject".
- Query tokens: supports `subject:foo`, `body:"multi word"`, quoted phrases, and bare terms.
- Uses FTS5 bm25 scoring when available; otherwise falls back to SQL LIKE on subject/body with your chosen scope.
- Results show subject, sender, created time, thread id, and a highlighted snippet when using FTS.
- Agents panel shows registered agents for the project with a link to each inbox.
- Quick links to File Reservations and Attachments for the project header.
- `/mail/{project}/inbox/{agent}` (Inbox for one agent)
- Reverse-chronological list with subject, sender, created time, importance badge, thread id.
- Pagination (`?page=N&limit=M`).
- `/mail/{project}/message/{id}` (Message detail)
- Shows subject, sender, created time, importance, recipients (To/Cc/Bcc), thread messages.
- Body rendering:
- If the server pre-converted markdown to HTML, it's sanitized with Bleach (limited tags/attributes, safe CSS via CSSSanitizer) and then displayed.
- Otherwise markdown is rendered client-side with Marked + Prism for code highlighting.
- Attachments are referenced from the message frontmatter (WebP files or inline data URIs).
- `/mail/{project}/search?q=...` (Dedicated search page)
- Same query syntax as the project overview search, with a token "pill" UI for assembling/removing filters.
- `/mail/{project}/file_reservations` (File Reservations list)
- Displays active and historical file reservations (exclusive/shared, path pattern, timestamps, released/expired state).
- `/mail/{project}/attachments` (Messages with attachments)
- Lists messages that contain any attachments, with subject and created time.
- `/mail/unified-inbox` (Cross-project activity)
- Shows recent messages across all projects with thread counts and sender/recipients.
### Human Overseer: Sending Messages to Agents
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`.
#### What Makes Overseer Messages Special
1. **Automatic Preamble**: Every message includes a formatted preamble that clearly identifies it as coming from a human operator and instructs agents to:
- **Pause current work** temporarily
- **Prioritize the human's request** over existing tasks
- **Resume original plans** afterward (unless modified by the instructions)
2. **High Priority**: All overseer messages are automatically marked as **high importance**, ensuring they stand out in agent inboxes.
3. **Policy Bypass**: Overseer messages bypass normal contact policies, so humans can always reach any agent regardless of their contact settings.
4. **Special Sender Identity**: Messages come from a special agent named **"HumanOverseer"** (automatically created per project) with:
- Program: `WebUI`
- Model: `Human`
- Contact Policy: `open`
#### The Message Preamble
Every 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]
```
#### Using the Composer
The composer interface provides:
- **Recipient Selection**: Checkbox grid of all registered agents (with "Select All" / "Clear" shortcuts)
- **Subject Line**: Required, shown in agent inboxes
- **Message Body**: GitHub-flavored Markdown editor with preview
- **Thread ID** (optional): Continue an existing conversation or start a new one
- **Preamble Preview**: See exactly how your message will appear to agents
#### Example Use Cases
**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.
```
#### How Agents See Overseer Messages
When agents check their inbox (via `fetch_inbox` or `resource://inbox/{name}`), overseer messages appear like any other message but with:
- **Sender**: `HumanOverseer`
- **Importance**: `high` (displayed prominently)
- **Body**: Starts with the overseer preamble, followed by the human's message
- **Visual cues**: In the Web UI, these messages may have special highlighting (future enhancement)
Agents can reply to overseer messages just like any other message, continuing the conversation thread.
#### Technical Details
- **Storage**: Overseer messages are stored identically to agent-to-agent messages (Git + SQLite)
- **Git History**: Fully auditable; message appears in `messages/YYYY/MM/{id}.md` with commit history
- **Thread Continuity**: Can be part of existing threads or start new ones
- **No Authentication Bypass**: The overseer compose form still requires proper HTTP server authentication (if enabled)
#### Design Philosophy
The Human Overseer feature is designed to be:
- **Explicit**: Agents clearly know when guidance comes from a human vs. another agent
- **Respectful**: Instructions acknowledge agents have existing work and shouldn't just "drop everything" permanently
- **Temporary**: Agents are told to resume original plans once the human's request is complete
- **Flexible**: Humans can override this guidance directly in their message body
This creates a clear hierarchy (human → agents) while maintaining the collaborative, respectful tone of the agent communication system.
### Related Projects Discovery
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.
#### How Discovery Works
**1. Smart Analysis**
The system uses multiple signals to identify relationships:
- **Pattern matching**: Compares project names and paths (e.g., "my-app-frontend" ↔ "my-app-backend")
- **AI understanding** (when `LLM_ENABLED=true`): Reads `README.md`, `AGENTS.md`, and other docs to understand each project's purpose and detect natural relationships
- **Confidence scoring**: Ranks suggestions from 0-100% with clear rationales
**2. Beautiful Suggestions**
Related projects appear as polished cards on your dashboard with:
- 🎯 Visual confidence indicators showing match strength
- 💬 AI-generated rationales explaining the relationship
- ✅ **Confirm Link** - accept the suggestion
- ✖️ **Dismiss** - hide irrelevant matches
**3. Quick Navigation**
Once confirmed, both projects display interactive badges for instant navigation between related codebases.
#### Why Suggestions, Not Auto-Linking?
> **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:
- **Security**: No accidental cross-project message delivery
- **Transparency**: You always know who can talk to whom
- **Audit trails**: All communication paths are explicitly approved
**Why not auto-link with AI?**
If we let an LLM automatically authorize messaging between projects, we'd be:
- ❌ Bypassing contact policies without human oversight
- ❌ Risking message misdelivery to unintended recipients
- ❌ Creating invisible routing paths that are hard to audit
- ❌ Potentially linking ambiguously-named projects incorrectly
Instead, we give you **discovery + control**:
- ✅ AI suggests likely relationships (safe, read-only analysis)
- ✅ You confirm what makes sense (one click)
- ✅ Agents still use `request_contact` / `respond_contact` for actual messaging permissions
- ✅ Clear separation: discovery ≠ authorization
#### The Complete Workflow
```
1. 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.
### Search syntax (UI)
The UI shares the same parsing as the API's `_parse_fts_query`:
- Field filters: `subject:login`, `body:"api key"`
- Phrase search: `"build plan"`
- Combine terms: `login AND security` (FTS)
- Fallback LIKE: scope determines whether subject, body, or both are searched
### Prerequisites to see data
The UI reads from the same SQLite + Git artifacts as the MCP tools. To populate content:
1) Ensure a project exists (via tool call or CLI):
- Ensure/create project: `ensure_project(human_key)`
2) Register one or more agents: `register_agent(project_key, program, model, name?)`
3) Send messages: `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.
### Implementation and dependencies
- Templates live in `src/mcp_agent_mail/templates/` and are rendered by Jinja2.
- Markdown is converted with `markdown2` on the server where possible; HTML is sanitized with Bleach (with CSS sanitizer when available).
- Tailwind CSS, Lucide icons, Alpine.js, Marked, and Prism are loaded via CDN in `base.html` for a modern look without a frontend build step.
- All rendering is server-side; there's no SPA router. Pages degrade cleanly without JavaScript.
### Security considerations
- HTML sanitization: Only a conservative set of tags/attributes are allowed; CSS is filtered. Links are limited to http/https/mailto/data.
- Auth: Use bearer token or JWT when exposing beyond localhost. For local dev, enable localhost bypass as noted above.
- Rate limiting (optional): Token-bucket limiter can be enabled; UI GET requests are light and unaffected by POST limits.
### Troubleshooting the UI
- Blank page or 401 on localhost: Either unset `HTTP_BEARER_TOKEN` or set `HTTP_ALLOW_LOCALHOST_UNAUTHENTICATED=true`.
- No projects listed: Create one with `ensure_project`.
- Empty inbox: Verify recipient names match exactly and messages were sent to that agent.
- Search returns nothing: Try simpler terms or the LIKE fallback (toggle scope/body).
## Static Mailbox Export (Share & Distribute Archives)
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.
### Why export to static bundles?
**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.
### What's included in an export
Each bundle contains:
- **Self-contained**: Everything ships in a single directory (HTML, CSS/JS, SQLite snapshot, attachments). Drop it on a static host or open it locally.
- **Rich reader UI**: Gmail-style inbox with project filters, search, and full-thread rendering—each message is shown with its metadata and Markdown body, just like in the live web UI.
- **Fast search & filters**: FTS-backed search and precomputed per-message summaries keep scrolling and filtering responsive even with large archives.
- **Verifiable integrity**: SHA-256 hashes for every asset plus optional Ed25519 signing make authenticity and tampering checks straightforward.
- **Chunk-friendly archives**: Large databases can be chunked for httpvfs streaming; a companion `chunks.sha256` file lists digests for each chunk so clients can trust streamed blobs without recomputing hashes.
- **One-click hosting**: The interactive wizard can publish straight to GitHub Pages or Cloudflare Pages, or you can serve the bundle locally with the CLI preview command.
## Disaster Recovery Archives (`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:
- A SQLite snapshot processed by the same cleanup pipeline as `share`, but using the `archive` scrub preset so ack/read state, recipients, attachments, and message bodies remain untouched.
- A byte-for-byte copy of the storage Git repo (`STORAGE_ROOT`), preserving markdown artifacts, attachments, and hook scripts.
### Quick ref
```bash
# 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/.zip --force
```
During restore the CLI:
1. Extracts the ZIP into a temp directory.
2. Moves any existing `storage.sqlite3`, WAL/SHM siblings, and `STORAGE_ROOT` into timestamped `.backup-` folders so nothing is lost.
3. Copies the snapshot back to the configured database path and rebuilds the storage repo from the archive contents.
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.
### Reset safety net
`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.
## Mailbox Health: `am doctor`
The `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.
### Why `am doctor`?
Over time, mailbox state can drift:
- **Stale locks**: Process crashes leave behind `.archive.lock` or `.commit.lock` files that block operations
- **Orphaned records**: Agents get deleted but their message recipients remain in the database
- **FTS index desync**: Full-text search index falls out of sync with actual messages
- **Expired file reservations**: Reservations expire but aren't cleaned up
- **WAL files**: SQLite WAL/SHM files accumulate (normal during operation, but worth monitoring)
The doctor commands detect these issues and offer safe, automatic repair.
### Diagnostic checks
Run comprehensive diagnostics on your mailbox:
```bash
# 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!
```
### Semi-automatic repair
The repair command uses a **semi-automatic approach**:
- **Safe repairs** (locks, expired reservations) are applied automatically
- **Data-affecting repairs** (orphan cleanup) require confirmation
- A **backup is created before any changes**
```bash
# 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:**
1. **Create backup** — Git bundle + SQLite copy created before any changes
2. **Safe repairs** (auto-applied):
- Heal stale locks (removes orphaned `.archive.lock`, `.commit.lock` files)
- Release expired file reservations (marks `released_ts` in database)
3. **Data repairs** (require confirmation):
- Delete orphaned message recipients
- Rebuild FTS index (if needed)
### Backup management
Doctor creates timestamped backups before repairs. You can also manage backups directly:
```bash
# 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 copy
- `database.sqlite3-wal`, `database.sqlite3-shm` — WAL files if present
- `archive.bundle` or `{project}.bundle` — Git bundle of the archive repository
- `manifest.json` — Metadata: when created, why, what's included, restore instructions
**Directory structure:**
```
{storage_root}/backups/
2026-01-06T12-30-45_doctor-repair/
manifest.json
database.sqlite3
archive.bundle
```
### Restore from backup
If something goes wrong, restore from any backup:
```bash
# 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:**
1. Validates backup manifest exists and is readable
2. Shows backup metadata (creation time, reason, contents)
3. **Creates a pre-restore backup** of current state (safety net)
4. Restores SQLite database from backup
5. Restores Git archive from bundle
6. Reports any errors encountered
**Safety features:**
- Current database saved as `*.sqlite3.pre-restore` before overwrite
- Current archive saved as `*.pre-restore` directory before overwrite
- Errors during restore are captured and reported
### Best practices
1. **Run diagnostics regularly**: `am doctor check` is fast and non-destructive
2. **Review before repair**: Use `--dry-run` first to see what would change
3. **Keep backups**: Don't delete old backups until you've verified the system is healthy
4. **Automate checks**: Include `am doctor check --json` in your CI/monitoring for early warning
### Quick Start: Interactive Deployment Wizard
The easiest way to export and deploy is the interactive wizard, which supports both GitHub Pages and Cloudflare Pages:
```bash
# Via CLI (recommended)
uv run python -m mcp_agent_mail.cli share wizard
# Or run the script directly
./scripts/share_to_github_pages.py
```
#### What the wizard does
The wizard provides a fully automated end-to-end deployment experience:
1. **Session resumption**: Detects interrupted sessions and offers to resume exactly where you left off, avoiding re-export
2. **Configuration management**: Remembers your last settings and offers to reuse them, saving time on repeated exports
3. **Deployment target selection**: Choose between GitHub Pages, Cloudflare Pages, or local export
4. **Automatic CLI installation**: Detects and installs missing tools (`gh` for GitHub, `wrangler` for Cloudflare)
5. **Guided authentication**: Step-by-step browser login flows for GitHub and Cloudflare
6. **Smart project selection**:
- Shows all available projects in a formatted table
- Supports multiple selection modes: `all`, single number (`1`), lists (`1,3,5`), or ranges (`1-3`, `2-5,8`)
- Remembers your previous selection for quick re-export
7. **Redaction configuration**: Choose between `standard` (scrub secrets like API keys/tokens, keep agent names) or `strict` (redact all message bodies)
8. **Cryptographic signing**: Optional Ed25519 signing with automatic key generation or reuse of existing keys
9. **Pre-flight validation**: Checks that GitHub repo names are available before starting the export
10. **Deployment summary**: Shows what will be deployed (project count, bundle size, target, signing status) and asks for confirmation
11. **Export and preview**: Exports the bundle and launches an interactive preview server with automatic port detection (tries 9000-9100)
12. **Interactive preview controls**:
- Press **'r'** to force browser refresh (manual cache bust)
- Press **'d'** to skip preview and deploy immediately
- Press **'q'** to quit preview server
13. **Automatic viewer asset refresh**: Always ensures latest HTML/JS/CSS from source tree are used, even when reusing bundles
14. **Real-time deployment**: Streams git and deployment output in real-time so you can follow the progress
15. **Automatic deployment**: Creates repos, enables Pages, pushes code, and gives you the live URL
#### Session resumption (new in latest version)
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:**
- Selected projects and scrub preset
- Deployment configuration (target, repo name, etc.)
- Signing key preferences and paths
- Exported bundle (in session workspace)
- Current stage (preview, deploy)
**Resume scenarios:**
- **Closed terminal during preview**: Resume → Skip re-export → Launch preview immediately
- **Changed your mind after export**: Resume → "Reuse bundle?" → Preview or re-export
- **Want to deploy later**: Resume → Press 'd' in preview → Deploy without re-exporting
- **Made viewer code changes**: Resume → Assets auto-refresh from source tree
After successful deployment, the session state is automatically cleared. Sessions also clear if they become invalid (workspace deleted, projects removed, etc.).
#### Configuration persistence
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:
- Selected project indices (validates against current project list)
- Redaction preset
- Deployment target and parameters (repo name, privacy, project name)
- Signing preferences (whether to sign, whether to generate new key)
- Last used signing key path (offered as default when not generating new key)
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:**
- **Session state** (`wizard-session/`): Temporary, for resuming interrupted runs, includes exported bundle
- **Config file** (`wizard-config.json`): Persistent, for "use last settings" across fresh runs, no bundle
#### Multi-project selection
The 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 only
- `1,3,5`: Export projects #1, #3, and #5
- `1-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.
#### Dynamic port allocation
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.
#### Deployment summary panel
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.
#### Real-time deployment streaming
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.
#### Platform-specific details
**For GitHub Pages:**
- Wizard detects your package manager (brew/apt/dnf) and offers automated installation of `gh` CLI
- For apt/dnf, shows complete manual installation instructions (including repo setup) since automation requires sudo
- Runs `gh auth login` interactively to authenticate via browser
- Creates new repository with your specified name and visibility (public/private)
- Initializes git, commits, and pushes with streaming output
- Enables GitHub Pages automatically via the GitHub API
- Provides the GitHub Pages URL (may take 1-2 minutes to become live)
**For Cloudflare Pages:**
- Detects npm and offers automated installation of `wrangler` CLI
- Runs `wrangler login` interactively to authenticate via browser
- Deploys directly to Cloudflare's global CDN (no git repository needed)
- Streams wrangler output in real-time
- Provides the `.pages.dev` URL immediately (site is live instantly)
- Benefits: instant deployment, 275+ global locations, automatic HTTPS, unlimited requests on free tier
**For local export:**
- Saves bundle to specified directory
- No CLI installation or authentication required
- Suitable for manual deployment to custom hosting or inspection
#### Error handling and recovery
The wizard includes comprehensive error handling:
- **Pre-flight validation**: Checks GitHub repo availability before starting export to avoid conflicts
- **Port conflict resolution**: Automatically finds an available port for preview server
- **Invalid selection handling**: Validates project selections and prompts for correction
- **CLI installation failures**: Shows manual installation instructions if automatic installation fails
- **Git operation failures**: Each git step is validated; stops on first failure with clear error message
- **Deployment failures**: Distinguishes between repo creation, push, and Pages enablement failures
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.
### Basic export workflow (manual)
**1. Export a bundle**
```bash
# 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:
1. Creates a snapshot of the SQLite database (read-only, no WAL/SHM files)
2. Copies message bodies, attachments, and metadata into the bundle structure
3. Applies redaction rules based on the scrub preset (default: `standard`)
4. Generates `manifest.json` with SHA-256 hashes for all assets
5. Optionally signs the manifest with Ed25519 (produces `manifest.sig.json`)
6. Packages everything into a ZIP archive (optional, enabled by default)
7. If chunking is enabled, writes the segmented database plus a `chunks.sha256` manifest so streamed pages can be verified cheaply
8. Optionally encrypts the ZIP with age (produces `bundle.zip.age`)
### Refresh an existing bundle
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.
```bash
# 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**
```bash
# 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:**
- **'r'**: Force browser reload (bumps manual cache-bust token, triggers viewer refresh)
- **'d'**: Request deployment (exits with code 42; wizard detects and proceeds to deploy)
- **'q'**: Quit preview server
- **Ctrl+C**: Stop preview server
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**
```bash
# 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:
- SHA-256 hashes for all vendor libraries (Marked.js, DOMPurify, SQL.js)
- SHA-256 hashes for the SQLite database and attachments
- Ed25519 signature over the canonical manifest (if present)
**4. Decrypt (if encrypted)**
```bash
# 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.
### Export options reference
| 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) |
### Security features
**XSS protection (DOMPurify + CSP)**
Message bodies are rendered using a defense-in-depth pipeline:
1. **Marked.js** parses GitHub-Flavored Markdown into HTML
2. **DOMPurify** sanitizes the HTML, removing dangerous tags and attributes
3. **Content Security Policy** restricts script sources, blocks inline event handlers, and limits network access
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 scenarios
- `style-src`: Allows self, inline styles (for Tailwind), and font CDNs
- Trusted Types removed for browser compatibility (Firefox, Safari don't support it yet)
**Cryptographic signing (Ed25519)**
When you provide a signing key, the export process:
1. Generates a canonical JSON representation of `manifest.json`
2. Signs it with Ed25519 (fast, 64-byte signatures, 128-bit security)
3. Writes the signature and public key to `manifest.sig.json`
Recipients can verify the signature using `share verify` to ensure:
- The bundle hasn't been modified since signing
- The bundle was created by someone with the private key
- All assets match their declared SHA-256 hashes
**Requirements and fallback:**
- Requires PyNaCl >= 1.6.0 (installed automatically with this package)
- If PyNaCl is unavailable or signing fails, export gracefully falls back to unsigned mode
- Wizard reuses existing signing keys by default (no re-generation unless requested)
- Private keys are automatically excluded from git via `.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:
```bash
# 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.
### Static viewer features
The bundled HTML viewer provides:
**Dashboard layout**:
- **Gmail-style three-pane interface**: Projects sidebar, message list (center), and detail pane (right)
- **Bundle metadata header**: Shows bundle creation time, export settings, and scrubbing preset
- **Summary panels**: Side-by-side panels displaying projects included, attachment statistics, and redaction summary
- **Message list**: Virtual-scrolled message list with sender, subject, snippet, and importance badges
- **Raw manifest viewer**: Collapsible JSON display of the complete manifest for verification
**Advanced boolean search** (new): Powered by SQLite FTS5 with LIKE fallback, supports complex queries:
- **Boolean operators**: `(auth OR login) AND NOT admin`
- **Quoted phrases**: `"build plan"` (exact match)
- **Parentheses**: Control precedence like `(A OR B) AND (C OR D)`
- **Operator precedence**: NOT > AND > OR (e.g., `A OR B AND C` = `A OR (B AND C)`)
- **Automatic debouncing**: 140ms delay avoids hammering the database on every keystroke
- **Performance**: FTS5 search is 10-100x faster than LIKE on large datasets
**Lazy message loading** (performance optimization):
- Initial load fetches only 280-character snippets for all messages (3-6x faster)
- Full message body loaded on-demand when you click a message
- Dramatically reduces memory usage and initial load time
**Virtual scrolling** (new): Clusterize.js-powered virtual list rendering:
- Smoothly handles 100,000+ messages without slowdown
- Only ~30 DOM nodes exist at any time (visible rows + buffers)
- Maintains native scrollbar feel with keyboard navigation
**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:
- First load: Downloads from network, caches to OPFS during idle time
- Subsequent loads: Instant from OPFS (even faster than IndexedDB)
- Automatic cache key validation prevents stale data
**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).
### Deployment options
**Option 1: GitHub Pages (automated via wizard)**
```bash
# 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:
```bash
# 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 git@github.com: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)**
```bash
# 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:
```bash
# 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:**
- Instant deployment (no git repo required)
- Global CDN with 275+ locations
- Automatic HTTPS and DDoS protection
- Zero-downtime updates
- Generous free tier (500 builds/month, unlimited requests)
**Option 3: S3 + CloudFront**
```bash
# 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**
```nginx
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:
```bash
# 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
```
### Example workflows
**Quarterly audit package**
```bash
# 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 age1auditor@firm.example
# 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**
```bash
# 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**
```bash
# 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 age1dr@company.example
# 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
```
### Troubleshooting exports
**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.
```bash
# 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:
```bash
# 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:
1. The original `manifest.json` (unmodified)
2. The `manifest.sig.json` file (contains signature and public key)
3. All assets referenced in the manifest with matching SHA-256 hashes
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:
- **OPFS not supported**: Older browsers may not support Origin Private File System. The viewer will fall back to in-memory mode (slower).
- **Database too large**: Browsers limit in-memory database size to ~1-2GB. Use chunking (`--chunk-threshold`) for very large archives.
- **CSP violations**: If hosting the bundle, ensure the web server doesn't add conflicting CSP headers. The viewer's CSP is defined in `index.html` and should not be overridden.
### On-disk layout (per project)
```
/projects//
agents//profile.json
agents//inbox/YYYY/MM/.md
agents//outbox/YYYY/MM/.md
messages/YYYY/MM/.md
messages/threads/.md # optional human digest maintained by the server
file_reservations/.json
attachments//.webp
```
### Message file format
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.
```markdown
---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 ...
```
### Data model (SQLite)
- `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
### Concurrency and lifecycle
- One request/task = one isolated operation
- Archive writes are guarded by a per-project `.archive.lock` under `projects//`
- Git index/commit operations are serialized across the shared archive repo by a repo-level `.commit.lock`
- DB operations are short-lived and scoped to each tool call; FTS triggers keep the search index current
- Artifacts are written first, then committed as a cohesive unit with a descriptive message
- Attachments are content-addressed (sha1) to avoid duplication
## How it works (key flows)
1) Create an identity
- `register_agent(project_key, program, model, name?, task_description?)` → creates/updates a named identity, persists profile to Git, and commits.
2) Send a message
- `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?)`
- Writes a canonical message under `messages/YYYY/MM/`, an outbox copy for the sender, and inbox copies for each recipient; commits all artifacts.
- Optionally converts images (local paths or data URIs) to WebP and embeds small ones inline.
```mermaid
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
```
3) Check inbox
- `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.
4) Avoid conflicts with file reservations (leases)
- `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.
- Optional: install a pre-commit hook in your code repo that blocks commits conflicting with other agents' active exclusive file reservations.
```mermaid
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
```
5) Search & summarize
- `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, ..., sender_token?)` creates a subject-prefixed reply, preserving or creating a thread.
### Semantics & invariants
- Identity
- Names are memorable adjective+noun and unique per project; `name_hint` is sanitized (alnum) and used if available
- `whois` returns the stored profile; `list_agents` can filter by recent activity
- `last_active_ts` is bumped on relevant interactions (messages, inbox checks, etc.)
- Threads
- Replies inherit `thread_id` from the original; if missing, the reply sets `thread_id` to the original message id
- Subject lines are prefixed (e.g., `Re:`) for readability in mailboxes
- Attachments
- Image references (file path or data URI) are converted to WebP; small images embed inline when policy allows
- Non-absolute `attachment_paths` (and markdown image paths) resolve relative to the project archive root under `STORAGE_ROOT/projects//`, not the code repo root
- Absolute attachment paths are disabled by default. Enabling `ALLOW_ABSOLUTE_ATTACHMENT_PATHS=true` on a networked deployment turns message sending into a filesystem read primitive for whatever paths the server process can access.
- Stored under `attachments//.webp` and referenced by relative path in frontmatter
- File Reservations
- TTL-based; exclusive means "please don't modify overlapping surfaces" for others until expiry or release
- Conflict detection is per exact path pattern; shared reservations can coexist, exclusive conflicts are surfaced
- JSON artifacts remain in Git for audit even after release (DB tracks release_ts)
- Search
- External-content FTS virtual table and triggers index subject/body on insert/update/delete
- Queries are constrained to the project id and ordered by `created_ts DESC`
## Contact model and "consent-lite" messaging
Goal: 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.
### Isolation by project
- All tools require a `project_key`. Agents only see messages addressed to them within that project.
- An agent working in Project A is invisible to agents in Project B unless explicit cross-project contact is established (see below). This avoids distraction between unrelated repositories.
### Policies (per agent)
- `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/approve contact
- `request_contact(project_key, from_agent, to_agent, reason?, ttl_seconds?, registration_token?)` 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?, registration_token?)` approves or denies; approval grants messaging until expiry.
- `list_contacts(project_key, agent_name)` surfaces outbound links with target projects and audit flags for expiry/messageability.
Auth note: these tools require either an agent already authenticated in the current MCP session, or the relevant `registration_token`. `send_message(..., auto_contact_if_blocked=true)` now creates pending contact requests by default; it only auto-approves when both agents are already authenticated in the same MCP session.
`macro_contact_handshake(..., auto_accept=true)` follows the same rule for new approvals: it only auto-approves when the target agent is already authenticated in the current MCP session or when `target_registration_token` is supplied explicitly. If the link is already approved, the macro reuses that approval. Otherwise the request remains pending and the macro reports a `response_error`.
### Auto-allow heuristics (no explicit request required)
- Same thread: replies or messages to thread participants are allowed.
- Recent overlapping file reservations: if sender and recipient hold active file reservations in the project, messaging is allowed.
- Recent prior contact: a sliding TTL allows follow-ups between the same pair.
These heuristics minimize friction while preventing cold spam.
### Cross-project coordination (frontend vs backend repos)
When two repos represent the same underlying project (e.g., `frontend` and `backend`), you have two options:
1) 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.
2) Keep separate `project_key`s and establish explicit contact:
- In `backend`, agent `GreenCastle` calls:
- `request_contact(project_key="/abs/path/backend", from_agent="GreenCastle", to_agent="BlueLake", reason="API contract changes", registration_token="")`
- In `frontend`, `BlueLake` calls:
- `respond_contact(project_key="/abs/path/backend", to_agent="BlueLake", from_agent="GreenCastle", accept=true, registration_token="")`
- After approval, messages can be exchanged; in default `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.
## Resource layer (read-only URIs)
Expose common reads as resources that clients can fetch. See API Quick Reference → Resources for the full list and parameters.
Example (conceptual) resource read:
```json
{
"method": "resources/read",
"params": {"uri": "resource://inbox/BlueLake?project=/abs/path/backend&limit=20&agent_token="}
}
```
```mermaid
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
```
## File Reservations and the optional pre-commit guard
- Guard status and pre-push
- Print guard status:
- `mcp-agent-mail guard status /path/to/repo`
- Install both guards (pre-commit + pre-push):
- `mcp-agent-mail guard install --prepush`
- Pre-commit honors `WORKTREES_ENABLED` and `AGENT_MAIL_GUARD_MODE` (`warn` advisory).
- Pre-push enumerates to-be-pushed commits (`rev-list`) and uses `diff-tree` with `--no-ext-diff`.
- Composition-safe install (chain-runner):
- A Python chain-runner is written to `.git/hooks/pre-commit` and `.git/hooks/pre-push`.
- It executes `hooks.d//*` in lexical order, then `.orig` if present (existing hooks are preserved, not overwritten).
- Agent Mail installs its guard as `hooks.d/pre-commit/50-agent-mail.py` and `hooks.d/pre-push/50-agent-mail.py`.
- Windows shims (`pre-commit.cmd/.ps1`, `pre-push.cmd/.ps1`) are written to invoke the Python chain-runner.
- Matching and safety details:
- Renames/moves are handled: both the old and new names are checked (`git diff --cached --name-status -M -z`).
- NUL-safe end-to-end: paths are collected and forwarded as NUL-delimited to avoid ambiguity.
- Git-native matching: reservations are checked using Git wildmatch pathspec semantics against repo-root relative paths; `core.ignorecase` is honored.
- Emergency bypass (use sparingly): set `AGENT_MAIL_BYPASS=1`, or use native Git `--no-verify`. In `warn` mode the guard never blocks.
## Git-based project identity (opt-in)
- 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 (MCP): `resource://identity/%2Fabs%2Fpath` (absolute paths must be URL-encoded inside the resource segment)
- CLI (diagnostics): `mcp-agent-mail mail status /abs/path`
- Precedence (when gate is on):
1) Committed marker `.agent-mail-project-id` (recommended)
2) Discovery YAML `.agent-mail.yaml` with `project_uid:`
3) Private marker under Git common dir `.git/agent-mail/project-id`
4) Remote fingerprint: normalized `origin` URL + default branch
5) `git-common-dir` hash; else dir hash
- Migration helpers:
- Write committed marker: `mcp-agent-mail projects mark-identity . --commit`
- Scaffold discovery file: `mcp-agent-mail projects discovery-init . --product `
Example identity payload (resource):
```json
{
"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"
}
```
## Adopt/Merge legacy projects (optional)
Consolidate legacy per-worktree projects into a canonical one (safe, explicit, and auditable).
- Plan the merge (no changes):
- `mcp-agent-mail projects adopt --dry-run`
- Apply the merge (moves artifacts and re-keys DB rows):
- `mcp-agent-mail projects adopt --apply`
- Safeguards and behavior:
- Requires both projects be in the same repository (validated via `git-common-dir`).
- Moves archived Git artifacts from `projects//…` to `projects//…` while preserving history.
- Re-keys database rows (`agents`, `messages`, `file_reservations`) from source to target project.
- Records `aliases.json` under the target with `"former_slugs": [...]` for discoverability.
- Aborts if agent-name conflicts would break uniqueness in the target (fix names, then retry).
- Idempotent where possible; dry-run always prints a clear plan before apply.
## Build slots and helpers (opt-in)
- `amctl env` prints helpful environment keys:
- `SLUG`, `PROJECT_UID`, `BRANCH`, `AGENT`, `CACHE_KEY`, `ARTIFACT_DIR`
- Example: `mcp-agent-mail amctl env --path . --agent AliceDev`
- `am-run` wraps a command with those keys set:
- Example: `mcp-agent-mail am-run frontend-build -- npm run dev`
- Auth: when talking to the HTTP server, `am-run` now auto-loads the local agent `registration_token` from the database when possible. You can also pass `--registration-token` or set `AGENT_MAIL_REGISTRATION_TOKEN`.
- Build slots (advisory, per-project coarse locking):
- Flags:
- `--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 starting
- Acquire:
- Tool: `acquire_build_slot(project_key, agent_name, slot, ttl_seconds=3600, exclusive=true)`
- Renew:
- Tool: `renew_build_slot(project_key, agent_name, slot, extend_seconds=1800)`
- Release (non-destructive; marks released):
- Tool: `release_build_slot(project_key, agent_name, slot)`
- Notes:
- Slots are recorded under the project archive `build_slots//__.json`
- `exclusive=true` reports conflicts if another active exclusive holder exists
- Intended for long-running tasks (dev servers, watchers); pair with `am-run` and `amctl env`
## Product Bus
Group multiple repositories (e.g., frontend, backend, infra) under a single product for product‑wide inbox/search and shared threads.
- Ensure a product:
- `mcp-agent-mail products ensure MyProduct --name "My Product"`
- Link a project (slug or path) into the product:
- `mcp-agent-mail products link MyProduct .`
- Inspect product and linked projects:
- `mcp-agent-mail products status MyProduct`
- Product‑wide message search (FTS):
- `mcp-agent-mail products search MyProduct "urgent AND deploy" --agent Alice --registration-token "$AGENT_MAIL_REGISTRATION_TOKEN" --limit 50`
- Product‑wide inbox:
- `mcp-agent-mail products inbox MyProduct Alice --registration-token "$AGENT_MAIL_REGISTRATION_TOKEN" --limit 50 --urgent-only --include-bodies --since-ts "2025-11-01T00:00:00Z"`
- Product‑wide thread summarization:
- `mcp-agent-mail products summarize-thread MyProduct "bd-123" --agent Alice --registration-token "$AGENT_MAIL_REGISTRATION_TOKEN" --per-thread-limit 100 --no-llm`
- Auth note:
- Product-wide search, inbox, and thread summarization now require an authenticated agent identity. These commands accept `--registration-token` / `AGENT_MAIL_REGISTRATION_TOKEN` and will auto-use a single unambiguous locally stored token when that is possible.
## Containers
- Build and run locally:
```bash
docker 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
```
- Or with Compose:
```bash
docker compose up --build
```
- Notes:
- Runs as an unprivileged user (`appuser`, uid 10001).
- Includes a HEALTHCHECK against `/health/liveness`.
- The server reads config from `.env` via python-decouple. You can mount it read-only into the container at `/app/.env`.
- Default bind host is `0.0.0.0` in the container; port `8765` is exposed.
- Persistent archive lives under `/data/mailbox` (mapped to the `agent_mail_data` volume by default).
- **Bind-mount uid mismatches:** if you bind-mount a host directory (instead of a named volume) into `/data/mailbox`, the files on the host are typically owned by uid 1000 rather than the container's `appuser` (uid 10001). Git would normally refuse to operate on such a "dubious ownership" directory, which surfaces as the non-obvious error `Unknown parameter: --cached` on diff/status. The image now whitelists `/data/mailbox` (and, as a catch-all inside the container only, `*`) via `git config --global --add safe.directory`. If you rebuild from a fork that omits that step, either re-add the safe.directory lines or `chown -R 10001:10001` the host path (see issue #143).
Notes
- A unique `product_uid` is stored for each product; you can reference a product by uid or name.
- Server tools also exist for orchestration: `ensure_product`, `products_link`, `search_messages_product`, and `resource://product/{key}`.
Exclusive file reservations are advisory but visible and auditable:
- A reservation JSON is written to `file_reservations/.json` capturing holder, pattern, exclusivity, created/expires
- The pre-commit guard scans active exclusive reservations and blocks commits that touch conflicting paths held by another agent
- Agents must set `AGENT_NAME` so the guard knows who "owns" the commit
- The server continuously evaluates reservations for staleness (agent inactivity + mail/filesystem/git silence) and releases abandoned locks automatically; the `force_release_file_reservation` tool uses the same heuristics and notifies the previous holder when another agent clears a stale lease
Install the guard into a code repo (conceptual tool call):
```json
{
"method": "tools/call",
"params": {
"name": "install_precommit_guard",
"arguments": {
"project_key": "/abs/path/backend",
"code_repo_path": "/abs/path/backend"
}
}
}
```
## Configuration
Configuration is loaded from an existing `.env` via `python-decouple`. Do not use `os.getenv` or auto-dotenv loaders.
### Changing the HTTP Port
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:
```bash
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**
```bash
# 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**
```bash
# Override port at server startup (doesn't modify .env)
uv run python -m mcp_agent_mail.cli serve-http --port 9000
```
```python
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:
### Configuration reference
| 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/` | Preferred MCP endpoint mount path (`/api` and `/mcp` aliases are also 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 |
| `MCP_AGENT_MAIL_OUTPUT_FORMAT` | | Default output format for tools/resources (`json` or `toon`) |
| `TOON_DEFAULT_FORMAT` | | Global default output format fallback (`json` or `toon`) |
| `TOON_STATS` | `false` | Emit TOON token stats (uses `tru --stats`) |
| `TOON_TRU_BIN` | | Explicit path/command for the `tru` encoder (overrides `TOON_BIN`) |
| `TOON_BIN` | `tru` | Path/command for toon_rust encoder (Node `toon` is rejected) |
| `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/) |
| `ALLOW_ABSOLUTE_ATTACHMENT_PATHS` | `false` | Allow absolute filesystem paths in attachments and markdown image references. Keep this disabled on networked deployments unless you explicitly want server-side file reads. |
| `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 instrument