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.
This commit is contained in:
Anthony
2026-06-03 11:47:01 +02:00
parent db14b8fd53
commit a0601d1230
11 changed files with 833 additions and 8 deletions
Generated
+97 -4
View File
@@ -383,6 +383,61 @@ dependencies = [
"fs_extra",
]
[[package]]
name = "axum"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "base64"
version = "0.21.7"
@@ -2748,6 +2803,12 @@ dependencies = [
"web_atoms",
]
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "memchr"
version = "2.8.0"
@@ -4118,7 +4179,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -4185,6 +4246,12 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "same-file"
version = "1.0.6"
@@ -4494,6 +4561,17 @@ dependencies = [
"zmij",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
dependencies = [
"itoa",
"serde",
"serde_core",
]
[[package]]
name = "serde_repr"
version = "0.1.20"
@@ -4523,6 +4601,18 @@ dependencies = [
"serde_core",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_with"
version = "3.20.0"
@@ -5288,10 +5378,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -5639,6 +5729,7 @@ dependencies = [
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
@@ -5677,6 +5768,7 @@ version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log 0.4.29",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -5842,6 +5934,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"axum",
"base64 0.22.1",
"crypto-primitives",
"dirs",
@@ -5900,7 +5993,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset",
"tempfile",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]