Uses Git repositories for storing human-auditable message artifacts, agent profiles, and claim records with automatic commits for coordination history
Provides fast search capabilities through FTS5 indexing of messages, agent directories, and claims management for multi-agent coordination
mcp-agent-mail
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-claim “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 claims (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.
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 claims/leases
“Directory/LDAP” style queries for agents; memorable adjective+noun names
Advisory claims 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 claims and a pre-commit guard
Searching and summarizing long technical discussions as threads evolve
Architecture
On-disk layout (per project)
Message file format
Messages are GitHub-Flavored Markdown with JSON frontmatter (fenced by ---json
). Attachments are either WebP files referenced by relative path or inline base64 WebP data URIs.
Data model (SQLite)
projects(id, human_key, slug, created_ts, meta)
agents(id, project_id, name, program, model, inception_ts, task, last_active_ts)
messages(id, project_id, thread_id, subject, body_md, from_agent, created_ts, importance, ack_required, attachments_json)
message_recipients(message_id, agent_name, kind, read_ts, ack_ts)
claims(id, project_id, agent_name, path, exclusive, reason, created_ts, expires_ts, released_ts)
fts_messages(subject, body_md)
+ triggers for incremental updates
Concurrency and lifecycle
One request/task = one isolated operation; Git writes are serialized by a lock file in the project repo root
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)
Create an identity
create_agent(project_key, program, model, task_description, name_hint?)
→ creates a memorable name, stores to DB, writesagents/<Name>/profile.json
in Git, and commits.
Send a message
send_message(project_key, from_agent, to[], subject, body_md, cc?, bcc?, importance?, ack_required?, thread_id?, convert_images?)
Writes a canonical message under
messages/YYYY/MM/
, an outbox copy for the sender, and inbox copies for each recipient; commits all artifacts.Optionally converts images (local paths or data URIs) to WebP and embeds small ones inline.
Check inbox
check_my_messages(project_key, agent_name, since_ts?, urgent_only?, include_bodies?, limit?)
returns recent messages, preserving thread_id where available.acknowledge_message(project_key, agent_name, message_id)
marks acknowledgements.
Avoid conflicts with claims (leases)
claim_paths(project_key, agent_name, paths[], ttl_seconds, exclusive, reason)
records an advisory lease in DB and writes JSON claim artifacts in Git; conflicts are reported if overlapping active exclusives exist.release_claims(project_key, agent_name, paths[])
releases active leases. 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 claims.
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, from_agent, reply_to_message_id, body_md, ...)
creates a subject-prefixed reply, preserving or creating a thread.
Semantics & invariants
Identity
Names are memorable adjective+noun and unique per project;
name_hint
is sanitized (alnum) and used if availablewhois
returns the stored profile;list_agents
can filter by recent activitylast_active_ts
is bumped on relevant interactions (messages, inbox checks, etc.)
Threads
Replies inherit
thread_id
from the original; if missing, the reply setsthread_id
to the original message idSubject lines are prefixed (e.g.,
Re:
) for readability in mailboxes
Attachments
Image references (file path or data URI) are converted to WebP; small images embed inline when policy allows
Non-absolute paths resolve relative to the project repo root
Stored under
attachments/<xx>/<sha1>.webp
and referenced by relative path in frontmatter
Claims
TTL-based; exclusive means “please don’t modify overlapping surfaces” for others until expiry or release
Conflict detection is per exact path pattern; shared claims 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
Tools (MCP surface)
Tool | Purpose |
| Register a new agent identity and write
in Git |
| Fetch a profile for one agent |
| Directory-style listing of agents and activity |
| Create canonical + inbox/outbox markdown artifacts and commit |
| Reply to an existing message and continue the thread |
| Pull recent messages for an agent |
| Mark a message as acknowledged by agent |
| Request advisory leases on files/globs |
| Release existing leases |
| FTS5 search over subject/body |
| Extract summary/action items across a thread |
| Install a Git pre-commit guard in a target repo |
| Remove the guard |
Resource layer (read-only URIs)
Expose common reads as resources that clients can fetch:
resource://inbox/{agent}{?project,since_ts,urgent_only,include_bodies,limit}
resource://message/{id}{?project}
resource://thread/{thread_id}{?project,include_bodies}
Example (conceptual) resource read:
Resource parameters
resource://inbox/{agent}{?project,since_ts,urgent_only,include_bodies,limit}
project
: disambiguate if the same agent name exists in multiple projects; if omitted, the most recent agent activity determines the projectsince_ts
: epoch seconds filter (defaults to 0)urgent_only
: when true, onlyimportance in ('high','urgent')
include_bodies
: include markdown bodies in resultslimit
: max results (default 20)
resource://message/{id}{?project}
: fetch one message;project
optional if id is globally uniqueresource://thread/{thread_id}{?project,include_bodies}
: list a thread’s messages; ifproject
omitted, resolves to the most recent matching project
Claims and the optional pre-commit guard
Exclusive claims are advisory but visible and auditable:
A claim JSON is written to
claims/<sha1(path)>.json
capturing holder, pattern, exclusivity, created/expiresThe pre-commit guard scans active exclusive claims and blocks commits that touch conflicting paths held by another agent
Agents should set
AGENT_NAME
(or rely onGIT_AUTHOR_NAME
) so the guard knows who “owns” the commit
Install the guard into a code repo (conceptual tool call):
Configuration
Configuration is loaded from an existing .env
via python-decouple
. Do not use os.getenv
or auto-dotenv loaders.
Common variables you may set:
MCP_MAIL_STORE
: Root for per-project archives and SQLite (default:~/.mcp-agent-mail
)HOST
/PORT
: Server bind host/port when running HTTP transportOptional: any future limits (attachment max bytes, etc.)
Configuration reference
Name | Default | Description |
|
| Root for per-project repos and SQLite DB |
|
| Bind host for HTTP transport |
|
| Bind port for HTTP transport |
|
| Threshold for inlining WebP images during send_message (if enabled) |
|
| Future: server log level |
|
| Future:
,
, or
default for image conversion |
Development quick start
This repository targets Python 3.14 and uses uv
with a virtual environment. We manage dependencies via pyproject.toml
only.
Run the server (HTTP-only). Depending on your entrypoint, one of the following patterns will apply when the implementation is in place:
Connect with your MCP client using the HTTP (Streamable HTTP) transport on the configured host/port.
End-to-end walkthrough
Create two agent identities (backend and frontend projects):
Backend agent claims
app/api/*.py
exclusively for 2 hours while preparing DB migrations:
Backend agent sends a design doc with an embedded diagram image:
Frontend agent checks inbox and replies in-thread with questions; reply inherits/sets
thread_id
:
Summarize the thread for quick context:
Pre-commit guard is installed on the backend repo to protect exclusive claims:
HTTP usage examples (JSON-RPC over Streamable HTTP)
Assuming the server is running at http://127.0.0.1:8765/mcp/
.
Call a tool:
Read a resource:
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.
Design choices and rationale
HTTP-only FastMCP: Streamable HTTP is the modern remote transport; SSE is legacy; 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 claims: Make intent explicit and reviewable; optional guard enforces claims at commit time
WebP attachments: Compact images by default; inline embedding keeps small diagrams in context
Examples (conceptual tool calls)
Create an agent:
Send a message (auto-convert images to WebP; inline small ones):
Claim a surface for editing:
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 (future-ready)
Add Bearer/JWT to the server or mount under a parent FastAPI app with auth middleware
Backups and retention
The Git repos and SQLite DB live under
MCP_MAIL_STORE
; back them up together for consistency
Observability
Add logging and metrics at the ASGI layer returned by
mcp.http_app()
(Prometheus, OpenTelemetry)
Concurrency
Git operations are serialized by a file lock per project to avoid index contention
Python client example (HTTP JSON-RPC)
Troubleshooting
"from_agent not registered"
Create the agent first with
create_agent
, or check theproject_key
you’re using matches the sender’s project
Pre-commit hook blocks commits
Set
AGENT_NAME
to your agent identity; release or wait for conflicting exclusive claims; inspect.git/hooks/pre-commit
Inline images didn’t embed
Ensure
convert_images=true
,image_embed_policy="auto"
orinline
, and the resulting WebP size is belowinline_max_bytes
Message not found
Confirm the
project
disambiguation when usingresource://message/{id}
; ids are unique per project
Inbox empty but messages exist
Check
since_ts
,urgent_only
, andlimit
; verify recipient names match exactly (case-sensitive)
FAQ
Why Git and SQLite together?
Git provides human-auditable artifacts and history; SQLite provides fast queries and FTS search. Each is great at what the other isn’t.
Are claims enforced?
Claims are advisory at the server layer; the optional pre-commit hook adds local enforcement at commit time.
Why HTTP-only?
Streamable HTTP is the modern remote transport for MCP; avoiding extra transports reduces complexity and encourages a uniform integration path.
Roadmap (selected)
See TODO.md
for the in-progress task list, including:
Filesystem archive and Git integration hardening (locks, authoring, commits)
Agent identity workflow polish (uniqueness, activity tracking)
Messaging enhancements (replies, read/ack semantics, urgent-only)
Claims/leases (overlap detection, releases, resources)
Resources for inbox, thread, message, claims
Search UI and thread summaries
Config/auth/CLI and health endpoints
Tests for archive, claims, search, CLI
If you’re building with or contributing to this project, please read project_idea_and_guide.md
for full design context and the motivation behind these decisions. Contributions that preserve the clean, HTTP-only FastMCP approach and the Git+SQLite dual persistence model are welcome.
This server cannot be installed
hybrid server
The server is able to function both locally and remotely, depending on the configuration or use case.
A coordination layer for coding agents that provides memorable identities, inbox/outbox messaging, searchable message history, and file lease management to prevent conflicts. Uses Git for human-auditable artifacts and SQLite for fast queries, enabling multiple agents to collaborate across projects without stepping on each other.
- Why this exists
- Core ideas (at a glance)
- Typical use cases
- Architecture
- How it works (key flows)
- Tools (MCP surface)
- Resource layer (read-only URIs)
- Claims and the optional pre-commit guard
- Configuration
- Development quick start
- End-to-end walkthrough
- HTTP usage examples (JSON-RPC over Streamable HTTP)
- Search syntax tips (SQLite FTS5)
- Design choices and rationale
- Examples (conceptual tool calls)
- Operational notes
- Security and ops
- Python client example (HTTP JSON-RPC)
- Troubleshooting
- FAQ
- Roadmap (selected)