From 1f98695821ca9fcc4a4e44453a87b23af5216a18 Mon Sep 17 00:00:00 2001 From: Stefan Lohmaier Date: Fri, 12 Jun 2026 08:16:53 +0200 Subject: [PATCH] Add create_draft tool to Mail server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates email drafts via IMAP APPEND to the Drafts folder. Supports Stefan (1blu, Drafts folder) and Kati (Gmail, Entwürfe). Draft can be reviewed and sent from Roundcube or phone. IMAP credentials externalized to config.json. Co-Authored-By: Claude Opus 4.6 --- mail/server.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/mail/server.py b/mail/server.py index b737eb8..71f1ffb 100644 --- a/mail/server.py +++ b/mail/server.py @@ -3,8 +3,11 @@ import os import sys import contextlib +import imaplib import mailbox from email.header import decode_header +from email.mime.text import MIMEText +from email.utils import formatdate from pathlib import Path from typing import Annotated @@ -17,7 +20,25 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from common import get_current_user, OAUTH_ROUTES, BearerAuthMiddleware from common import load_config as _lc -MAIL_ROOTS = _lc()["mail_roots"] +_cfg = _lc() +MAIL_ROOTS = _cfg["mail_roots"] + +IMAP_ACCOUNTS = { + "stefan": { + "host": "imap.1blu.de", + "user": "d370128_0-slohmaier", + "password": _cfg["imap_passwords"]["stefan"], + "drafts_folder": "Drafts", + "from_addr": "stefan@slohmaier.de", + }, + "kati": { + "host": "imap.gmail.com", + "user": "akpolke@gmail.com", + "password": _cfg["imap_passwords"]["kati"], + "drafts_folder": "[Google Mail]/Entw&APw-rfe", + "from_addr": "akpolke@gmail.com", + }, +} mcp = FastMCP("Mail", stateless_http=True, transport_security={"enable_dns_rebinding_protection": False}) @@ -171,6 +192,41 @@ def read_mail( ) +@mcp.tool() +def create_draft( + to: Annotated[str, Field(description="Recipient email address, e.g. 'max@example.com'")], + subject: Annotated[str, Field(description="Email subject line")], + body: Annotated[str, Field(description="Email body text (plain text)")], + cc: Annotated[str, Field(description="CC recipients, comma-separated")] = "", + account: Annotated[str, Field(description="Which account to create the draft in. Leave empty for default account.")] = "", +) -> str: + """Create an email draft in the Drafts folder. The draft can then be reviewed and sent from Roundcube or phone. Does NOT send the email.""" + user = get_current_user() + if not user: return "Error: not authenticated" + acct = IMAP_ACCOUNTS.get(user) + if not acct: return f"Kein IMAP-Account fuer {user} konfiguriert" + + msg = MIMEText(body, "plain", "utf-8") + msg["From"] = acct["from_addr"] + msg["To"] = to + if cc: + msg["Cc"] = cc + msg["Subject"] = subject + msg["Date"] = formatdate(localtime=True) + msg["X-Mailer"] = "MCP Mail Server" + + try: + imap = imaplib.IMAP4_SSL(acct["host"]) + imap.login(acct["user"], acct["password"]) + status, _ = imap.append(acct["drafts_folder"], "\\Draft", None, msg.as_bytes()) + imap.logout() + if status == "OK": + return f"Entwurf erstellt: An {to}, Betreff \"{subject}\". Oeffne Roundcube oder die Mail-App um ihn zu senden." + return f"Fehler beim Erstellen des Entwurfs: {status}" + except Exception as e: + return f"IMAP-Fehler: {e}" + + def create_app(): from contextlib import asynccontextmanager mcp_app = mcp.streamable_http_app()