MCP Servers

A collection of Model Context Protocol servers, templates, tools and more.

Fork of mcp_agent_mail - A mail-like coordination layer for coding agents

Created 11/8/2025
Updated about 1 month ago
Repository documentation and setup instructions

MCP Mail (Forked from mcp_agent_mail)

PyPI version Python 3.11+ License: MIT

A mail-like coordination layer for coding agents

Agent Mail Showcase

"It's like gmail for your coding agents!"

📦 Installation

Quick Install from PyPI (Recommended):

pip install mcp-mail

or with uv:

uv pip install mcp_mail

One-Line Installer (sets up everything):

curl -fsSL https://raw.githubusercontent.com/jleechanorg/mcp_mail/main/scripts/install.sh | bash -s -- --yes

This installer:

  • Installs uv if missing and updates your PATH
  • Creates a Python 3.11 virtual environment
  • Installs dependencies
  • Starts the MCP HTTP server on port 8765
  • Creates helper scripts (including run_server_with_token.sh)

After installation, the server listens on http://127.0.0.1:8765/mail.

Manual Installation:

# Install uv (if you don't have it already)
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

# Clone the repo
git clone https://github.com/jleechanorg/mcp_mail
cd mcp_mail

# Create a Python 3.11 virtual environment and install
uv python install 3.11
uv venv -p 3.11
source .venv/bin/activate
uv sync

# Start the MCP server
scripts/run_server_with_token.sh

Alternative Install Sources:

PyPI Piwheels

🔗 Repository Links

This Fork: jleechanorg/mcp_mailOriginal: Dicklesworthstone/mcp_agent_mail


🌟 Fork Attribution

**This repository is forked from https://github.com/Dicklesworthstone/mcp_agent_mail. Full credit goes to the original author for creating this innovative multi-agent coordination system.

Full credit goes to the original author for creating this innovative multi-agent coordination system.

Note: This was copied as a standalone repository rather than kept as a fork because Codex web appears to ignore forks in its repository indexing. Here was my first attempt at a normal fork: jleechanorg/mcp_agent_mail

Fork Improvements

This fork extends the original MCP Agent Mail with 11 core enhancements focused on removing coordination barriers and improving efficiency:

🚀 Token Efficiency

  • 🎯 Lazy Loading System (Phase 2 Complete) - Reduces token usage by ~65%:
    • Core mode (default): 8 essential tools + 2 meta-tools = 10 tools loaded
    • Extended mode: 15 additional tools loaded on-demand via call_extended_tool
    • Meta-tools: list_extended_tools (discovery) and call_extended_tool (invocation)
    • Organized into 6 functional clusters (infrastructure, identity, messaging, search, file reservations, macros)
    • Configure via MCP_TOOLS_MODE environment variable (core|extended)
    • Implementation: src/mcp_agent_mail/app.py:2426-2476 (CORE_TOOLS, EXTENDED_TOOLS, EXTENDED_TOOL_METADATA)

🌐 Global Architecture (No Boundaries)

  • 🌍 Projects as Informational Metadata - Projects don't create barriers:

    • Projects are metadata only (badges, tags, context) - NOT organizational boundaries
    • Unified inbox shows ALL messages regardless of project
    • Backend queries ignore project filters (messages fetched by agent, not project)
    • Consistent treatment across all layers (UI, database, tools)
    • Why: Agents work seamlessly across multiple repos/projects
    • Implementation: src/mcp_agent_mail/app.py:2312-2327 (_get_message_by_id_global)
  • 🎯 Globally Unique Agent Names - Database-enforced global uniqueness:

    • Case-insensitive uniqueness across ALL projects (prevents name collisions)
    • Auto-migration: Existing duplicates renamed with numeric suffixes (Alice → Alice2, Alice3)
    • Flexible enforcement: strict (reject duplicates), coerce (auto-rename), always_auto (generate memorable names)
    • Race condition protection at database level with functional index
    • Configure via AGENT_NAME_ENFORCEMENT_MODE environment variable
    • Implementation: src/mcp_agent_mail/db.py:236-302 (_migrate_agent_name_uniqueness)
  • 🌍 Global Agent Lookup - Agents accessible by name alone:

    • _get_agent_by_name() looks up agents globally without needing project context
    • Enables cross-project agent references and coordination
    • Simplifies agent discovery and communication
    • Implementation: src/mcp_agent_mail/app.py:1569-1582
  • 🌍 Global Message Lookup - Messages accessible across projects:

    • _get_message_by_id_global() retrieves messages by ID regardless of project
    • Agents can reply to messages they received from any project
    • Eliminates "Message X not found for project Y" errors
    • Tools using global lookup: reply_message, mark_message_read, acknowledge_message
    • Implementation: src/mcp_agent_mail/app.py:2312-2327

🎙️ Automatic Notifications

  • 📬 Global Inbox with Mention Scanning - Never miss important messages:
    • Automatic @mention detection when agents fetch inbox
    • Scans global inbox for messages mentioning agent name (case-insensitive)
    • Works in both subject and body text
    • Messages marked with "source": "global_inbox_mention" for traceability
    • Automatic deduplication (no duplicate messages)
    • Falls back gracefully if global inbox unavailable
    • Implementation: src/mcp_agent_mail/app.py:66-68 (get_global_inbox_name), 676-711 (_ensure_global_inbox_agent)

⚡ Simplified Workflows

  • ✅ Simplified Registration - One-step agent onboarding:

    • register_agent now auto-creates projects if they don't exist
    • Eliminates need for separate ensure_project() call
    • Automatic archive initialization and Git repo setup
    • Just call register_agent and start sending messages
    • Implementation: src/mcp_agent_mail/app.py:657-673 (_ensure_project auto-called)
  • 🔧 Flexible Project Keys - Any string works as project identifier:

    • Repo names (myapp-frontend, api-backend)
    • Custom slugs (team-alpha, prod-db)
    • Filesystem paths (still supported for backward compatibility)
    • Not limited to absolute paths - use whatever makes sense
    • Backward compatible with existing path-based keys
  • 💬 Contact-Free Messaging - No approval workflow:

    • Direct agent-to-agent messaging without approval
    • Removed contact tools: request_contact, respond_contact, macro_contact_handshake
    • Auto-registration of missing local recipients
    • Streamlines multi-agent coordination across projects
    • Configure via messaging_auto_register_recipients (default: True)
  • ⚡ Auto-Fetch Inbox - Streamline agent registration:

    • register_agent(..., auto_fetch_inbox=True) automatically fetches inbox after registration
    • Eliminates need for separate fetch_inbox call
    • Configurable filters: inbox_limit, inbox_urgent_only, inbox_since_ts
    • Agents immediately see relevant messages upon joining
    • Implementation: src/mcp_agent_mail/app.py:3021-3159

🔍 Advanced Search

  • 🎯 Dual Search System - Find messages with sophisticated ranking:
    • search_mailbox (CORE): Advanced FTS5 search with BM25 relevance scoring
      • Global inbox prioritization (important messages ranked higher)
      • Agent filtering and subject/body snippet highlighting
      • Full message body inclusion option
      • Returns detailed metadata with relevance scores
    • search_messages (EXTENDED): Simpler FTS5 search for basic queries
    • Full-text search across subjects, bodies, and attachments
    • Helps agents find prior work before duplicating effort
    • Implementation:
      • search_mailbox: src/mcp_agent_mail/app.py:4218-4454
      • search_messages: src/mcp_agent_mail/app.py:4646-4767

Example usage:

# Search with relevance ranking
results = await search_mailbox(
    query="authentication bug",
    agent="MyAgent",
    project_key="my-project",
    limit=10
)

# Results sorted by relevance (BM25)
for result in results:
    print(f"Score: {result['score']}, Subject: {result['subject']}")

📝 How Messages Are Stored

Every message sent through MCP Agent Mail is stored in two places for redundancy and auditability:

1. Git Repository Archive (.mcp_mail/ by default - committed to your project)

.mcp_mail/                                  # ← Inside your project directory
└── projects/
    └── <project-slug>/
        ├── messages/
        │   └── YYYY/
        │       └── MM/
        │           └── <message-id>.md     # Canonical message with frontmatter
        ├── agents/
        │   └── mailboxes/
        │       ├── <agent-name>/
        │       │   ├── inbox/<msg-id>.md   # Symlink to canonical message
        │       │   └── outbox/<msg-id>.md  # Symlink to canonical message
        ├── attachments/
        │   └── <hash-prefix>/
        │       └── <sha1>.webp             # Images converted to WebP
        └── file_reservations/
            └── <sha1>.json                 # File lock metadata

✅ Benefits of project-local storage (default):

  • Transparent collaboration: All agent conversations committed alongside code
  • Code review: Review agent decisions as part of PR reviews
  • Audit trail: Full Git history of agent coordination
  • Portable context: Clone repo and see all agent communications
  • Team sharing: Everyone sees the same agent conversation history

2. SQLite Database (.mcp_mail/storage.sqlite3 - local only, not committed)

  • Full-text search indexes (FTS5)
  • Message metadata (sender, recipients, timestamps)
  • Agent directory and profiles
  • File reservation tracking
  • Fast queries without scanning Git history
  • Note: SQLite database is gitignored (.mcp_mail/*.db*) - only messages are committed

🔄 Git Commit Flow

When an agent sends a message via send_message, here's what happens:

  1. Message written to messages/YYYY/MM/<id>.md with YAML frontmatter
  2. Inbox/outbox copies created as symlinks in each agent's mailbox
  3. Git add all new files (canonical message + symlinks)
  4. Git commit with structured message:
    mail: SenderAgent -> RecipientAgent | Subject Line
    
    TOOL: send_message
    Agent: SenderAgent
    Project: myproject
    Started: 2025-11-11T10:30:00Z
    Status: SUCCESS
    Thread: thread-id-123
    
  5. Commit trailers automatically appended (Agent, Thread) for traceability

Key benefits:

  • Human-auditable: git log shows all agent communication
  • Diffable: git diff to see message changes over time
  • Blameable: git blame traces who sent what and when
  • Reversible: git revert to undo problematic messages
  • Portable: Clone the repo to backup or share message history

Configuration:

  • Storage location: STORAGE_ROOT env var (default: .mcp_mail)
    • Project-local: set STORAGE_LOCAL_ARCHIVE_ENABLED=true to store in .mcp_mail - messages stored in project directory and committed to Git
    • Project-key anchored: set STORAGE_PROJECT_KEY_ENABLED=true to store under <project_key>/.mcp_mail (project_key must be the git repo root)
    • Default behavior: Both storage modes disabled by default; must explicitly enable one to use MCP Agent Mail
    • Global alternative: ~/.mcp_agent_mail_git_mailbox_repo - messages stored in user home directory
  • Git author: GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL env vars

Messages are automatically committed to Git:

# Just run the server - messages go to .mcp_mail/ by default
uv run python -m mcp_agent_mail.http

# Messages stored in ./mcp_mail/projects/<slug>/messages/
# Commit to share: git add .mcp_mail && git commit -m "Add agent coordination messages"

# Ensure git hooks are installed (enforces Ruff/ty/Bandit on commit)
./scripts/ensure_git_hooks.sh

🔒 Want private messages? Use global storage instead:

# Set STORAGE_ROOT to use global directory (not committed to Git)
STORAGE_ROOT=~/.mcp_agent_mail_git_mailbox_repo uv run python -m mcp_agent_mail.http

# Use project-key anchored storage (project_key must be repo root)
STORAGE_PROJECT_KEY_ENABLED=true uv run python -m mcp_agent_mail.http

Summary: This fork removes coordination barriers by making everything global by default - agents, messages, and projects all work across boundaries. Combined with advanced search with BM25 relevance ranking, auto-fetch inbox on registration, automatic @mention scanning, and project-local storage (.mcp_mail/ directory), it creates a frictionless multi-agent collaboration platform while maintaining full Git auditability for all communications.


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 (YouTube link) 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, 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

curl -fsSL https://raw.githubusercontent.com/jleechanorg/mcp_mail/main/scripts/install.sh | bash -s -- --yes

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/ (including run_server_with_token.sh)
  • Installs/updates, verifies, and wires the Beads bd CLI into your PATH via its official curl installer so the task planner is ready out of the box (pass --skip-beads to opt out or install manually)
  • 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 installed or want to handle it yourself? Append --skip-beads to the installer command to bypass the automatic bd setup and PATH wiring.

Port conflicts? Use --port to specify a different port (default: 8765):

# Install with custom port
curl -fsSL https://raw.githubusercontent.com/jleechanorg/mcp_mail/main/scripts/install.sh | 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)
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

# Clone the repo
git clone https://github.com/jleechanorg/mcp_mail
cd mcp_mail

# Create a Python 3.14 virtual environment and install dependencies
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=<abs-path>&limit=20` or `resource://thread/{id}?project=<abs-path>&include_bodies=true`.
   - Tip: set `AGENT_NAME` in your environment so the pre-commit guard can block commits that conflict with others' active exclusive file reservations.

2) Across different repos in one project (e.g., Next.js frontend + FastAPI backend)
   - Option A (single project bus): register both sides under the same `project_key` (shared key/path). Keep reservation patterns specific (e.g., `frontend/**` vs `backend/**`).
   - Option B (separate projects): each repo has its own `project_key`; agents can message each other directly using `send_message` with cross-project addressing. 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 (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 bd CLI via prebuilt release or Go build; see the repository for platform specifics.

Copy/paste blurb for agent-facing docs (leave as-is for reuse):


## Integrating with Beads (dependency-aware task planning)

Beads provides a lightweight, dependency-aware issue database and a CLI (`bd`) for selecting "ready work," setting priorities, and tracking status. It complements MCP Agent Mail's messaging, audit trail, and file-reservation signals. Project: [steveyegge/beads](https://github.com/steveyegge/beads)

Recommended conventions
- **Single source of truth**: Use **Beads** for task status/priority/dependencies; use **Agent Mail** for conversation, decisions, and attachments (audit).
- **Shared identifiers**: Use the Beads issue id (e.g., `bd-123`) as the Mail `thread_id` and prefix message subjects with `[bd-123]`.
- **Reservations**: When starting a `bd-###` task, call `file_reservation_paths(...)` for the affected paths; include the issue id in the `reason` and release on completion.

Typical flow (agents)
1) **Pick ready work** (Beads)
   - `bd ready --json` → choose one item (highest priority, no blockers)
2) **Reserve edit surface** (Mail)
   - `file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true, reason="bd-123")`
3) **Announce start** (Mail)
   - `send_message(..., thread_id="bd-123", subject="[bd-123] Start: <short title>", ack_required=true)`
4) **Work and update**
   - Reply in-thread with progress and attach artifacts/images; keep the discussion in one thread per issue id
5) **Complete and release**
   - `bd close bd-123 --reason "Completed"` (Beads is status authority)
   - `release_file_reservations(project_key, agent_name, paths=["src/**"])`
   - Final Mail reply: `[bd-123] Completed` with summary and links

Mapping cheat-sheet
- **Mail `thread_id`** ↔ `bd-###`
- **Mail subject**: `[bd-###] …`
- **File reservation `reason`**: `bd-###`
- **Commit messages (optional)**: include `bd-###` for traceability

Event mirroring (optional automation)
- On `bd update --status blocked`, send a high-importance Mail message in thread `bd-###` describing the blocker.
- On Mail "ACK overdue" for a critical decision, add a Beads label (e.g., `needs-ack`) or bump priority to surface it in `bd ready`.

Pitfalls to avoid
- Don't create or manage tasks in Mail; treat Beads as the single task queue.
- Always include `bd-###` in message `thread_id` to avoid ID drift across tools.

Prefer automation? Run uv run python -m mcp_agent_mail.cli docs insert-blurbs to scan your code directories for AGENTS.md/CLAUDE.md files and append the latest Agent Mail + Beads snippets with per-project confirmation. The installer also offers to launch this helper right after setup so you can take care of onboarding docs immediately.

Slack Integration

MCP Agent Mail includes full Slack integration, enabling agents to send notifications and post messages to Slack channels. See SLACK_BIDIRECTIONAL_SYNC.md for complete documentation.

Quick Setup:

  1. Create a Slack app at https://api.slack.com/apps
  2. Add bot token scopes: chat:write, channels:read, groups:read, channels:history, groups:history, reactions:read, app_mentions:read
  3. Install to workspace and copy the bot token
  4. Configure in .env:
    SLACK_ENABLED=true
    SLACK_BOT_TOKEN=xoxb-your-token-here
    SLACK_DEFAULT_CHANNEL=general
    
  5. Restart the server

Features:

  • Automatic Notifications: MCP messages automatically posted to Slack
  • Manual Posting: slack_post_message() tool for direct Slack posts
  • Channel Discovery: slack_list_channels() to explore available channels
  • Channel Insights: slack_get_channel_info() to inspect metadata and member counts before posting
  • Thread Support: MCP threads map to Slack threads for conversation continuity
  • Rich Formatting: Uses Slack Block Kit for enhanced message presentation
  • Webhook Support: Receive Slack events for future bidirectional sync

Example:

# Automatically notify Slack when sending an MCP message
send_message(
    project_key="myproject",
    agent_name="BlueWhale",
    to=["GreenCastle"],
    subject="Deploy completed",
    body_md="Production deployment successful ✅",
    importance="normal"
)
# → Slack notification appears in configured channel

# Or post directly to Slack
slack_post_message(
    channel="deployments",
    text="🚀 Build #1234 deployed to production"
)

See SLACK_BIDIRECTIONAL_SYNC.md for full configuration options, MCP tools reference, webhook setup, and troubleshooting.

Slack Integration

MCP Agent Mail includes full Slack integration, enabling agents to send notifications and post messages to Slack channels. See SLACK_BIDIRECTIONAL_SYNC.md for complete documentation.

Quick Setup:

  1. Create a Slack app at https://api.slack.com/apps
  2. Add bot token scopes: chat:write, channels:read, groups:read, channels:history, groups:history, reactions:read, app_mentions:read
  3. Install to workspace and copy the bot token
  4. Configure in .env:
    SLACK_ENABLED=true
    SLACK_BOT_TOKEN=xoxb-your-token-here
    SLACK_DEFAULT_CHANNEL=general
    
  5. Restart the server

Features:

  • Automatic Notifications: MCP messages automatically posted to Slack
  • Manual Posting: slack_post_message() tool for direct Slack posts
  • Channel Discovery: slack_list_channels() to explore available channels
  • Channel Insights: slack_get_channel_info() to inspect metadata and member counts before posting
  • Thread Support: MCP threads map to Slack threads for conversation continuity
  • Rich Formatting: Uses Slack Block Kit for enhanced message presentation
  • Webhook Support: Receive Slack events for future bidirectional sync

Example:

# Automatically notify Slack when sending an MCP message
send_message(
    project_key="myproject",
    agent_name="BlueWhale",
    to=["GreenCastle"],
    subject="Deploy completed",
    body_md="Production deployment successful ✅",
    importance="normal"
)
# → Slack notification appears in configured channel

# Or post directly to Slack
slack_post_message(
    channel="deployments",
    text="🚀 Build #1234 deployed to production"
)

See SLACK_BIDIRECTIONAL_SYNC.md for full configuration options, MCP tools reference, webhook setup, and troubleshooting.

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; auto-generated adjective+noun codenames keep things memorable (custom alphanumeric names are also allowed)
  • 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

Architecture

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):

scripts/run_server_with_token.sh
# then open http://127.0.0.1:8765/mail

Advanced (manual commands):

uv run python -m mcp_agent_mail.http --host 127.0.0.1 --port 8765
# or:
uv run uvicorn mcp_agent_mail.http:build_http_app --factory --host 127.0.0.1 --port 8765

Auth notes:

  • 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 for organizational clarity. Agents can message across projects directly without requiring contact approval.
  • /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)
  • ✅ Confirmed links update UI badges for better organization
  • ✅ Agents can message across projects directly without additional approval

The Workflow

1. System suggests: "These projects look related" (AI analysis)
           ↓
2. You confirm: "Yes, link them" (updates UI badges for clarity)
           ↓
3. Agents communicate: send_message with cross-project addressing

NOTE: Contact approval is no longer required. Agents can send messages across projects directly.

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.

Quick Start: Interactive Deployment Wizard

The easiest way to export and deploy is the interactive wizard, which supports both GitHub Pages and Cloudflare Pages:

# Via CLI (recommended)
uv run python -m mcp_agent_mail.cli share wizard

# Or run the script directly
./scripts/share_to_github_pages.py

What the wizard does

The wizard provides a fully automated end-to-end deployment experience:

  1. Configuration management: Remembers your last settings and offers to reuse them, saving time on repeated exports
  2. Deployment target selection: Choose between GitHub Pages, Cloudflare Pages, or local export
  3. Automatic CLI installation: Detects and installs missing tools (gh for GitHub, wrangler for Cloudflare)
  4. Guided authentication: Step-by-step browser login flows for GitHub and Cloudflare
  5. 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
  6. Redaction configuration: Choose between standard (scrub secrets like API keys/tokens, keep agent names) or strict (redact all message bodies)
  7. Cryptographic signing: Optional Ed25519 signing with automatic key generation or use your existing key
  8. Pre-flight validation: Checks that GitHub repo names are available before starting the export
  9. Deployment summary: Shows what will be deployed (project count, bundle size, target, signing status) and asks for confirmation
  10. Export and preview: Exports the bundle and launches a local preview server with automatic port detection (tries 9000-9100)
  11. Real-time deployment: Streams git and deployment output in real-time so you can follow the progress
  12. Automatic deployment: Creates repos, enables Pages, pushes code, and gives you the live URL

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)

Configuration is project-agnostic: if you add or remove projects, the wizard validates saved indices and prompts for re-selection if needed.

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

# 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.

# Refresh bundle using the originally recorded settings
uv run python -m mcp_agent_mail.cli share update ./my-bundle

# Override one or more export options while updating
uv run python -m mcp_agent_mail.cli share update ./my-bundle \
  --project backend-abc123 \
  --inline-threshold 16384 \
  --chunk-threshold 104857600

# Re-sign and package the refreshed bundle
uv run python -m mcp_agent_mail.cli share update ./my-bundle \
  --zip \
  --signing-key ./keys/signing.key

When chunking was enabled previously but the refreshed snapshot no longer needs it, share update cleans up the chunks/ directory, chunks.sha256, and mailbox.sqlite3.config.json automatically, ensuring the bundle tree matches the new manifest. You can still tweak any setting at update time; overrides are written back into the export_config section of manifest.json for the next refresh.

2. Preview locally

# Serve the bundle on localhost:9000
uv run python -m mcp_agent_mail.cli share preview ./my-bundle

# Custom port and auto-open browser
uv run python -m mcp_agent_mail.cli share preview ./my-bundle \
  --port 8080 \
  --open-browser

This launches a lightweight HTTP server that serves the static files. Open http://127.0.0.1:9000/ in your browser to explore the archive.

3. Verify integrity

# Verify SRI hashes and signature
uv run python -m mcp_agent_mail.cli share verify ./my-bundle

# Verify with explicit public key (overrides manifest.sig.json)
uv run python -m mcp_agent_mail.cli share verify ./my-bundle \
  --public-key AAAA...base64...

Verification checks:

  • 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)

# 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 + Trusted Types)

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. Trusted Types enforces that all innerHTML assignments use TrustedHTML objects
  4. Content Security Policy blocks inline scripts, external resources, and unsafe-eval

This prevents malicious content in message bodies from executing JavaScript or exfiltrating data.

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)
  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

Encryption (age)

The age encryption tool (https://age-encryption.org/) provides modern, secure file encryption. When you provide recipient public keys, the export process encrypts the final ZIP archive. Only holders of the corresponding private keys can decrypt it.

Generate keys with:

# Install age (example for macOS)
brew install age

# Generate a new key pair
age-keygen -o key.txt

# Public key is printed to stdout; share it with exporters
# Private key is saved to key.txt; keep it secret

Redaction presets

The export pipeline supports configurable scrubbing to remove sensitive data:

  • standard: Clears acknowledgment/read state, removes file reservations and agent links, scrubs secrets (GitHub tokens, Slack tokens, OpenAI keys, bearer tokens, JWTs) from message bodies and attachment metadata. Retains agent names (which are already meaningless pseudonyms like "BlueMountain"), full message bodies, and attachments.

  • strict: All standard redactions plus replaces entire message bodies with "[Message body redacted]" placeholder and removes all attachments from the bundle.

All presets apply redaction to message subjects, bodies, and attachment metadata before the bundle is written.

Static viewer features

The bundled HTML viewer provides:

Dashboard layout:

  • Bundle metadata header: Shows bundle creation time, export settings, and scrubbing preset
  • Summary panels: Three side-by-side panels displaying projects included, attachment statistics, and redaction summary
  • Message overview: Searchable list of all messages with filtering by subject/body
  • Raw manifest viewer: Collapsible JSON display of the complete manifest for verification

Full-text search: Powered by SQLite FTS5, runs entirely in the browser. Search syntax supports phrases ("build plan"), boolean operators (plan AND users), and field filters (subject:deploy).

Markdown rendering: Message bodies are rendered with GitHub-Flavored Markdown, supporting code blocks, tables, task lists, and inline images.

OPFS caching: The SQLite database is cached in Origin Private File System (OPFS) for instant subsequent loads. First load downloads the database, subsequent loads are instant.

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 view its full body, metadata (sender, recipients, timestamp, importance), and 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.

Deployment options

Option 1: GitHub Pages (automated via wizard)

# Use the wizard for fully automated deployment
uv run python -m mcp_agent_mail.cli share wizard
# Select: GitHub Pages → provide repo name → wizard handles everything

Or manually:

# Export and unzip
uv run python -m mcp_agent_mail.cli share export --output ./bundle --no-zip
cd bundle

# Initialize git and push to GitHub
git init
git add .
git commit -m "Initial export"
git remote add origin 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)

# Use the wizard for instant global CDN deployment
uv run python -m mcp_agent_mail.cli share wizard
# Select: Cloudflare Pages → provide project name → wizard deploys directly

Or manually with wrangler CLI:

# Export and deploy
uv run python -m mcp_agent_mail.cli share export --output ./bundle --no-zip
npx wrangler pages deploy ./bundle --project-name=project-archive

# Your site is live at: https://project-archive.pages.dev

Benefits of Cloudflare Pages:

  • 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

# Export and unzip
uv run python -m mcp_agent_mail.cli share export --output ./bundle --no-zip

# Upload to S3
aws s3 sync ./bundle s3://your-bucket/archives/project-2024/ --acl public-read

# Access via CloudFront
# https://d123abc.cloudfront.net/archives/project-2024/

Option 4: Nginx static site

server {
  listen 443 ssl;
  server_name archives.example.com;

  ssl_certificate /etc/letsencrypt/live/archives.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/archives.example.com/privkey.pem;

  root /var/www/archives/project-2024;
  index index.html;

  # Enable gzip for efficient transfer
  gzip on;
  gzip_types text/html text/css application/javascript application/json application/wasm;

  # Cache static assets
  location ~* \.(js|css|wasm|png|jpg|webp)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  # CSP headers are already in index.html meta tag
  # Add HTTPS-only and frame protection
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  add_header X-Frame-Options "DENY" always;
  add_header X-Content-Type-Options "nosniff" always;
}

Option 5: Encrypted distribution via file sharing

For confidential archives:

# Export with age encryption
uv run python -m mcp_agent_mail.cli share export \
  --output ./bundle \
  --signing-key ./signing.key \
  --age-recipient age1auditor... \
  --age-recipient age1manager...

# This produces bundle.zip.age
# Upload to Dropbox, Google Drive, or send via secure file transfer

# Recipients decrypt locally
uv run python -m mcp_agent_mail.cli share decrypt bundle.zip.age \
  --identity ~/.age/key.txt

# Verify integrity
unzip bundle.zip
uv run python -m mcp_agent_mail.cli share verify ./bundle

# Preview locally
uv run python -m mcp_agent_mail.cli share preview ./bundle

Example workflows

Quarterly audit package

# Export Q4 2024 communications for audit
uv run python -m mcp_agent_mail.cli share export \
  --output ./audit-q4-2024 \
  --scrub-preset strict \
  --signing-key ./audit-signing.key \
  --signing-public-out ./audit-signing.pub \
  --age-recipient 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

# Export high-importance threads only
# (filter in UI after export, or use SQL to create filtered snapshot)
uv run python -m mcp_agent_mail.cli share export \
  --output ./exec-summary \
  --project backend-prod \
  --scrub-preset standard

# Host on internal web server
cp -r ./exec-summary /var/www/exec-archives/2024-12/
# Share link: https://internal.example.com/exec-archives/2024-12/

Disaster recovery backup

# Monthly encrypted backup
uv run python -m mcp_agent_mail.cli share export \
  --output ./backup-$(date +%Y-%m) \
  --scrub-preset none \
  --signing-key ./dr-signing.key \
  --age-recipient 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.

# Bundle files under 1MB, mark larger files as external
uv run python -m mcp_agent_mail.cli share export \
  --output ./bundle \
  --detach-threshold 1048576

Alternatively, filter to specific projects with --project.

Encrypted bundle won't decrypt

Verify you're using the correct identity file:

# Get your public key from your identity file
age-keygen -y identity.txt

# Ensure this public key was included in the --age-recipient values during export
# If you have multiple identity files, try each one
age -d -i identity.txt bundle.zip.age > bundle.zip

Signature verification fails

Signature verification requires:

  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)

<store>/projects/<slug>/
  agents/<AgentName>/profile.json
  agents/<AgentName>/inbox/YYYY/MM/<msg-id>.md
  agents/<AgentName>/outbox/YYYY/MM/<msg-id>.md
  messages/YYYY/MM/<msg-id>.md
  messages/threads/<thread-id>.md  # optional human digest maintained by the server
  file_reservations/<sha1-of-path>.json
  attachments/<xx>/<sha1>.webp

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.

---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)
  • 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)
  • 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/<slug>/
  • 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.
  1. 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.
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
  1. 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.
  1. 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.
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
  1. 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 can be any sanitized alphanumeric string and are globally unique; if you omit name (or it conflicts), the server generates an adjective+noun codename, and any name_hint is sanitized (alnum) before use
    • Reusing a name automatically retires the previous identity (history stays readable) so you can reclaim favorite handles without manual cleanup
    • 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 paths resolve relative to the project repo root
    • Stored under attachments/<xx>/<sha1>.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 global agent namespace

Goal: make coordination "just work" without barriers between agents. Agent names are globally unique across all projects, enabling seamless communication.

Global agent namespace

  • Agent names are globally unique (case-insensitive). A name like "BlueLake" can only exist once across the entire system.
  • Agents can discover and message any other agent regardless of project boundaries using resource://agents (canonical global directory).
  • Projects serve as organizational containers for archiving and context, not as access control boundaries.

Projects as organizational units

  • All tools still use a project_key for message archival and organization.
  • Messages are stored in project-specific archives for organizational purposes.
  • Agents see messages addressed to them globally, regardless of which project the sender belongs to.

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. All messages are archived together in one location.

  2. Keep separate project_keys for organizational separation. Agents can still message each other directly by name since agent names are globally unique.

NOTE: No special addressing is required for cross-project messaging. Simply use the agent name in the to field of send_message. The project:<slug>#<AgentName> format is still supported for explicit cross-project addressing but is optional.

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:

{
  "method": "resources/read",
  "params": {"uri": "resource://inbox/BlueLake?project=/abs/path/backend&limit=20"}
}
sequenceDiagram
  participant Client
  participant Server
  participant DB

  Client->>Server: read inbox resource
  Server->>DB: select messages for agent
  DB-->>Server: rows
  Server-->>Client: inbox data

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_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.

Identity and worktree mode (opt-in)

  • Gate: WORKTREES_ENABLED=1 enables worktree-friendly 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}
    • CLI (diagnostics): mcp-agent-mail mail status /abs/path

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
  • Build slots (advisory, per-project coarse locking):

    • 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>.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" --limit 50

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/<sha1(path)>.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):

{
  "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
curl -fsSL https://raw.githubusercontent.com/jleechanorg/mcp_mail/main/scripts/install.sh | bash -s -- --port 9000 --yes

# Or with local script
./scripts/install.sh --port 9000 --yes

Option 2: After installation (CLI)

# Change port using CLI command
uv run python -m mcp_agent_mail.cli config set-port 9000

# View current port configuration
uv run python -m mcp_agent_mail.cli config show-port

# Restart server for changes to take effect
scripts/run_server_with_token.sh

Option 3: Manual .env edit

# Edit .env file manually with your text editor (recommended)
nano .env  # or vim, code, etc.

# Or append (⚠️ warning: will create duplicate if HTTP_PORT already exists)
echo "HTTP_PORT=9000" >> .env

Option 4: CLI server override

# Override port at server startup (doesn't modify .env)
uv run python -m mcp_agent_mail.cli serve-http --port 9000
from decouple import Config as DecoupleConfig, RepositoryEnv

decouple_config = DecoupleConfig(RepositoryEnv(".env"))

STORAGE_ROOT = decouple_config("STORAGE_ROOT", default=".mcp_mail")
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:

Tool Exposure Modes (Lazy Loading)

The server supports two tool exposure modes to manage token usage:

Core Mode (Default)

  • Exposes 10 essential tools: health_check, ensure_project, register_agent, whois, send_message, reply_message, fetch_inbox, mark_message_read, list_extended_tools, call_extended_tool
  • ~10k tokens initial context
  • Extended tools (19 additional tools) remain accessible via the call_extended_tool meta-tool
  • Best for most use cases where you want to minimize initial context usage

Extended Mode

  • Exposes all 29 tools directly to the MCP client
  • ~25k tokens initial context
  • No need to use meta-tools; all tools are available directly
  • Best for workflows that heavily use many advanced features

Switching modes

# Set to extended mode (expose all tools)
echo "MCP_TOOLS_MODE=extended" >> .env

# Set to core mode (lazy loading, default)
echo "MCP_TOOLS_MODE=core" >> .env
# Or simply remove the line (core is now the default)

# Restart server for changes to take effect
scripts/run_server_with_token.sh

Using extended tools in core mode

# Instead of calling the tool directly:
await client.call_tool("acknowledge_message", {"project_key": "...", "agent_name": "...", "message_id": 123})

# Use the meta-tool in core mode:
await client.call_tool("call_extended_tool", {
    "tool_name": "acknowledge_message",
    "arguments": {"project_key": "...", "agent_name": "...", "message_id": 123}
})

# List all extended tools:
result = await client.call_tool("list_extended_tools", {})
# Returns: {"total": 19, "by_category": {...}, "tools": [{name, category, description}, ...]}

Configuration reference

| Name | Default | Description | | :-- | :-- | :-- | | STORAGE_ROOT | .mcp_mail | Root for per-project repos and SQLite DB (project-local by default) | | STORAGE_PROJECT_KEY_ENABLED | false | When true, use <project_key>/.mcp_mail as archive root (project_key must be git repo root) | | STORAGE_LOCAL_ARCHIVE_ENABLED | false | When true, enable .mcp_mail archive; when false, requires project-key storage | | HTTP_HOST | 127.0.0.1 | Bind host for HTTP transport | | HTTP_PORT | 8765 | Bind port for HTTP transport | | HTTP_PATH | /mcp/ | HTTP path where MCP endpoint is mounted | | HTTP_JWT_ENABLED | false | Enable JWT validation middleware | | HTTP_JWT_SECRET | | HMAC secret for HS* algorithms (dev) | | HTTP_JWT_JWKS_URL | | JWKS URL for public key verification | | HTTP_JWT_ALGORITHMS | HS256 | CSV of allowed algs | | HTTP_JWT_AUDIENCE | | Expected aud (optional) | | HTTP_JWT_ISSUER | | Expected iss (optional) | | HTTP_JWT_ROLE_CLAIM | role | JWT claim name containing role(s) | | HTTP_RBAC_ENABLED | true | Enforce read-only vs tools RBAC | | HTTP_RBAC_READER_ROLES | reader,read,ro | CSV of reader roles | | HTTP_RBAC_WRITER_ROLES | writer,write,tools,rw | CSV of writer roles | | HTTP_RBAC_DEFAULT_ROLE | reader | Role used when none present | | HTTP_RBAC_READONLY_TOOLS | health_check,fetch_inbox,whois,search_messages,summarize_thread,summarize_threads | CSV of read-only tool names | | HTTP_RATE_LIMIT_ENABLED | false | Enable token-bucket limiter | | HTTP_RATE_LIMIT_BACKEND | memory | memory or redis | | HTTP_RATE_LIMIT_PER_MINUTE | 60 | Legacy per-IP limit (fallback) | | HTTP_RATE_LIMIT_TOOLS_PER_MINUTE | 60 | Per-minute for tools/call | | HTTP_RATE_LIMIT_TOOLS_BURST | 0 | Optional burst for tools (0=auto=rpm) | | HTTP_RATE_LIMIT_RESOURCES_PER_MINUTE | 120 | Per-minute for resources/read | | HTTP_RATE_LIMIT_RESOURCES_BURST | 0 | Optional burst for resources (0=auto=rpm) | | HTTP_RATE_LIMIT_REDIS_URL | | Redis URL for multi-worker limits | | HTTP_REQUEST_LOG_ENABLED | false | Print request logs (Rich + JSON) | | LOG_JSON_ENABLED | false | Output structlog JSON logs | | INLINE_IMAGE_MAX_BYTES | 65536 | Threshold (bytes) for inlining WebP images during send_message | | CONVERT_IMAGES | true | Convert images to WebP (and optionally inline small ones) | | KEEP_ORIGINAL_IMAGES | false | Also store original image bytes alongside WebP (attachments/originals/) | | LOG_LEVEL | INFO | Server log level | | HTTP_CORS_ENABLED | false | Enable CORS middleware when true | | HTTP_CORS_ORIGINS | | CSV of allowed origins (e.g., https://app.example.com,https://ops.example.com) | | HTTP_CORS_ALLOW_CREDENTIALS | false | Allow credentials on CORS | | HTTP_CORS_ALLOW_METHODS | * | CSV of allowed methods or * | | HTTP_CORS_ALLOW_HEADERS | * | CSV of allowed headers or * | | HTTP_BEARER_TOKEN | | Static bearer token (only when JWT disabled) | | HTTP_ALLOW_LOCALHOST_UNAUTHENTICATED | true | Allow localhost requests without auth (dev convenience) | | HTTP_OTEL_ENABLED | false | Enable OpenTelemetry instrumentation | | OTEL_SERVICE_NAME | mcp-agent-mail | Service name for telemetry | | OTEL_EXPORTER_OTLP_ENDPOINT | | OTLP exporter endpoint URL | | APP_ENVIRONMENT | development | Environment name (development/production) | | DATABASE_URL | sqlite+aiosqlite:///./.mcp_mail/storage.sqlite3 | SQLAlchemy async database URL (stored in .mcp_mail/) | | DATABASE_ECHO | false | Echo SQL statements for debugging | | GIT_AUTHOR_NAME | mcp-agent | Git commit author name | | GIT_AUTHOR_EMAIL | mcp-agent@example.com | Git commit author email | | LLM_ENABLED | true | Enable LiteLLM for thread summaries and discovery | | LLM_DEFAULT_MODEL | gpt-5-mini | Default LiteLLM model identifier | | LLM_TEMPERATURE | 0.2 | LLM temperature for text generation | | LLM_MAX_TOKENS | 512 | Max tokens for LLM completions | | LLM_CACHE_ENABLED | true | Enable LLM response caching | | LLM_CACHE_BACKEND | memory | LLM cache backend (memory or redis) | | LLM_CACHE_REDIS_URL | | Redis URL for LLM cache (if backend=redis) | | LLM_COST_LOGGING_ENABLED | true | Log LLM API costs and token usage | | FILE_RESERVATIONS_CLEANUP_ENABLED | false | Enable background cleanup of expired file reservations | | FILE_RESERVATIONS_CLEANUP_INTERVAL_SECONDS | 60 | Interval for file reservations cleanup task | | FILE_RESERVATION_INACTIVITY_SECONDS | 1800 | Inactivity threshold (seconds) before a reservation is considered stale | | FILE_RESERVATION_ACTIVITY_GRACE_SECONDS | 900 | Grace window for recent mail/filesystem/git activity to keep a reservation active | | FILE_RESERVATIONS_ENFORCEMENT_ENABLED | true | Block message writes on conflicting file reservations | | ACK_TTL_ENABLED | false | Enable overdue ACK scanning (logs/panels; see views/resources) | | ACK_TTL_SECONDS | 1800 | Age threshold (seconds) for overdue ACKs | | ACK_TTL_SCAN_INTERVAL_SECONDS | 60 | Scan interval for overdue ACKs | | ACK_ESCALATION_ENABLED | false | Enable escalation for overdue ACKs | | ACK_ESCALATION_MODE | log | log or file_reservation escalation mode | | ACK_ESCALATION_CLAIM_TTL_SECONDS | 3600 | TTL for escalation file reservations | | ACK_ESCALATION_CLAIM_EXCLUSIVE | false | Make escalation file reservation exclusive | | ACK_ESCALATION_CLAIM_HOLDER_NAME | | Ops agent name to own escalation file reservations | | MESSAGING_AUTO_REGISTER_RECIPIENTS | true | Automatically create missing local recipients during send_message and retry routing | | TOOLS_LOG_ENABLED | true | Log tool invocations for debugging | | LOG_RICH_ENABLED | true | Enable Rich console logging | | LOG_INCLUDE_TRACE | false | Include trace-level logs | | TOOL_METRICS_EMIT_ENABLED | false | Emit periodic tool usage metrics | | TOOL_METRICS_EMIT_INTERVAL_SECONDS | 60 | Interval for metrics emission | | RETENTION_REPORT_ENABLED | false | Enable retention/quota reporting | | RETENTION_REPORT_INTERVAL_SECONDS | 3600 | Interval for retention reports (1 hour) | | RETENTION_MAX_AGE_DAYS | 180 | Max age for retention policy reporting | | QUOTA_ENABLED | false | Enable quota enforcement | | QUOTA_ATTACHMENTS_LIMIT_BYTES | 0 | Max attachment storage per project (0=unlimited) | | QUOTA_INBOX_LIMIT_COUNT | 0 | Max inbox messages per agent (0=unlimited) | | RETENTION_IGNORE_PROJECT_PATTERNS | demo,test*,testproj*,testproject,backendproj*,frontendproj* | CSV of project patterns to ignore in retention/quota reports | | AGENT_NAME_ENFORCEMENT_MODE | coerce | Agent naming policy: strict (reject invalid/taken sanitized names), coerce (auto-generate when invalid or taken), always_auto (always auto-generate) | | MCP_TOOLS_MODE | core | Tool exposure mode: core (10 essential tools + meta-tools for lazy loading ~10k tokens) or extended (all 29 tools exposed directly ~25k tokens). Core mode reduces initial context by exposing only essential coordination tools, with extended tools accessible via call_extended_tool meta-tool. |

Development quick start

Prerequisite: complete the setup above (Python 3.11 + uv venv + uv sync).

Dev helpers:

# Quick endpoint smoke test (server must be running locally)
bash scripts/test_endpoints.sh

# Pre-commit guard smoke test (no pytest)
bash scripts/test_guard.sh

Database schema (automatic):

# Tables are created from SQLModel definitions on first run.
# If models change, delete the SQLite DB (and WAL/SHM) and run migrate again.
uv run python -m mcp_agent_mail.cli migrate

Run the server (HTTP-only). Use the Typer CLI or module entry:

uv run python -m mcp_agent_mail.cli serve-http
uv run python -m mcp_agent_mail.http --host 127.0.0.1 --port 8765

Connect with your MCP client using the HTTP (Streamable HTTP) transport on the configured host/port. The endpoint tolerates both /mcp and /mcp/.

Search syntax tips (SQLite FTS5)

  • Basic terms: plan users
  • Phrase search: "build plan"
  • Prefix search: mig*
  • Boolean operators: plan AND users NOT legacy
  • Field 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=true
    • Static 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 matching kid header; tokens without kid or with unknown kid are rejected
    • Starter 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.lock prevents cross-project head-of-line blocking
    • Commits: repo-level .commit.lock serializes 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_agent or create_agent_identity. Agent names are globally unique, so ensure the sender name exists in the system.
  • Pre-commit hook blocks commits
    • Set AGENT_NAME to 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's INLINE_IMAGE_MAX_BYTES threshold (default 64KB). Larger images are stored as attachments instead.
  • Message not found
    • Message IDs may collide across projects. Use resource://message/{id}?project={project_key} when in doubt; bare resource://message/{id} works only when the id is unique.
  • Inbox empty but messages exist
    • Check since_ts, urgent_only, and limit; 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 both granted and conflicts. 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 do auto-generated names look like adjective+noun combos?

    • When you skip name (or it conflicts), the server falls back to a curated adjective/noun pool to create memorable codenames. You're still free to supply any sanitized alphanumeric name if you want a custom identity.
  • Why is project_key an 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 | | :-- | :-- | :-- | :-- | | health_check | health_check() | {status, environment, http_host, http_port, database_url} | Lightweight readiness probe | | ensure_project | ensure_project(human_key: str) | {id, slug, human_key, created_at} | Idempotently creates/ensures project | | register_agent | register_agent(project_key: str, program: str, model: str, name?: str, task_description?: str, attachments_policy?: str) | Agent profile dict | Creates/updates agent; writes profile to Git | | whois | whois(project_key: str, agent_name: str, include_recent_commits?: bool, commit_limit?: int) | Agent profile dict | Enriched profile for one agent (optionally includes recent commits) | | create_agent_identity | create_agent_identity(project_key: str, program: str, model: str, name_hint?: str, task_description?: str, attachments_policy?: str) | Agent profile dict | Always creates a new unique agent | | send_message | send_message(project_key: str, sender_name: str, to: list[str], subject: str, body_md: str, cc?: list[str], bcc?: list[str], attachment_paths?: list[str], convert_images?: bool, importance?: str, ack_required?: bool, thread_id?: str, auto_contact_if_blocked?: bool) | {deliveries: list, count: int, attachments?} | Writes canonical + inbox/outbox, converts images | | reply_message | reply_message(project_key: str, message_id: int, sender_name: str, body_md: str, to?: list[str], cc?: list[str], bcc?: list[str], subject_prefix?: str) | {thread_id, reply_to, deliveries: list, count: int, attachments?} | Preserves/creates thread, inherits flags | | fetch_inbox | fetch_inbox(project_key: str, agent_name: str, limit?: int, urgent_only?: bool, include_bodies?: bool, since_ts?: str) | list[dict] | Non-mutating inbox read | | mark_message_read | mark_message_read(project_key: str, agent_name: str, message_id: int) | {message_id, read, read_at} | Per-recipient read receipt | | acknowledge_message | acknowledge_message(project_key: str, agent_name: str, message_id: int) | {message_id, acknowledged, acknowledged_at, read_at} | Sets ack and read | | macro_start_session | macro_start_session(human_key: str, program: str, model: str, task_description?: str, agent_name?: str, file_reservation_paths?: list[str], file_reservation_reason?: str, file_reservation_ttl_seconds?: int, inbox_limit?: int) | {project, agent, file_reservations, inbox} | Orchestrates ensure→register→optional file reservation→inbox fetch | | macro_prepare_thread | macro_prepare_thread(project_key: str, thread_id: str, program: str, model: str, agent_name?: str, task_description?: str, register_if_missing?: bool, include_examples?: bool, inbox_limit?: int, include_inbox_bodies?: bool, llm_mode?: bool, llm_model?: str) | {project, agent, thread, inbox} | Bundles registration, thread summary, and inbox context | | macro_file_reservation_cycle | macro_file_reservation_cycle(project_key: str, agent_name: str, paths: list[str], ttl_seconds?: int, exclusive?: bool, reason?: str, auto_release?: bool) | {file_reservations, released} | File Reservation + optionally release surfaces around a focused edit block | | macro_contact_handshake | macro_contact_handshake(project_key: str, requester|agent_name: str, target|to_agent: str, to_project?: str, reason?: str, ttl_seconds?: int, auto_accept?: bool, welcome_subject?: str, welcome_body?: str) | {request, response, welcome_message} | [OPTIONAL] Automates contact request/approval (no longer required; use send_message directly) | | search_messages | search_messages(project_key: str, query: str, limit?: int) | list[dict] | FTS5 search (bm25) | | summarize_thread | summarize_thread(project_key: str, thread_id: str, include_examples?: bool, llm_mode?: bool, llm_model?: str) | {thread_id, summary, examples} | Extracts participants, key points, actions | | summarize_threads | summarize_threads(project_key: str, thread_ids: list[str], llm_mode?: bool, llm_model?: str, per_thread_limit?: int) | {threads[], aggregate} | Digest across multiple threads (optional LLM refinement) | | install_precommit_guard | install_precommit_guard(project_key: str, code_repo_path: str) | {hook} | Install a Git pre-commit guard in a target repo | | uninstall_precommit_guard | uninstall_precommit_guard(code_repo_path: str) | {removed} | Remove the guard from a repo | | file_reservation_paths | file_reservation_paths(project_key: str, agent_name: str, paths: list[str], ttl_seconds?: int, exclusive?: bool, reason?: str) | {granted: list, conflicts: list} | Advisory leases; Git artifact per path | | release_file_reservations | release_file_reservations(project_key: str, agent_name: str, paths?: list[str], file_reservation_ids?: list[int]) | {released, released_at} | Releases agent's active file reservations | | force_release_file_reservation | force_release_file_reservation(project_key: str, agent_name: str, file_reservation_id: int, notify_previous?: bool, note?: str) | {released, released_at, reservation} | Clears stale reservations using inactivity/mail/fs/git heuristics and notifies the previous holder | | renew_file_reservations | renew_file_reservations(project_key: str, agent_name: str, extend_seconds?: int, paths?: list[str], file_reservation_ids?: list[int]) | {renewed, file reservations[]} | Extend TTL of existing file reservations |

Resources

| URI | Params | Returns | Notes | | :-- | :-- | :-- | :-- | | resource://config/environment | — | {environment, database_url, http} | Inspect server settings | | resource://tooling/directory | — | {generated_at, metrics_uri, clusters[], playbooks[]} | Grouped tool directory + workflow playbooks | | resource://tooling/schemas | — | {tools: {<name>: {required[], optional[], aliases{}}}} | Argument hints for tools | | resource://tooling/metrics | — | {generated_at, tools[]} | Aggregated call/error counts per tool | | resource://tooling/locks | — | {locks[], summary} | Active locks and owners (debug only). Categories: archive (per-project .archive.lock) and custom (e.g., repo .commit.lock). | | resource://agents | — | {agents[], total} | Global agent directory with project metadata and unread counts | | resource://tooling/capabilities/{agent}{?project} | listed| {generated_at, agent, project, capabilities[]} | Capabilities assigned to the agent (see deploy/capabilities/agent_capabilities.json) | | resource://tooling/recent/{window_seconds}{?agent,project} | listed | {generated_at, window_seconds, count, entries[]} | Recent tool usage filtered by agent/project | | resource://projects | — | list[project] | All projects | | resource://project/{slug} | slug | {project..., agents[]} | Project detail + agents | | resource://file_reservations/{slug}{?active_only} | slug, active_only? | list[file reservation] | File reservations plus staleness metadata (heuristics, last activity timestamps) | | resource://message/{id}{?project} | id, project | message | Single message with body | | resource://thread/{thread_id}{?project,include_bodies} | thread_id, project, include_bodies? | {project, thread_id, messages[]} | Thread listing | | resource://inbox/{agent}{?project,since_ts,urgent_only,include_bodies,limit} | listed | {project, agent, count, messages[]} | Inbox listing | | resource://mailbox/{agent}{?project,limit} | project, limit | {project, agent, count, messages[]} | Mailbox listing (recent messages with basic commit ref) | | resource://mailbox-with-commits/{agent}{?project,limit} | project, limit | {project, agent, count, messages[]} | Mailbox listing enriched with commit metadata | | resource://outbox/{agent}{?project,limit,include_bodies,since_ts} | listed | {project, agent, count, messages[]} | Messages sent by the agent | | resource://views/acks-stale/{agent}{?project,ttl_seconds,limit} | listed | {project, agent, ttl_seconds, count, messages[]} | Ack-required older than TTL without ack | | resource://views/urgent-unread/{agent}{?project,limit} | listed | {project, agent, count, messages[]} | High/urgent importance messages not yet read | | resource://views/ack-required/{agent}{?project,limit} | listed | {project, agent, count, messages[]} | Pending acknowledgements for an agent | | resource://views/ack-overdue/{agent}{?project,ttl_minutes,limit} | listed | {project, agent, ttl_minutes, count, messages[]} | Ack-required older than TTL without ack |

Client Integration Guide

  1. Fetch onboarding metadata first. Issue resources/read for resource://tooling/directory (and optionally resource://tooling/metrics) before exposing tools to an agent. Use the returned clusters and playbooks to render a narrow tool palette for the current workflow rather than dumping every verb into the UI.
  2. 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."
  3. Monitor real usage. Periodically pull or subscribe to log streams containing the tool_metrics_snapshot events emitted by the server (or query resource://tooling/metrics) so you can detect high-error-rate tools and decide whether to expose macros or extra guidance.
  4. 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.
  5. 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.

{
  "steps": [
    "resources/read -> resource://tooling/directory",
    "select active cluster (e.g. messaging)",
    "mount tools listed in cluster.tools plus macros if model size <= S",
    "optional: resources/read -> resource://tooling/metrics for dashboard display",
    "optional: resources/read -> resource://tooling/recent/60?agent=<name>&project=<slug> for UI hints"
  ]
}

Monitoring & Alerts

  1. Enable metric emission. Set TOOL_METRICS_EMIT_ENABLED=true and choose an interval (TOOL_METRICS_EMIT_INTERVAL_SECONDS=120 is a good starting point). The server will periodically emit a structured log entry such as:
{
  "event": "tool_metrics_snapshot",
  "tools": [
    {"name": "send_message", "cluster": "messaging", "calls": 42, "errors": 1},
    {"name": "file_reservation_paths", "cluster": "file reservations", "calls": 11, "errors": 0}
  ]
}
  1. 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.
  2. Alert on anomalies. Create a rule that raises when errors / calls exceeds a threshold for any tool (for example 5% over a 5-minute window) so you can decide whether to expose a macro or improve documentation.
  3. Dashboard the clusters. Group by cluster to see where agents are spending time and which workflows might warrant additional macros or guard-rails.

See docs/observability.md for a step-by-step cookbook (Loki/Prometheus example pipelines included), and docs/GUIDE_TO_OPTIMAL_MCP_SERVER_DESIGN.md for a comprehensive design guide covering tool curation, capability gating, security, and observability best practices.

Operations teams can follow docs/operations_alignment_checklist.md, which links to the capability templates in deploy/capabilities/ and the sample Prometheus alert rules in deploy/observability/.


Deployment quick notes

  • Direct uvicorn: uvicorn mcp_agent_mail.http:build_http_app --factory --host 0.0.0.0 --port 8765
  • Python module: python -m mcp_agent_mail.http --host 0.0.0.0 --port 8765
  • Gunicorn: gunicorn -c deploy/gunicorn.conf.py mcp_agent_mail.http:build_http_app --factory
  • Docker: 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.0 builds and pushes a multi-arch Docker image to GHCR under ghcr.io/<owner>/<repo> with latest and 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/*.log and install the provided logrotate config.
  • Environment file path for systemd is /etc/mcp-agent-mail.env (see deploy/systemd/mcp-agent-mail.service).

Container build and multi-arch push

Use Docker Buildx for multi-arch images. Example flow:

# Create and select a builder (once)
docker buildx create --use --name mcp-builder || docker buildx use mcp-builder

# Build and test locally (linux/amd64)
docker buildx build --load -t your-registry/mcp-agent-mail:dev .

# Multi-arch build and push (amd64, arm64)
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t your-registry/mcp-agent-mail:latest \
  -t your-registry/mcp-agent-mail:v0.1.0 \
  --push .

Recommended tags: a moving latest and immutable version tags per release. Ensure your registry login is configured (docker login).

Systemd manual deployment steps

  1. Copy project files to /opt/mcp-agent-mail and ensure permissions (owner appuser).
  2. Place environment file at /etc/mcp-agent-mail.env based on deploy/env/production.env.
  3. Install service file deploy/systemd/mcp-agent-mail.service to /etc/systemd/system/.
  4. Reload systemd and start:
sudo systemctl daemon-reload
sudo systemctl enable mcp-agent-mail
sudo systemctl start mcp-agent-mail
sudo systemctl status mcp-agent-mail

Optional (non-journald log rotation): install deploy/logrotate/mcp-agent-mail into /etc/logrotate.d/ and write logs to /var/log/mcp-agent-mail/*.log via your process manager or app config.

See deploy/gunicorn.conf.py for a starter configuration. For project direction and planned areas, read project_idea_and_guide.md.

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 exist
  • lint / typecheck: developer helpers
  • list-projects [--include-agents]: enumerate projects
  • guard install <project_key> <code_repo_path>: install the pre-commit guard into a repo
  • guard uninstall <code_repo_path>: remove the guard from a repo
  • share 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 settings
  • share preview <bundle_path> [--port N] [--open-browser]: serve a static bundle locally for inspection
  • share 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 bundle
  • config set-port <port>: change the HTTP server port (updates .env)
  • config show-port: display the current configured HTTP port
  • clear-and-reset-everything [--force]: DELETE the SQLite database (incl. WAL/SHM) and WIPE all contents under STORAGE_ROOT (including per-project Git archives). Use only when you intentionally want a clean slate.
  • list-acks --project <key> --agent <name> [--limit N]: list messages requiring acknowledgement for an agent where ack is missing
  • acks pending <project> <agent> [--limit N]: show pending acknowledgements for an agent
  • acks remind <project> <agent> [--min-age-minutes N] [--limit N]: highlight pending ACKs older than a threshold
  • acks overdue <project> <agent> [--ttl-minutes N] [--limit N]: list overdue ACKs beyond TTL
  • file_reservations list <project> [--active-only/--no-active-only]: list file reservations
  • file_reservations active <project> [--limit N]: list active file reservations
  • file_reservations soon <project> [--minutes N]: show file reservations expiring soon

Examples:

# Interactive wizard: export + deploy to GitHub Pages (easiest)
./scripts/share_to_github_pages.py

# Export a static bundle with signing and encryption
uv run python -m mcp_agent_mail.cli share export \
  --output ./bundle \
  --signing-key ./keys/signing.key \
  --age-recipient age1abc...xyz

# Preview a bundle locally
uv run python -m mcp_agent_mail.cli share preview ./bundle --port 9000 --open-browser

# Verify bundle integrity
uv run python -m mcp_agent_mail.cli share verify ./bundle

# Refresh an existing bundle in place with recorded settings
uv run python -m mcp_agent_mail.cli share update ./bundle

# Change server port
uv run python -m mcp_agent_mail.cli config set-port 9000

# Install guard into a repo
uv run python -m mcp_agent_mail.cli guard install /abs/path/backend /abs/path/backend

# List pending acknowledgements for an agent
uv run python -m mcp_agent_mail.cli acks pending /abs/path/backend BlueLake --limit 10

# WARNING: Destructive reset (clean slate)
uv run python -m mcp_agent_mail.cli clear-and-reset-everything --force

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.

Quick Setup
Installation guide for this server

Install Package (if required)

uvx mcp_mail

Cursor configuration (mcp.json)

{ "mcpServers": { "jleechanorg-mcp-mail": { "command": "uvx", "args": [ "mcp_mail" ] } } }