Apple Mail MCP API Design
The current API has 17 tools organized into 4 phases. Tool count should grow slowly and deliberately. Every new tool request must pass through the decision tree.
Decision Tree: Use BEFORE Adding Any Tool
Can an existing tool handle this with current parameters? (70% of cases: YES)
|-- Searching with a new filter? -> Add parameter to search_messages()
|-- Reading with different format? -> Add parameter to get_messages()
|-- Sending/composing with new options? -> Extend create_draft() or update_draft()
|
Can an existing tool handle this with a NEW parameter? (20% of cases: YES)
|-- Example: search by date range -> add date_from, date_to to search_messages()
|-- Example: search flagged only -> add is_flagged to search_messages()
|
Is this truly a distinct operation? (10% of cases: MAYBE)
|-- Different Mail.app object? (accounts, rules, smart mailboxes)
|-- Different action type? (not CRUD on messages/mailboxes)
|
If NO to all three: You might need a new tool. This is rare.
Anti-Patterns (Never Do These)
Field-Specific Tools
# WRONG - creates tool sprawl
mark_as_flagged(message_id)
mark_as_unflagged(message_id)
mark_as_junk(message_id)
# RIGHT - one CRUD-style update tool with patch semantics
update_message(message_ids, read_status=True)
update_message(message_ids, flag_color="red")
update_message(message_ids, destination_mailbox="Archive", account="Gmail")
Specialized Search Tools
# WRONG - each filter becomes a tool
get_unread_messages()
get_flagged_messages()
get_messages_from_sender()
# RIGHT - parameters on existing search
search_messages(read_status="unread")
search_messages(is_flagged=True)
search_messages(sender_contains="user@example.com")
Formatted Text Returns
# WRONG - returns human-readable string
def list_mailboxes():
return "Inbox (42 messages)\nSent (15 messages)"
# RIGHT - returns structured data
def list_mailboxes():
return {"success": True, "mailboxes": [{"name": "Inbox", "count": 42}, ...]}
Response Format Rules
- Always return
dict[str, Any]with"success": bool - Errors include
"error"(message) and"error_type"(category) - Never return formatted text strings — return structured data
- Include context fields — e.g.,
"messages_found": 5alongside the results
Tool Naming Convention
- Verb + noun:
search_messages,create_draft,delete_messages - Plural when accepting lists:
delete_messages,get_messages - CRUD-style for multi-field mutation:
update_message(read_status, flag_color, destination_mailbox, ...)over per-field verbs (mark_as_read,flag_message,move_messages). Patch semantics — unset fields are unchanged.
Adding a New Tool Checklist
- Verify it doesn't fit in an existing tool (run through decision tree above)
- Write unit tests (mock AppleScript)
- Write integration tests (real Mail.app)
- Implement in
mail_connector.py - Expose in
server.py - Run
./scripts/check_client_server_parity.sh - Update
docs/reference/TOOLS.md - Add blind eval scenarios if tool has non-obvious parameters