All checks were successful
Build And Test / publish (push) Successful in 47s
297 lines
11 KiB
Python
297 lines
11 KiB
Python
from typing import Optional
|
|
from fastmcp import FastMCP
|
|
|
|
from services.email_service import EmailService
|
|
from tools.logging_utils import log_tool_call
|
|
|
|
|
|
def register_email_tools(mcp: FastMCP, service: EmailService):
|
|
"""Register all email-related MCP tools."""
|
|
|
|
@mcp.tool(description="List all mailboxes/folders in the email account. Returns name, path, message count, and unread count for each mailbox.")
|
|
@log_tool_call
|
|
def list_mailboxes() -> list[dict]:
|
|
"""List all IMAP mailboxes/folders."""
|
|
mailboxes = service.list_mailboxes()
|
|
return [m.model_dump() for m in mailboxes]
|
|
|
|
@mcp.tool(description="List emails in a mailbox with pagination. Returns email summaries including subject, from, date, and read status.")
|
|
@log_tool_call
|
|
def list_emails(
|
|
mailbox: str = "INBOX",
|
|
limit: int = 50,
|
|
offset: int = 0,
|
|
include_body: bool = False,
|
|
) -> dict:
|
|
"""
|
|
List emails in a mailbox.
|
|
|
|
Args:
|
|
mailbox: The mailbox/folder to list (default: INBOX)
|
|
limit: Maximum number of emails to return (default: 50)
|
|
offset: Number of emails to skip for pagination (default: 0)
|
|
include_body: Whether to include email body snippets (default: False)
|
|
"""
|
|
result = service.list_emails(mailbox, limit, offset, include_body)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="List draft emails in the Drafts mailbox with pagination.")
|
|
@log_tool_call
|
|
def list_drafts(
|
|
mailbox: Optional[str] = None,
|
|
limit: int = 50,
|
|
offset: int = 0,
|
|
include_body: bool = False,
|
|
) -> dict:
|
|
"""
|
|
List draft emails.
|
|
|
|
Args:
|
|
mailbox: Drafts mailbox/folder override (default: auto-detect)
|
|
limit: Maximum number of drafts to return (default: 50)
|
|
offset: Number of drafts to skip for pagination (default: 0)
|
|
include_body: Whether to include body snippets (default: False)
|
|
"""
|
|
result = service.list_drafts(mailbox, limit, offset, include_body)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Read a specific email by ID with full body content and attachment information.")
|
|
@log_tool_call
|
|
def read_email(
|
|
mailbox: str,
|
|
email_id: str,
|
|
format: str = "text",
|
|
) -> Optional[dict]:
|
|
"""
|
|
Read a specific email.
|
|
|
|
Args:
|
|
mailbox: The mailbox containing the email
|
|
email_id: The unique ID of the email
|
|
format: Body format to return - 'text', 'html', or 'both' (default: text)
|
|
"""
|
|
result = service.read_email(mailbox, email_id, format)
|
|
return result.model_dump() if result else None
|
|
|
|
@mcp.tool(description="Search emails in a mailbox using various criteria like subject, sender, or body content.")
|
|
@log_tool_call
|
|
def search_emails(
|
|
query: str,
|
|
mailbox: str = "INBOX",
|
|
search_in: Optional[list[str]] = None,
|
|
date_from: Optional[str] = None,
|
|
date_to: Optional[str] = None,
|
|
limit: int = 50,
|
|
) -> dict:
|
|
"""
|
|
Search for emails matching criteria.
|
|
|
|
Args:
|
|
query: Search term to look for
|
|
mailbox: Mailbox to search in (default: INBOX)
|
|
search_in: Fields to search - any of ['subject', 'from', 'body'] (default: all)
|
|
date_from: Only emails after this date (format: DD-Mon-YYYY, e.g., 01-Jan-2024)
|
|
date_to: Only emails before this date (format: DD-Mon-YYYY)
|
|
limit: Maximum results to return (default: 50)
|
|
"""
|
|
if search_in is None:
|
|
search_in = ["subject", "from", "body"]
|
|
result = service.search_emails(query, mailbox, search_in, date_from, date_to, limit)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Move an email from one mailbox/folder to another.")
|
|
@log_tool_call
|
|
def move_email(
|
|
email_id: str,
|
|
source_mailbox: str,
|
|
destination_mailbox: str,
|
|
) -> dict:
|
|
"""
|
|
Move an email to a different folder.
|
|
|
|
Args:
|
|
email_id: The unique ID of the email to move
|
|
source_mailbox: The current mailbox containing the email
|
|
destination_mailbox: The target mailbox to move the email to
|
|
"""
|
|
result = service.move_email(email_id, source_mailbox, destination_mailbox)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Delete an email, either moving it to trash or permanently deleting it.")
|
|
@log_tool_call
|
|
def delete_email(
|
|
email_id: str,
|
|
mailbox: str,
|
|
permanent: bool = False,
|
|
) -> dict:
|
|
"""
|
|
Delete an email.
|
|
|
|
Args:
|
|
email_id: The unique ID of the email to delete
|
|
mailbox: The mailbox containing the email
|
|
permanent: If True, permanently delete; if False, move to Trash (default: False)
|
|
"""
|
|
result = service.delete_email(email_id, mailbox, permanent)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Delete a drafted email by ID, optionally permanently.")
|
|
@log_tool_call
|
|
def delete_draft(
|
|
email_id: str,
|
|
mailbox: Optional[str] = None,
|
|
permanent: bool = False,
|
|
) -> dict:
|
|
"""
|
|
Delete a draft email.
|
|
|
|
Args:
|
|
email_id: The unique ID of the draft
|
|
mailbox: Drafts mailbox/folder override (default: auto-detect)
|
|
permanent: If True, permanently delete; if False, move to Trash (default: False)
|
|
"""
|
|
result = service.delete_draft(email_id, mailbox, permanent)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Save a new draft email to the Drafts mailbox. Supports reply threading via in_reply_to_email_id.")
|
|
@log_tool_call
|
|
def save_draft(
|
|
to: Optional[list[str]] = None,
|
|
subject: Optional[str] = None,
|
|
body: Optional[str] = None,
|
|
cc: Optional[list[str]] = None,
|
|
bcc: Optional[list[str]] = None,
|
|
mailbox: Optional[str] = None,
|
|
in_reply_to_email_id: Optional[str] = None,
|
|
in_reply_to_mailbox: Optional[str] = None,
|
|
reply_all: bool = False,
|
|
) -> dict:
|
|
"""
|
|
Save a new email draft.
|
|
|
|
Args:
|
|
to: List of recipient email addresses (required unless in_reply_to_email_id is set)
|
|
subject: Email subject line (required unless in_reply_to_email_id is set)
|
|
body: Plain text email body (required unless in_reply_to_email_id is set)
|
|
cc: List of CC recipients (optional)
|
|
bcc: List of BCC recipients (optional)
|
|
mailbox: Drafts mailbox/folder override (default: auto-detect)
|
|
in_reply_to_email_id: Email UID to reply to (optional, derives recipients/subject and sets threading headers)
|
|
in_reply_to_mailbox: Mailbox containing the in_reply_to_email_id (default: INBOX)
|
|
reply_all: Whether to include original recipients when replying (default: False)
|
|
"""
|
|
result = service.save_draft(
|
|
to=to,
|
|
subject=subject,
|
|
body=body,
|
|
cc=cc,
|
|
bcc=bcc,
|
|
html_body=None,
|
|
mailbox=mailbox,
|
|
in_reply_to_email_id=in_reply_to_email_id,
|
|
in_reply_to_mailbox=in_reply_to_mailbox,
|
|
reply_all=reply_all,
|
|
)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Edit an existing draft email. Only provided fields will be modified. Supports reply threading via in_reply_to_email_id.")
|
|
@log_tool_call
|
|
def edit_draft(
|
|
email_id: str,
|
|
mailbox: Optional[str] = None,
|
|
to: Optional[list[str]] = None,
|
|
subject: Optional[str] = None,
|
|
body: Optional[str] = None,
|
|
cc: Optional[list[str]] = None,
|
|
bcc: Optional[list[str]] = None,
|
|
in_reply_to_email_id: Optional[str] = None,
|
|
in_reply_to_mailbox: Optional[str] = None,
|
|
reply_all: bool = False,
|
|
) -> dict:
|
|
"""
|
|
Update an existing draft email.
|
|
|
|
Args:
|
|
email_id: The unique ID of the draft
|
|
mailbox: Drafts mailbox/folder override (default: auto-detect)
|
|
to: List of recipient email addresses
|
|
subject: Email subject line
|
|
body: Plain text email body
|
|
cc: List of CC recipients (optional)
|
|
bcc: List of BCC recipients (optional)
|
|
in_reply_to_email_id: Email UID to reply to (optional, derives recipients/subject and sets threading headers)
|
|
in_reply_to_mailbox: Mailbox containing the in_reply_to_email_id (default: INBOX)
|
|
reply_all: Whether to include original recipients when replying (default: False)
|
|
"""
|
|
result = service.update_draft(
|
|
email_id=email_id,
|
|
mailbox=mailbox,
|
|
to=to,
|
|
subject=subject,
|
|
body=body,
|
|
cc=cc,
|
|
bcc=bcc,
|
|
html_body=None,
|
|
in_reply_to_email_id=in_reply_to_email_id,
|
|
in_reply_to_mailbox=in_reply_to_mailbox,
|
|
reply_all=reply_all,
|
|
)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Send an existing draft by ID. Only drafts can be sent.")
|
|
@log_tool_call
|
|
async def send_draft(
|
|
email_id: str,
|
|
mailbox: Optional[str] = None,
|
|
) -> dict:
|
|
"""
|
|
Send a draft email.
|
|
|
|
Args:
|
|
email_id: The unique ID of the draft to send
|
|
mailbox: Drafts mailbox/folder override (default: auto-detect)
|
|
"""
|
|
result = await service.send_draft(email_id, mailbox)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Set or remove IMAP flags on an email. Standard flags: \\Seen, \\Answered, \\Flagged, \\Deleted, \\Draft. Custom keywords are also supported.")
|
|
@log_tool_call
|
|
def set_email_flags(
|
|
email_id: str,
|
|
mailbox: str,
|
|
add_flags: Optional[list[str]] = None,
|
|
remove_flags: Optional[list[str]] = None,
|
|
) -> dict:
|
|
"""
|
|
Set or remove flags on an email.
|
|
|
|
Args:
|
|
email_id: The unique ID of the email
|
|
mailbox: The mailbox containing the email
|
|
add_flags: Flags to add (e.g., ["\\Flagged", "important"])
|
|
remove_flags: Flags to remove (e.g., ["\\Seen"])
|
|
"""
|
|
result = service.set_flags(email_id, mailbox, add_flags, remove_flags)
|
|
return result.model_dump()
|
|
|
|
@mcp.tool(description="Unsubscribe from a mailing list. Parses List-Unsubscribe headers and attempts automatic unsubscribe via HTTP or provides mailto instructions.")
|
|
@log_tool_call
|
|
async def unsubscribe_maillist(
|
|
email_id: str,
|
|
mailbox: str = "INBOX",
|
|
) -> dict:
|
|
"""
|
|
Unsubscribe from a mailing list based on email headers.
|
|
|
|
Args:
|
|
email_id: The unique ID of the email containing unsubscribe info
|
|
mailbox: The mailbox containing the email (default: INBOX)
|
|
|
|
Returns:
|
|
Result with unsubscribe status. For HTTP unsubscribe, it attempts
|
|
automatic unsubscription. For mailto, it returns the email details
|
|
to send.
|
|
"""
|
|
result = await service.unsubscribe(mailbox, email_id)
|
|
return result.model_dump()
|