Skip to main content
Glama
pras-labs

bichon-mcp

by pras-labs
README.md
# bichon-mcp

MCP server that wraps the [Bichon](https://github.com/rustmailer/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.

## Architecture

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

## Requirements

- Python 3.11+
- [uv](https://github.com/astral-sh/uv) (recommended) or pip
- Bichon running locally — see [Bichon quickstart](https://github.com/rustmailer/bichon)

## Installation

### 1. Clone and install

```bash
git clone https://github.com/pras-labs/bichon-mcp
cd bichon-mcp
uv venv
uv pip install -e .
```

### 2. Get a Bichon access token

Start Bichon (Docker):

```bash
docker run -d --name bichon -p 15630:15630 rustmailer/bichon:latest
```

Then get a token:

```bash
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**.

### 3. Configure your client

#### Claude Code

Copy `.mcp.json.example` to `.mcp.json` and fill in the token:

```bash
cp .mcp.json.example .mcp.json
```

```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.json` is gitignored — your token will not be committed.

#### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):

```json
{
  "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>"
      }
    }
  }
}
```

## Configuration

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 |

## MCP Tools

### `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:**
- *"Search for emails about invoices from last month."*
- *"Find emails from notifications@github.com in my INBOX."*

---

### `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 packt@mail.packtpub.com and what are they about?"*

---

## Typical workflow

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

## Security notes

- The access token is read from `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.
- All string inputs have length limits; integer IDs must be positive.
- API error responses are not forwarded to the AI (only the HTTP status code is).
- Email bodies are passed directly to Claude. Malicious emails may contain text designed to manipulate AI behaviour — a known limitation of any email-reading AI tool.

## Development

```bash
# 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']
```

## License

AGPL-3.0. If you distribute this software, the source must remain open.

TDQS

A4.1/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a distinct purpose: listing accounts, mailboxes, threads; searching emails; retrieving full email content; and obtaining statistics on a mailbox or sender. No overlapping functionality.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern using snake_case: 'list_' for enumerations, 'get_' for retrievals, and 'search_' for searching. No mixing of conventions.

Tool Count5/5

With 7 tools, the set is well-scoped for a read-only email retrieval and statistics server. Each tool covers a necessary operation without redundancy or deficiency.

Completeness5/5

The tool surface covers the full lifecycle for read-only email access: listing accounts and mailboxes, searching, retrieving full content, and aggregating stats. No obvious gaps for the stated purpose.

Maintenance

ActivityInactive
ResponsivenessNo issues