mirror of
https://github.com/spartanz51/tutabridge.git
synced 2026-06-24 10:54:32 +02:00
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.
35 lines
1018 B
TypeScript
35 lines
1018 B
TypeScript
/** Read-only MCP server access tier. `disabled` = server off. */
|
|
export type McpPermission = "disabled" | "metadata" | "full";
|
|
|
|
export interface Config {
|
|
email: string;
|
|
imap_port: number;
|
|
smtp_port: number;
|
|
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 };
|
|
|
|
export type WsStatus = "Stopped" | "Connecting" | "Connected" | "Reconnecting";
|
|
|
|
export interface BridgeStats {
|
|
uptime_secs: number | null;
|
|
mails_synced: number;
|
|
ws_status: WsStatus;
|
|
}
|
|
|
|
export function statusLabel(status: BridgeStatus): string {
|
|
if (typeof status === "string") return status;
|
|
return `Error: ${status.Error}`;
|
|
}
|
|
|
|
export function isError(status: BridgeStatus): status is { Error: string } {
|
|
return typeof status !== "string";
|
|
}
|