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
+7
View File
@@ -1,3 +1,6 @@
/** Read-only MCP server access tier. `disabled` = server off. */
export type McpPermission = "disabled" | "metadata" | "full";
export interface Config {
email: string;
imap_port: number;
@@ -5,6 +8,10 @@ export interface Config {
api_url: string;
/** Max mails synced per folder; 0 = fetch all. */
sync_limit: number;
/** Read-only MCP server permission tier. */
mcp_permission: McpPermission;
/** Port the read-only MCP HTTP server listens on (127.0.0.1). */
mcp_port: number;
}
export type BridgeStatus = "Stopped" | "Starting" | "Running" | { Error: string };