Files
Anthony a0601d1230 Read-only MCP server (HTTP, GUI-controlled)
Expose the mailbox to an LLM client (Claude Desktop / Code) over an
in-process MCP server, so the bridge itself hosts it and the GUI controls
it live. Strictly read-only: there is no tool that sends, moves, deletes or
mutates mail — by design and asserted in tests.

Transport: Streamable HTTP (MCP 2025-06-18) on a single POST /mcp endpoint
bound to 127.0.0.1, answering each JSON-RPC request with application/json
(no SSE — the server never pushes). Auth is a bearer token (the bridge
password); the Origin header is validated to block DNS-rebinding.

Permission tiers (config.McpPermission, default Disabled = server off):
- Metadata — folders, metadata search (subject/sender/date), headers only.
- Full — the above plus full-text body search and message body text.

Tools: list_folders, search_messages, list_unread, get_message. Search
combines subject/sender always and the encrypted FTS body index under Full;
get_message returns headers always and body only under Full.

Wiring: spawned in-process by both the CLI (main.rs) and the GUI bridge
task (bridge.rs); a Disabled tier makes serve() a no-op, and it is kept out
of the select! so it never triggers teardown. GUI gains an MCP section
(tier selector, port, full-read warning, "copy client config" button) and a
get_mcp_client_config command that emits the ready-to-paste client snippet.

Validated live on a ~19k-message mailbox: initialize / tools/list /
tools/call all conform; 401 without the bearer token, 403 on a foreign
Origin, 202 on notifications; list_folders, body search and get_message
(HTML stripped to text) all return correctly. 240 unit tests.
2026-06-03 11:47:01 +02:00

58 lines
1.7 KiB
TOML

[package]
name = "tutabridge-core"
version = "0.1.0"
edition = "2021"
rust-version = "1.84.0"
license = "GPL-3.0-or-later"
[dependencies]
tuta-sdk = { path = "../../tuta-repo/tuta-sdk/rust/sdk", features = ["net"] }
tokio = { version = "1.43", features = ["full"] }
async-trait = "0.1"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
tokio-rustls = { version = "0.26", features = ["ring"] }
rustls-pemfile = "2"
rcgen = "0.13"
# Read-only MCP server (HTTP, localhost). Pure-Rust, builds on every target.
axum = "0.7"
crypto-primitives = { path = "../../tuta-repo/tuta-sdk/rust/crypto-primitives" }
rand = "0.8"
thiserror = "2.0"
base64 = "0.22"
dirs = "6"
anyhow = "1"
rand_core = "0.6"
# Vendor OpenSSL for SQLCipher so the build needs no system OpenSSL — works
# identically on macOS / Linux / Windows (Windows has none) and keeps the AUR
# package self-contained.
rusqlite = { version = "0.32", features = ["bundled-sqlcipher-vendored-openssl"] }
hex = "0.4"
# OS keychain backend — one per platform so the CLI builds (and packages,
# e.g. on the AUR) on Linux/Windows, not just macOS. Linux uses the
# Secret Service (gnome-keyring / KWallet) with pure-Rust crypto so there's
# no OpenSSL build dependency.
[target.'cfg(target_os = "macos")'.dependencies]
keyring = { version = "3", features = ["apple-native"] }
[target.'cfg(target_os = "windows")'.dependencies]
keyring = { version = "3", features = ["windows-native"] }
[target.'cfg(target_os = "linux")'.dependencies]
keyring = { version = "3", features = ["sync-secret-service", "crypto-rust"] }
[dev-dependencies]
tuta-sdk = { path = "../../tuta-repo/tuta-sdk/rust/sdk", features = ["net", "logging", "testing"] }
rpassword = "7"