A Model Context Protocol server that exposes a local Thunderbird profile — mail, contacts, and calendar — to an LLM.
It works with any MCP client and is designed to pair with LocalAI.
thunderbird-mcp reads Thunderbird's own on-disk state directly and uses IMAP/SMTP for anything live. There is no Thunderbird extension or add-on to install, and it works whether or not Thunderbird is running.
| Data source | File in the profile | Access |
|---|---|---|
| Accounts, SMTP, calendars | prefs.js |
read |
| Full-text search index | global-messages-db.sqlite (gloda) |
read-only |
| Credentials | key4.db + logins.json (NSS) |
read-only, decrypted in pure Go |
| Contacts | abook.sqlite |
read-only |
| Calendar | calendar-data/local.sqlite |
read-only |
| Live mail | IMAP / SMTP | fetch, mutate, send |
It is a single, static, CGO-free Go binary.
Expected latency while Thunderbird holds a lock. SQLite databases are normally read live, even with Thunderbird running. When Thunderbird does hold an exclusive lock, the first affected request waits about 5 seconds for it, then falls back to copying the database to a private snapshot, so a large search index is not instant. Subsequent requests are served from that snapshot and are fast for the next 60 seconds. Startup can pay the same wait once per data source. This is expected behaviour, not a hang.
Three domain tools, each driven by an action enum:
list_accounts, list_folders, search_messages, get_message, list_recent, set_flags, move_message, delete_message, send_mail, reply_message, forward_message, save_draft
search_contacts, get_contact
list_calendars, list_events
The action enum is built dynamically: write actions (set_flags/move_message/delete_message) appear only when writes are allowed, and send actions (send_mail/reply_message/forward_message/save_draft) only when THUNDERBIRD_ALLOW_SEND=true. A disabled action is not even present in the schema.
See docs/tools.md for the full per-action parameter reference.
- Never writes to any Thunderbird-owned file (mbox/maildir/gloda/abook/calendar/
key4.db/logins.json). Every SQLite database is opened read-only. All mutations go through IMAP, where the server syncs. - Local (POP/Local Folders) accounts, contacts, and calendar are read-only.
- Sending is gated behind
THUNDERBIRD_ALLOW_SEND=true(off by default).THUNDERBIRD_READ_ONLY=truedisables every mutating and sending action. - Local message paths are resolved with path-traversal protection — a crafted
message_refcannot escape the profile directory.
More detail in docs/security.md.
| Variable | Default | Effect |
|---|---|---|
THUNDERBIRD_PROFILE |
auto-discovered | path to the profile directory |
THUNDERBIRD_READ_ONLY |
false |
when true, mutating and sending actions are unavailable |
THUNDERBIRD_ALLOW_SEND |
false |
when true, the compose/send actions are enabled |
THUNDERBIRD_TOOLS |
all | comma-separated allowlist of the three tool names |
curl -fsSL https://mudler.github.io/thunderbird-mcp/install.sh | shInstalls the latest release binary (amd64/arm64) to ~/.local/bin after verifying its checksum against the release's checksums.txt. BINDIR=/usr/local/bin changes the destination, VERSION=v0.1.0 pins a release.
docker run -i --rm --network host \
-e THUNDERBIRD_PROFILE=/profile \
-v "$HOME/.thunderbird/xxxx.default-release:/profile:ro" \
ghcr.io/mudler/thunderbird-mcp:latest--network host lets the container reach your IMAP/SMTP servers; mounting the profile read-only is recommended (the server never writes to it).
go install github.com/mudler/thunderbird-mcp@latest
THUNDERBIRD_PROFILE=~/.thunderbird/xxxx.default-release thunderbird-mcpAdd to your model configuration:
mcp:
stdio: |
{
"mcpServers": {
"thunderbird": {
"command": "docker",
"args": [
"run", "-i", "--rm", "--network", "host",
"-e", "THUNDERBIRD_PROFILE=/profile",
"-e", "THUNDERBIRD_ALLOW_SEND=false",
"-v", "/home/user/.thunderbird/xxxx.default-release:/profile:ro",
"ghcr.io/mudler/thunderbird-mcp:latest"
]
}
}
}- OAuth2 accounts are not supported for live IMAP/SMTP (most Gmail/Outlook setups). They still appear in index search; live fetch/send report a clear "unsupported" error.
- A profile protected by a master password cannot have its credentials decrypted — the server logs a warning and keeps the read-only search/contacts/calendar tools working.
- No calendar or contact writes.
go test ./... # ginkgo suite
go vet ./...
CGO_ENABLED=0 go build .The credential-decryption path is covered by a regression test against a real NSS key4.db/logins.json fixture (generated by the NSS library itself), in addition to synthetic round-trip tests.