Architecture

A file-by-file map of the source tree.

Architecture

src/rubit_mcp_mail/
  server.py        MCP tool definitions
  __main__.py      CLI: serve | install | auth | doctor | gui
  installer.py     setup wizard: writes config, signs in, registers with Claude
  installer_gui.py the setup window (tkinter), falling back to the terminal
  shortcuts.py     Start Menu / Desktop / .desktop entries for the GUI
  claude_registration.py  claude_desktop_config.json / `claude mcp add`
  session.py       wires config + auth + backend; attachment path safety
  config.py        TOML config -> Account models
  diagnostics.py   the doctor checks, as data (shared by the CLI and the GUI)
  permissions.py   registry of per-account-toggleable tool names
  config_editor.py every write to config.toml: comment-preserving, validated
  config_gui.py    the settings window (tkinter): accounts, permissions, doctor
  providers.py     provider profile registry
  secrets.py       keyring with 0600-file fallback
  models.py        pydantic models + message handles
  mime.py          BODYSTRUCTURE walking, decoding, HTML->text
  backends/
    base.py        MailBackend protocol
    imap.py        the IMAP implementation
  auth/
    password.py         app passwords
    oauth_microsoft.py  MSAL device-code flow

Adding a provider

src/rubit_mcp_mail/providers.py is a dict of profiles — host, port, and which auth strategy to use. Adding a preset is one entry.

A provider that needs a non-IMAP API instead implements the MailBackend protocol in src/rubit_mcp_mail/backends/base.py; the tool layer does not change. That protocol's docstring carries the project's central rule:

No implementation may set flags, move, delete, append, or expunge.

The layering

  • server.py is the only file that knows about MCP. It does argument validation, permission guarding, and turns exceptions into readable strings.
  • session.py owns the wiring — config in, an authenticated backend out — and is the only place that touches the filesystem for downloads.
  • backends/imap.py is the only file that speaks IMAP. Every read path uses EXAMINE and BODY.PEEK; there are no write paths, and a test enforces that.
  • auth/ hands back credentials without knowing what they'll be used for.