Uses Git repositories for storing human-auditable message artifacts, agent profiles, and claim records with automatic commits for coordination history
Provides fast search capabilities through FTS5 indexing of messages, agent directories, and claims management for multi-agent coordination
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, 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:
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.
Companion stack (commercial): The iOS app + host automation can provision, pair, and steer heterogeneous fleets (Claude Code, Codex, Gemini CLI, 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
What this does:
Installs uv if missing and updates your PATH for this session
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/(includingrun_server_with_token.sh)Adds an
amshell alias to your.zshrcor.bashrcfor quick server startup (just typeamin a new terminal!)Installs/updates, verifies, and wires the Beads
bdCLI into your PATH via its official curl installer so the task planner is ready out of the box (pass--skip-beadsto opt out or install manually)Installs/updates the Beads Viewer
bvTUI for interactive task browsing and AI-friendly robot commands (pass--skip-bvto 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 <path>, --project-dir <path>, --no-start, --start-only, --port <number>, or --token <hex>.
Already have Beads or Beads Viewer installed? Append --skip-beads and/or --skip-bv to bypass automatic installation.
Starting the server in the future
After installation, you can start the MCP Agent Mail server from anywhere by simply typing:
That's it! The am alias (added to your .zshrc or .bashrc during installation) automatically:
Changes to the MCP Agent Mail directory
Runs the server startup script (which uses
uv runto handle the virtual environment)Loads your saved bearer token from
.envand 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):
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:
Ready-Made Blurb to Add to Your AGENTS.md or CLAUDE.md Files:
Integrating with Beads (dependency-aware task planning)
Beads is a lightweight task planner (bd CLI) that complements Agent Mail by keeping status and dependencies in one place while Mail handles messaging, file reservations, and audit trails. Project: steveyegge/beads
Highlights:
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.Install the
bdCLI via prebuilt release or Go build; see the repository for platform specifics.
Copy/paste blurb for agent-facing docs (leave as-is for reuse):
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
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/beads.jsonl directly or attempting to compute graph metrics (risking hallucinated results), they can call bv's deterministic robot flags and get JSON output they can trust.
Robot Flags for AI Integration
Flag | Output | Agent Use Case |
| All AI-facing commands | Discovery / capability check |
| PageRank, betweenness, HITS, critical path, cycles | Quick triage: "What's most impactful?" |
| Parallel tracks, items per track, unblocks lists | Execution planning: "What can run in parallel?" |
| Priority recommendations with reasoning + confidence | Task selection: "What should I work on next?" |
| Available filter presets (actionable, blocked, etc.) | Workflow setup: "Show me ready work" |
| Changes since commit/date, new/closed items, cycles | Progress tracking: "What changed?" |
Example: Agent Task Selection Workflow
When to Use bv vs bd
Tool | Best For |
| Creating, updating, closing tasks;
for simple "what's next" |
| 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:
Agent A runs
bv --robot-priority→ identifiesbd-42as highest-impactAgent A reserves files:
file_reservation_paths(..., reason="bd-42")Agent A announces:
send_message(..., thread_id="bd-42", subject="[bd-42] Starting high-impact refactor")Other agents see the reservation and Mail announcement, pick different tasks
Agent A completes, runs
bv --robot-diffto 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
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.httpunder the/mailpath.Who it's for: humans reviewing activity; agents should continue to use the MCP tools/resources API.
Launching the Web UI
Recommended (simple):
Advanced (manual commands):
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 optionallyHTTP_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
AgentLinkapprovals viarequest_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
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)
High Priority: All overseer messages are automatically marked as high importance, ensuring they stand out in agent inboxes.
Policy Bypass: Overseer messages bypass normal contact policies, so humans can always reach any agent regardless of their contact settings.
Special Sender Identity: Messages come from a special agent named "HumanOverseer" (automatically created per project) with:
Program:
WebUIModel:
HumanContact Policy:
open
The Message Preamble
Every overseer message begins with this preamble (automatically prepended):
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:
Priority Adjustment:
Clarification:
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:
HumanOverseerImportance:
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}.mdwith commit historyThread 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): ReadsREADME.md,AGENTS.md, and other docs to understand each project's purpose and detect natural relationshipsConfidence 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:
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_contactfor actual messaging permissions✅ Clear separation: discovery ≠ authorization
The Complete Workflow
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:
Ensure a project exists (via tool call or CLI):
Ensure/create project:
ensure_project(human_key)
Register one or more agents:
register_agent(project_key, program, model, name?)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
markdown2on 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.htmlfor 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_TOKENor setHTTP_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.sha256file 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 thearchivescrub 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
During restore the CLI:
Extracts the ZIP into a temp directory.
Moves any existing
storage.sqlite3, WAL/SHM siblings, andSTORAGE_ROOTinto timestamped.backup-<ts>folders so nothing is lost.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.
Quick Start: Interactive Deployment Wizard
The easiest way to export and deploy is the interactive wizard, which supports both GitHub Pages and Cloudflare Pages:
What the wizard does
The wizard provides a fully automated end-to-end deployment experience:
Session resumption: Detects interrupted sessions and offers to resume exactly where you left off, avoiding re-export
Configuration management: Remembers your last settings and offers to reuse them, saving time on repeated exports
Deployment target selection: Choose between GitHub Pages, Cloudflare Pages, or local export
Automatic CLI installation: Detects and installs missing tools (
ghfor GitHub,wranglerfor Cloudflare)Guided authentication: Step-by-step browser login flows for GitHub and Cloudflare
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
Redaction configuration: Choose between
standard(scrub secrets like API keys/tokens, keep agent names) orstrict(redact all message bodies)Cryptographic signing: Optional Ed25519 signing with automatic key generation or reuse of existing keys
Pre-flight validation: Checks that GitHub repo names are available before starting the export
Deployment summary: Shows what will be deployed (project count, bundle size, target, signing status) and asks for confirmation
Export and preview: Exports the bundle and launches an interactive preview server with automatic port detection (tries 9000-9100)
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
Automatic viewer asset refresh: Always ensures latest HTML/JS/CSS from source tree are used, even when reusing bundles
Real-time deployment: Streams git and deployment output in real-time so you can follow the progress
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:
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:
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 bundleConfig file (
wizard-config.json): Persistent, for "use last settings" across fresh runs, no bundle
Multi-project selection
The project selector supports flexible selection syntax:
Selection modes:
all: Export all projects (default)1: Export project #1 only1,3,5: Export projects #1, #3, and #51-3: Export projects #1, #2, and #3 (inclusive range)2-4,7: Export projects #2, #3, #4, and #7 (combined range and list)
Invalid selections (out of range, malformed) are rejected with helpful error messages and the wizard prompts again.
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:
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:
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:
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
ghCLIFor apt/dnf, shows complete manual installation instructions (including repo setup) since automation requires sudo
Runs
gh auth logininteractively to authenticate via browserCreates 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
wranglerCLIRuns
wrangler logininteractively to authenticate via browserDeploys directly to Cloudflare's global CDN (no git repository needed)
Streams wrangler output in real-time
Provides the
.pages.devURL 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
The export process:
Creates a snapshot of the SQLite database (read-only, no WAL/SHM files)
Copies message bodies, attachments, and metadata into the bundle structure
Applies redaction rules based on the scrub preset (default:
standard)Generates
manifest.jsonwith SHA-256 hashes for all assetsOptionally signs the manifest with Ed25519 (produces
manifest.sig.json)Packages everything into a ZIP archive (optional, enabled by default)
If chunking is enabled, writes the segmented database plus a
chunks.sha256manifest so streamed pages can be verified cheaplyOptionally 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.
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
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
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)
After decryption, unzip the archive and use share preview to view it.
Export options reference
Option | Type | Default | Description |
,
| Path | (required) | Directory where the static bundle will be written |
,
| List | All projects | Limit export to specific project slugs or human keys (repeatable) |
| Bytes | 65536 (64KB) | Inline attachments smaller than this as base64 data URIs |
| Bytes | 26214400 (25MB) | Mark attachments larger than this as external (not bundled) |
| String |
| Redaction preset:
or
(see Redaction presets section) |
| Bytes | 20971520 (20MB) | Split SQLite database into chunks if it exceeds this size |
| Bytes | 4194304 (4MB) | Chunk size when splitting large databases |
| Flag | false | Generate security summary and preview without writing files |
/
| Flag | true | Package the bundle into a ZIP archive |
| Path | None | Path to Ed25519 signing key (32-byte raw seed) |
| Path | None | Write the Ed25519 public key to this file after signing |
| String | None | age public key for encryption (repeatable for multiple recipients) |
,
| 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:
Marked.js parses GitHub-Flavored Markdown into HTML
DOMPurify sanitizes the HTML, removing dangerous tags and attributes
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 scenariosstyle-src: Allows self, inline styles (for Tailwind), and font CDNsTrusted Types removed for browser compatibility (Firefox, Safari don't support it yet)
Cryptographic signing (Ed25519)
When you provide a signing key, the export process:
Generates a canonical JSON representation of
manifest.jsonSigns it with Ed25519 (fast, 64-byte signatures, 128-bit security)
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:
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 adminQuoted 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)
Or manually:
Option 2: Cloudflare Pages (automated via wizard)
Or manually with wrangler CLI:
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
Option 4: Nginx static site
Option 5: Encrypted distribution via file sharing
For confidential archives:
Example workflows
Quarterly audit package
Executive summary for stakeholders
Disaster recovery backup
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.
Alternatively, filter to specific projects with --project.
Encrypted bundle won't decrypt
Verify you're using the correct identity file:
Signature verification fails
Signature verification requires:
The original
manifest.json(unmodified)The
manifest.sig.jsonfile (contains signature and public key)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.htmland should not be overridden.
On-disk layout (per project)
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.
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.lockunderprojects/<slug>/Git index/commit operations are serialized across the shared archive repo by a repo-level
.commit.lockDB 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)
Create an identity
register_agent(project_key, program, model, name?, task_description?)→ creates/updates a named identity, persists profile to Git, and commits.
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.
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.
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.
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, ...)creates a subject-prefixed reply, preserving or creating a thread.
Semantics & invariants
Identity
Names are memorable adjective+noun and unique per project;
name_hintis sanitized (alnum) and used if availablewhoisreturns the stored profile;list_agentscan filter by recent activitylast_active_tsis bumped on relevant interactions (messages, inbox checks, etc.)
Threads
Replies inherit
thread_idfrom the original; if missing, the reply setsthread_idto the original message idSubject 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 paths resolve relative to the project repo root
Stored under
attachments/<xx>/<sha1>.webpand 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?)creates or refreshes a pending link and sends a small ack_required "intro" message to the recipient.respond_contact(project_key, to_agent, from_agent, accept, ttl_seconds?)approves or denies; approval grants messaging until expiry.list_contacts(project_key, agent_name)surfaces current links.
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:
Use the same
project_keyacross both workspaces. Agents in both repos operate under one project namespace and benefit from full inbox/outbox coordination automatically.Keep separate
project_keys and establish explicit contact:In
backend, agentGreenCastlecalls:request_contact(project_key="/abs/path/backend", from_agent="GreenCastle", to_agent="BlueLake", reason="API contract changes")
In
frontend,BlueLakecalls:respond_contact(project_key="/abs/path/backend", to_agent="BlueLake", from_agent="GreenCastle", accept=true)
After approval, messages can be exchanged; in default
autopolicy 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:
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 <project_key> <repo_path> --prepush
Pre-commit honors
WORKTREES_ENABLEDandAGENT_MAIL_GUARD_MODE(warnadvisory).Pre-push enumerates to-be-pushed commits (
rev-list) and usesdiff-treewith--no-ext-diff.Composition-safe install (chain-runner):
A Python chain-runner is written to
.git/hooks/pre-commitand.git/hooks/pre-push.It executes
hooks.d/<hook>/*in lexical order, then<hook>.origif present (existing hooks are preserved, not overwritten).Agent Mail installs its guard as
hooks.d/pre-commit/50-agent-mail.pyandhooks.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.ignorecaseis honored.Emergency bypass (use sparingly): set
AGENT_MAIL_BYPASS=1, or use native Git--no-verify. Inwarnmode the guard never blocks.
Git-based project identity (opt-in)
Gate:
WORKTREES_ENABLED=1orGIT_IDENTITY_ENABLED=1enables 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/{/abs/path}(available whenWORKTREES_ENABLED=1)CLI (diagnostics):
mcp-agent-mail mail status /abs/path
Precedence (when gate is on):
Committed marker
.agent-mail-project-id(recommended)Discovery YAML
.agent-mail.yamlwithproject_uid:Private marker under Git common dir
.git/agent-mail/project-idRemote fingerprint: normalized
originURL + default branchgit-common-dirhash; else dir hash
Migration helpers:
Write committed marker:
mcp-agent-mail projects mark-identity . --commitScaffold discovery file:
mcp-agent-mail projects discovery-init . --product <product_uid>
Example identity payload (resource):
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 <from> <to> --dry-run
Apply the merge (moves artifacts and re-keys DB rows):
mcp-agent-mail projects adopt <from> <to> --apply
Safeguards and behavior:
Requires both projects be in the same repository (validated via
git-common-dir).Moves archived Git artifacts from
projects/<old-slug>/…toprojects/<new-slug>/…while preserving history.Re-keys database rows (
agents,messages,file_reservations) from source to target project.Records
aliases.jsonunder 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 envprints helpful environment keys:SLUG,PROJECT_UID,BRANCH,AGENT,CACHE_KEY,ARTIFACT_DIRExample:
mcp-agent-mail amctl env --path . --agent AliceDev
am-runwraps a command with those keys set:Example:
mcp-agent-mail am-run frontend-build -- npm run dev
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/<slot>/<agent>__<branch>.jsonexclusive=truereports conflicts if another active exclusive holder existsIntended for long-running tasks (dev servers, watchers); pair with
am-runandamctl 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" --limit 50
Product‑wide inbox:
mcp-agent-mail products inbox MyProduct Alice --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" --per-thread-limit 100 --no-llm
Containers
Build and run locally:
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-mailOr with Compose:
docker compose up --buildNotes:
Runs as an unprivileged user (
appuser, uid 10001).Includes a HEALTHCHECK against
/health/liveness.The server reads config from
.envvia python-decouple. You can mount it read-only into the container at/app/.env.Default bind host is
0.0.0.0in the container; port8765is exposed.Persistent archive lives under
/data/mailbox(mapped to theagent_mail_datavolume by default).
Notes
A unique
product_uidis 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, andresource://product/{key}.
Exclusive file reservations are advisory but visible and auditable:
A reservation JSON is written to
file_reservations/<sha1(path)>.jsoncapturing holder, pattern, exclusivity, created/expiresThe pre-commit guard scans active exclusive reservations and blocks commits that touch conflicting paths held by another agent
Agents must set
AGENT_NAMEso the guard knows who "owns" the commitThe server continuously evaluates reservations for staleness (agent inactivity + mail/filesystem/git silence) and releases abandoned locks automatically; the
force_release_file_reservationtool 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):
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
Option 2: After installation (CLI)
Option 3: Manual .env edit
Option 4: CLI server override
Common variables you may set:
Configuration reference
Name | Default | Description |
|
| Root for per-project repos and SQLite DB |
|
| Bind host for HTTP transport |
|
| Bind port for HTTP transport |
|
| HTTP path where MCP endpoint is mounted |
|
| Enable JWT validation middleware |
| HMAC secret for HS* algorithms (dev) | |
| JWKS URL for public key verification | |
|
| CSV of allowed algs |
| Expected
(optional) | |
| Expected
(optional) | |
|
| JWT claim name containing role(s) |
|
| Enforce read-only vs tools RBAC |
|
| CSV of reader roles |
|
| CSV of writer roles |
|
| Role used when none present |
|
| CSV of read-only tool names |
|
| Enable token-bucket limiter |
|
|
or
|
|
| Legacy per-IP limit (fallback) |
|
| Per-minute for tools/call |
|
| Optional burst for tools (0=auto=rpm) |
|
| Per-minute for resources/read |
|
| Optional burst for resources (0=auto=rpm) |
| Redis URL for multi-worker limits | |
|
| Print request logs (Rich + JSON) |
|
| Output structlog JSON logs |
|
| Threshold (bytes) for inlining WebP images during send_message |
|
| Convert images to WebP (and optionally inline small ones) |
|
| Also store original image bytes alongside WebP (attachments/originals/) |
|
| Server log level |
|
| Enable CORS middleware when true |
| CSV of allowed origins (e.g.,
) | |
|
| Allow credentials on CORS |
|
| CSV of allowed methods or
|
|
| CSV of allowed headers or
|
| Static bearer token (only when JWT disabled) | |
|
| Allow localhost requests without auth (dev convenience) |
|
| Enable OpenTelemetry instrumentation |
|
| Service name for telemetry |
| OTLP exporter endpoint URL | |
|
| Environment name (development/production) |
|
| SQLAlchemy async database URL |
|
| Echo SQL statements for debugging |
|
| Git commit author name |
|
| Git commit author email |
|
| Enable LiteLLM for thread summaries and discovery |
|
| Default LiteLLM model identifier |
|
| LLM temperature for text generation |
|
| Max tokens for LLM completions |
|
| Enable LLM response caching |
|
| LLM cache backend (
or
) |
| Redis URL for LLM cache (if backend=redis) | |
|
| Log LLM API costs and token usage |
|
| Enable background cleanup of expired file reservations |
|
| Interval for file reservations cleanup task |
|
| Inactivity threshold (seconds) before a reservation is considered stale |
|
| Grace window for recent mail/filesystem/git activity to keep a reservation active |
|
| Block message writes on conflicting file reservations |
|
| Enable overdue ACK scanning (logs/panels; see views/resources) |
|
| Age threshold (seconds) for overdue ACKs |
|
| Scan interval for overdue ACKs |
|
| Enable escalation for overdue ACKs |
|
|
or
escalation mode |
|
| TTL for escalation file reservations |
|
| Make escalation file reservation exclusive |
| Ops agent name to own escalation file reservations | |
|
| Enforce contact policy before messaging |
|
| TTL for auto-approved contacts (1 day) |
|
| Auto-retry contact requests on policy violations |
|
| Automatically create missing local recipients during
and retry routing |
|
| When contact policy blocks delivery, attempt a contact handshake (auto-accept) and retry |
|
| Log tool invocations for debugging |
|
| Enable Rich console logging |
|
| Include trace-level logs |
|
| Emit periodic tool usage metrics |
|
| Interval for metrics emission |
|
| Enable retention/quota reporting |
|
| Interval for retention reports (1 hour) |
|
| Max age for retention policy reporting |
|
| Enable quota enforcement |
|
| Max attachment storage per project (0=unlimited) |
|
| Max inbox messages per agent (0=unlimited) |
|
| CSV of project patterns to ignore in retention/quota reports |
|
| Agent naming policy:
(reject invalid adjective+noun names),
(auto-generate if invalid),
(always auto-generate) |
Development quick start
Prerequisite: complete the setup above (Python 3.14 + uv venv + uv sync).
Dev helpers:
Database schema (automatic):
Run the server (HTTP-only). Use the Typer CLI or module entry:
Connect with your MCP client using the HTTP (Streamable HTTP) transport on the configured host/port. The endpoint tolerates both /mcp and /mcp/.
Search syntax tips (SQLite FTS5)
Basic terms:
plan usersPhrase search:
"build plan"Prefix search:
mig*Boolean operators:
plan AND users NOT legacyField boosting is not enabled by default; subject and body are indexed. Keep queries concise. When FTS is unavailable, the UI/API automatically falls back to SQL LIKE on subject/body.
Design choices and rationale
HTTP-only FastMCP: Streamable HTTP is the modern remote transport; STDIO is not exposed here by design
Git + Markdown: Human-auditable, diffable artifacts that fit developer workflows (inbox/outbox mental model)
SQLite + FTS5: Efficient indexing/search with minimal ops footprint
Advisory file reservations: Make intent explicit and reviewable; optional guard enforces reservations at commit time
WebP attachments: Compact images by default; inline embedding keeps small diagrams in context
Optional: keep original binaries and dedup manifest under
attachments/for audit and reuse
Examples (conceptual tool calls)
This section has been removed to keep the README focused. See API Quick Reference below for canonical method signatures.
Operational notes
One async session per request/task; don't share across concurrent coroutines
Use explicit loads in async code; avoid implicit lazy loads
Use async-friendly file operations when needed; Git operations are serialized with a file lock
Clean shutdown should dispose any async engines/resources (if introduced later)
Security and ops
Transport
HTTP-only (Streamable HTTP). Place behind a reverse proxy (e.g., NGINX) with TLS termination for production
Auth
Optional JWT (HS*/JWKS) via HTTP middleware; enable with
HTTP_JWT_ENABLED=trueStatic bearer token (
HTTP_BEARER_TOKEN) is independent of JWT; when set, BearerAuth protects all routes (including UI). You may use it alone or together with JWT.When JWKS is configured (
HTTP_JWT_JWKS_URL), incoming JWTs must include a matchingkidheader; tokens withoutkidor with unknownkidare rejectedStarter RBAC (reader vs writer) using role configuration; see
HTTP_RBAC_*settings
Reverse proxy + TLS (minimal example)
NGINX location block:
upstream mcp_mail { server 127.0.0.1:8765; } server { listen 443 ssl; server_name mcp.example.com; ssl_certificate /etc/letsencrypt/live/mcp.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mcp.example.com/privkey.pem; location /mcp/ { proxy_pass http://mcp_mail; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; } }
Backups and retention
The Git repos and SQLite DB live under
STORAGE_ROOT; back them up together for consistency
Observability
Add logging and metrics at the ASGI layer returned by
mcp.http_app()(Prometheus, OpenTelemetry)
Concurrency
Archive writes: per-project
.archive.lockprevents cross-project head-of-line blockingCommits: repo-level
.commit.lockserializes Git index/commit to avoid races across projects
Python client example (HTTP JSON-RPC)
This section has been removed to keep the README focused. Client code samples belong in examples/.
Troubleshooting
"sender_name not registered"
Create the agent first with
register_agentorcreate_agent_identity, or check theproject_keyyou're using matches the sender's project
Pre-commit hook blocks commits
Set
AGENT_NAMEto your agent identity; release or wait for conflicting exclusive file reservations; inspect.git/hooks/pre-commit
Inline images didn't embed
Ensure
convert_images=true; images are automatically inlined if the compressed WebP size is below the server'sINLINE_IMAGE_MAX_BYTESthreshold (default 64KB). Larger images are stored as attachments instead.
Message not found
Confirm the
projectdisambiguation when usingresource://message/{id}; ids are unique per project
Inbox empty but messages exist
Check
since_ts,urgent_only, andlimit; verify recipient names match exactly (case-sensitive)
FAQ
Why Git and SQLite together?
Git provides human-auditable artifacts and history; SQLite provides fast queries and FTS search. Each is great at what the other isn't.
Are file reservations enforced?
Yes, optionally. The server can block message writes when a conflicting active exclusive reservation exists (
FILE_RESERVATIONS_ENFORCEMENT_ENABLED=true, default). Reservations themselves are advisory and always return bothgrantedandconflicts. The optional pre-commit hook adds local enforcement at commit time in your code repo.
Why HTTP-only?
Streamable HTTP is the modern remote transport for MCP; avoiding extra transports reduces complexity and encourages a uniform integration path.
Why JSON-RPC instead of REST or gRPC?
MCP defines a tool/resource method call model that maps naturally to JSON-RPC over a single endpoint. It keeps clients simple (one URL, method name + params), plays well with proxies, and avoids SDK lock-in while remaining language-agnostic.
Why separate "resources" (reads) from "tools" (mutations)?
Clear semantics enable aggressive caching and safe prefetch for resources, while tools remain explicit, auditable mutations. This split also powers RBAC (read-only vs writer) without guesswork.
Why canonical message storage in Git, not only in the database?
Git gives durable, diffable, human-reviewable artifacts you can clone, branch, and audit. SQLite provides fast indexing and FTS. The combo preserves governance and operability without a heavyweight message bus.
Why advisory file reservations instead of global locks?
Agents coordinate asynchronously; hard locks create head-of-line blocking and brittle failures. Advisory reservations surface intent and conflicts while the optional pre-commit guard enforces locally where it matters.
Why are agent names adjective+noun?
Memorable identities reduce confusion in inboxes, commit logs, and UI. The scheme yields low collision risk while staying human-friendly (vs GUIDs) and predictable for directory listings.
Why is
project_keyan absolute path?Using the workspace's absolute path creates a stable, collision-resistant project identity across shells and agents. Slugs are derived deterministically from it, avoiding accidental forks of the same project.
Why WebP attachments and optional inlining?
WebP provides compact, high-quality images. Small images can be inlined for readability; larger ones are stored as attachments. You can keep originals when needed (
KEEP_ORIGINAL_IMAGES=true).
Why both static bearer and JWT/JWKS support?
Local development should be zero-friction (single bearer). Production benefits from verifiable JWTs with role claims, rotating keys via JWKS, and layered RBAC.
Why SQLite FTS5 instead of an external search service?
FTS5 delivers fast, relevant search with minimal ops. It’s embedded, portable, and easy to back up with the Git archive. If FTS isn’t available, we degrade to SQL LIKE automatically.
Why is LLM usage optional?
Summaries and discovery should enhance, not gate, core functionality. Keeping LLM usage optional controls cost and latency while allowing richer UX when enabled.
API Quick Reference
Tools
Tip: to see tools grouped by workflow with recommended playbooks, fetch
resource://tooling/directory.
Name | Signature | Returns | Notes | ||
|
|
| Lightweight readiness probe | ||
|
|
| Idempotently creates/ensures project | ||
|
| Agent profile dict | Creates/updates agent; writes profile to Git | ||
|
| Agent profile dict | Enriched profile for one agent (optionally includes recent commits) | ||
|
| Agent profile dict | Always creates a new unique agent | ||
|
|
| Writes canonical + inbox/outbox, converts images | ||
|
|
| Preserves/creates thread, inherits flags | ||
|
| Contact link dict | Request permission to message another agent | ||
|
| Contact link dict | Approve or deny a contact request | ||
|
|
| List contact links for an agent | ||
|
| Agent dict | Set policy:
,
,
,
| ||
|
|
| Non-mutating inbox read | ||
|
|
| Per-recipient read receipt | ||
|
|
| Sets ack and read | ||
|
|
| Orchestrates ensure→register→optional file reservation→inbox fetch | ||
|
|
| Bundles registration, thread summary, and inbox context | ||
|
|
| File Reservation + optionally release surfaces around a focused edit block | ||
| `macro_contact_handshake(project_key: str, requester | agent_name: str, target | to_agent: str, to_project?: str, reason?: str, ttl_seconds?: int, auto_accept?: bool, welcome_subject?: str, welcome_body?: str)` |
| Automates contact request/approval and optional welcome ping |
|
|
| FTS5 search (bm25) | ||
|
| Single:
Multi (comma-sep):
| Extracts participants, key points, actions. Use comma-separated thread_id for multi-thread digest. | ||
|
|
| Install a Git pre-commit guard in a target repo | ||
|
|
| Remove the guard from a repo | ||
|
|
| Advisory leases; Git artifact per path | ||
|
|
| Releases agent's active file reservations | ||
|
|
| Clears stale reservations using inactivity/mail/fs/git heuristics and notifies the previous holder | ||
|
|
| Extend TTL of existing file reservations |
Resources
URI | Params | Returns | Notes |
| — |
| Inspect server settings |
| — |
| Grouped tool directory + workflow playbooks |
| — |
| Argument hints for tools |
| — |
| Aggregated call/error counts per tool |
| — |
| Active locks and owners (debug only). Categories:
(per-project
) and
(e.g., repo
). |
| listed |
| Capabilities assigned to the agent (see
) |
| listed |
| Recent tool usage filtered by agent/project |
| — |
| All projects |
|
|
| Project detail + agents |
|
,
|
| File reservations plus staleness metadata (heuristics, last activity timestamps) |
|
,
|
| Single message with body |
|
,
,
|
| Thread listing |
| listed |
| Inbox listing |
|
,
|
| Mailbox listing (recent messages with basic commit ref) |
|
,
|
| Mailbox listing enriched with commit metadata |
| listed |
| Messages sent by the agent |
| listed |
| Ack-required older than TTL without ack |
| listed |
| High/urgent importance messages not yet read |
| listed |
| Pending acknowledgements for an agent |
| listed |
| Ack-required older than TTL without ack |
Client Integration Guide
Fetch onboarding metadata first. Issue
resources/readforresource://tooling/directory(and optionallyresource://tooling/metrics) before exposing tools to an agent. Use the returned clusters and playbooks to render a narrow tool palette for the current workflow rather than dumping every verb into the UI.Scope tools per workflow. When the agent enters a new phase (e.g., "Messaging Lifecycle"), remount only the cluster's tools in your MCP client. This mirrors the workflow macros already provided and prevents "tool overload."
Monitor real usage. Periodically pull or subscribe to log streams containing the
tool_metrics_snapshotevents emitted by the server (or queryresource://tooling/metrics) so you can detect high-error-rate tools and decide whether to expose macros or extra guidance.Fallback to macros for smaller models. If you're routing work to a lightweight model, prefer the macro helpers (
macro_start_session,macro_prepare_thread,macro_file_reservation_cycle,macro_contact_handshake) and hide the granular verbs until the agent explicitly asks for them.Show recent actions. Read
resource://tooling/recent/60?agent=<name>&project=<slug>(adjust window as needed) to display the last few successful tool invocations relevant to the agent/project.
See examples/client_bootstrap.py for a runnable reference implementation that applies the guidance above.
Monitoring & Alerts
Enable metric emission. Set
TOOL_METRICS_EMIT_ENABLED=trueand choose an interval (TOOL_METRICS_EMIT_INTERVAL_SECONDS=120is a good starting point). The server will periodically emit a structured log entry such as:
Ship the logs. Forward the structured stream (stderr/stdout or JSON log files) into your observability stack (e.g., Loki, Datadog, Elastic) and parse the
tools[]array.Alert on anomalies. Create a rule that raises when
errors / callsexceeds a threshold for any tool (for example 5% over a 5-minute window) so you can decide whether to expose a macro or improve documentation.Dashboard the clusters. Group by
clusterto see where agents are spending time and which workflows might warrant additional macros or guard-rails.
See docs/observability.md for a step-by-step cookbook (Loki/Prometheus example pipelines included), and docs/GUIDE_TO_OPTIMAL_MCP_SERVER_DESIGN.md for a comprehensive design guide covering tool curation, capability gating, security, and observability best practices.
Operations teams can follow docs/operations_alignment_checklist.md, which links to the capability templates in deploy/capabilities/ and the sample Prometheus alert rules in deploy/observability/.
Deployment quick notes
Direct uvicorn:
uvicorn mcp_agent_mail.http:build_http_app --factory --host 0.0.0.0 --port 8765Python module:
python -m mcp_agent_mail.http --host 0.0.0.0 --port 8765Gunicorn:
gunicorn -c deploy/gunicorn.conf.py mcp_agent_mail.http:build_http_app --factoryDocker:
docker compose up --build
CI/CD
Lint and Typecheck CI: GitHub Actions workflow runs Ruff and Ty on pushes/PRs to main/develop.
Release: Pushing a tag like
v0.1.0builds and pushes a multi-arch Docker image to GHCR underghcr.io/<owner>/<repo>withlatestand version tags.Nightly: A scheduled workflow runs migrations and lists projects daily for lightweight maintenance visibility.
Log rotation (optional)
If not using journald, a sample logrotate config is provided at deploy/logrotate/mcp-agent-mail to rotate /var/log/mcp-agent-mail/*.log weekly, keeping 7 rotations.
Logging (journald vs file)
Default systemd unit (
deploy/systemd/mcp-agent-mail.service) is configured to send logs to journald (StandardOutput/StandardError=journal).For file logging, configure your process manager to write to files under
/var/log/mcp-agent-mail/*.logand install the provided logrotate config.Environment file path for systemd is
/etc/mcp-agent-mail.env(seedeploy/systemd/mcp-agent-mail.service).
Container build and multi-arch push
Use Docker Buildx for multi-arch images. Example flow:
Recommended tags: a moving latest and immutable version tags per release. Ensure your registry login is configured (docker login).
Systemd manual deployment steps
Copy project files to
/opt/mcp-agent-mailand ensure permissions (ownerappuser).Place environment file at
/etc/mcp-agent-mail.envbased ondeploy/env/production.env.Install service file
deploy/systemd/mcp-agent-mail.serviceto/etc/systemd/system/.Reload systemd and start:
Optional (non-journald log rotation): install deploy/logrotate/mcp-agent-mail into /etc/logrotate.d/ and write logs to /var/log/mcp-agent-mail/*.log via your process manager or app config.
See deploy/gunicorn.conf.py for a starter configuration. For project direction and planned areas, read project_idea_and_guide.md.
CLI Commands
The project exposes a developer CLI for common operations:
serve-http: run the HTTP transport (Streamable HTTP only)migrate: ensure schema and FTS structures existlint/typecheck: developer helperslist-projects [--include-agents]: enumerate projectsguard install <project_key> <code_repo_path>: install the pre-commit guard into a repoguard uninstall <code_repo_path>: remove the guard from a reposhare wizard: launch interactive deployment wizard (auto-installs CLIs, authenticates, exports, deploys to GitHub Pages or Cloudflare Pages)share export --output <path> [options]: export mailbox to a static HTML bundle (see Static Mailbox Export section for full options)share update <bundle_path> [options]: refresh an existing bundle using recorded (or overridden) export settingsshare preview <bundle_path> [--port N] [--open-browser]: serve a static bundle locally for inspectionshare verify <bundle_path> [--public-key <key>]: verify bundle integrity (SRI hashes and Ed25519 signature)share decrypt <encrypted_path> [--identity <file> | --passphrase]: decrypt an age-encrypted bundleconfig set-port <port>: change the HTTP server port (updates .env)config show-port: display the current configured HTTP portclear-and-reset-everything [--force] [--archive/--no-archive]: DELETE the SQLite database (incl. WAL/SHM) and WIPE all contents underSTORAGE_ROOTafter optionally saving a restore point. Without flags it prompts to create an archive first;--force --no-archiveskips all prompts for automation.list-acks --project <key> --agent <name> [--limit N]: list messages requiring acknowledgement for an agent where ack is missingacks pending <project> <agent> [--limit N]: show pending acknowledgements for an agentacks remind <project> <agent> [--min-age-minutes N] [--limit N]: highlight pending ACKs older than a thresholdacks overdue <project> <agent> [--ttl-minutes N] [--limit N]: list overdue ACKs beyond TTLfile_reservations list <project> [--active-only/--no-active-only]: list file reservationsfile_reservations active <project> [--limit N]: list active file reservationsfile_reservations soon <project> [--minutes N]: show file reservations expiring soon
Examples:
Client integrations
Use the automated installer to wire up supported tools automatically (e.g., Claude Code, Cline, Windsurf, OpenCode). Run scripts/automatically_detect_all_installed_coding_agents_and_install_mcp_agent_mail_in_all.sh or the one-liner in the Quickstart above.