loading…
Search for a command to run...
loading…
MCP server that enables AI agents to search and read locally-archived emails via the Bichon email archiver API, without exposing Gmail or IMAP credentials.
MCP server that enables AI agents to search and read locally-archived emails via the Bichon email archiver API, without exposing Gmail or IMAP credentials.
MCP server that wraps the Bichon email archiver REST API, letting an AI agent (Claude Code, Claude Desktop) search and read locally-archived emails — without ever touching Gmail or IMAP credentials directly.
Gmail / IMAP
└─► Bichon (Docker, localhost:15630)
local Tantivy FTS + compressed EML store
└─► bichon-mcp (this repo, stdio MCP server)
└─► Claude Code / Claude Desktop
After the initial IMAP sync, the AI only ever calls localhost:15630. No external auth is granted to the AI.
git clone https://github.com/pras-labs/bichon-mcp
cd bichon-mcp
uv venv
uv pip install -e .
Start Bichon (Docker):
docker run -d --name bichon -p 15630:15630 rustmailer/bichon:latest
Then get a token:
curl -s -X POST http://localhost:15630/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin@bichon"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])"
For a long-lived token, create one in the Bichon WebUI under Settings → Access Tokens.
Copy .mcp.json.example to .mcp.json and fill in the token:
cp .mcp.json.example .mcp.json
{
"mcpServers": {
"bichon-email": {
"command": ".venv/bin/python",
"args": ["-m", "bichon_mcp"],
"env": {
"BICHON_BASE_URL": "http://localhost:15630",
"BICHON_ACCESS_TOKEN": "<your-token>"
}
}
}
}
.mcp.jsonis gitignored — your token will not be committed.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"bichon-email": {
"command": "/path/to/bichon-mcp/.venv/bin/python",
"args": ["-m", "bichon_mcp"],
"env": {
"BICHON_BASE_URL": "http://localhost:15630",
"BICHON_ACCESS_TOKEN": "<your-token>"
}
}
}
}
All configuration is via environment variables:
| Variable | Default | Description |
|---|---|---|
BICHON_BASE_URL |
http://localhost:15630 |
Bichon instance URL |
BICHON_ACCESS_TOKEN |
(required) | Bearer token for Bichon API |
BICHON_STATS_CACHE_TTL |
300 |
Mailbox stats cache TTL in seconds. 0 = disabled |
BICHON_REQUEST_TIMEOUT |
30 |
HTTP request timeout in seconds |
list_accounts()List all email accounts configured in Bichon.
Returns: [{id, email}]
Example prompt: "What email accounts are set up?"
list_mailboxes(account_id)List mailbox folders for an account (INBOX, Sent, Drafts, etc.).
account_id* integer From list_accounts
Returns: [{id, name, total, unseen}]
Example prompt: "Show me all mailboxes for my account."
get_mailbox_stats(account_id, mailbox_id)Per-mailbox statistics: total email count, top senders, top subjects by frequency. Results are cached (default 5 min, configurable via BICHON_STATS_CACHE_TTL).
account_id* integer From list_accounts
mailbox_id* integer From list_mailboxes
Returns:
total_emails integer Exact count from Bichon
sampled integer Emails analysed (up to 200 most recent)
top_senders [{sender, count}]
top_subjects [{subject, count}]
oldest_in_sample / newest_in_sample integer (unix ms)
cached boolean
Example prompt: "Give me stats for my INBOX — who emails me most?"
search_emails(query, ...)Full-text search across archived emails. Returns summaries only — no body text.
query* string Full-text search term (max 500 chars)
date_from string ISO date YYYY-MM-DD (inclusive)
date_to string ISO date YYYY-MM-DD (inclusive)
sender string Filter by sender address (max 254 chars)
account_id integer Scope to one account
mailbox_id integer Scope to one mailbox folder
limit integer Max results, default 20, max 50
Returns: [{id, account_id, subject, from, to, date, preview, thread_id, has_attachments}]
Example prompts:
get_email(account_id, envelope_id)Fetch the full plain-text content of one email. HTML is stripped. Body is truncated at ~4000 tokens.
account_id* integer From search_emails result
envelope_id* string UUID from search_emails result
Returns: {account_id, envelope_id, body, attachment_count}
Example prompt: "Show me the full content of that invoice email."
list_threads(subject_contains?, sender?, limit?)Group search results by thread_id to show conversation threads.
subject_contains string Filter by subject text (max 500 chars)
sender string Filter by sender (max 254 chars)
limit integer Max threads to return, default 10
Returns: [{thread_id, account_id, subject, latest_from, latest_date, count}]
Example prompt: "Show me all threads from GitHub notifications."
get_sender_summary(email_address)Aggregate stats for a sender: total email count, date range, top subjects.
email_address* string Valid email address (max 254 chars)
Returns: {email, count, first_seen, last_seen, top_subjects}
Example prompt: "How many emails have I received from [email protected] and what are they about?"
User: "Summarise my email activity for the past month."
Claude:
1. list_accounts() → finds account IDs
2. list_mailboxes(account_id) → finds INBOX mailbox_id
3. get_mailbox_stats(...) → top senders, subjects, total count
4. search_emails(query="", date_from="2026-04-20", ...) → recent emails
5. get_email(...) × N → reads relevant messages
→ synthesises a summary
BICHON_ACCESS_TOKEN at startup — never hardcode it in source files..mcp.json is gitignored to prevent accidental token commits.envelope_id values are validated as UUIDs before use in API paths.# Install in editable mode
uv pip install -e .
# Verify MCP tools are registered (no Bichon needed)
python3 -c "
import subprocess, json, os, threading, queue, time
proc = subprocess.Popen(['.venv/bin/python', '-m', 'bichon_mcp'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
env={**os.environ, 'BICHON_BASE_URL':'http://localhost:15630','BICHON_ACCESS_TOKEN':'test'},
text=True, bufsize=1)
q = queue.Queue()
threading.Thread(target=lambda: [q.put(l.strip()) for l in proc.stdout], daemon=True).start()
for msg in [
{'jsonrpc':'2.0','id':1,'method':'initialize','params':{'protocolVersion':'2024-11-05','capabilities':{},'clientInfo':{'name':'t','version':'0'}}},
{'jsonrpc':'2.0','method':'notifications/initialized','params':{}},
{'jsonrpc':'2.0','id':2,'method':'tools/list','params':{}},
]:
proc.stdin.write(json.dumps(msg)+'\n')
proc.stdin.flush()
deadline = time.time()+10
while time.time()<deadline:
try:
line = q.get(timeout=1)
obj = json.loads(line)
if obj.get('id')==2:
print([t['name'] for t in obj['result']['tools']])
break
except: pass
proc.terminate()
"
# Expected: ['list_accounts', 'list_mailboxes', 'get_mailbox_stats', 'search_emails', 'get_email', 'list_threads', 'get_sender_summary']
AGPL-3.0. If you distribute this software, the source must remain open.
Run in your terminal:
claude mcp add bichon-mcp -- npx