# API Design

> Use BEFORE adding any new tool, parameter, or endpoint to the Apple Mail MCP server. Also use when considering API changes, evaluating feature requests, or when tempted to create a specialized operation. Contains the decision tree that prevents tool sprawl and the anti-pattern catalog.

- Skill: `s-morgan-jeffries/api-design` (Agent Skill)
- Install (CLI): `npx skillmds@latest add s-morgan-jeffries/api-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/s-morgan-jeffries/api-design/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: s-morgan-jeffries (https://skillmd.com/u/s-morgan-jeffries)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/s-morgan-jeffries/api-design

---


# 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
```python
# 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
```python
# 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
```python
# 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

1. **Always return `dict[str, Any]`** with `"success": bool`
2. **Errors include `"error"` (message) and `"error_type"` (category)**
3. **Never return formatted text strings** — return structured data
4. **Include context fields** — e.g., `"messages_found": 5` alongside 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

1. Verify it doesn't fit in an existing tool (run through decision tree above)
2. Write unit tests (mock AppleScript)
3. Write integration tests (real Mail.app)
4. Implement in `mail_connector.py`
5. Expose in `server.py`
6. Run `./scripts/check_client_server_parity.sh`
7. Update `docs/reference/TOOLS.md`
8. Add blind eval scenarios if tool has non-obvious parameters

