apple-mail-mcp 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,24 +77,49 @@ On first use, macOS will ask for permission to automate Mail.app. Click "OK" to
77
77
 
78
78
  ## Features
79
79
 
80
+ ### Messages
81
+
80
82
  | Feature | Description |
81
83
  |---------|-------------|
82
- | **List Messages** | List messages in any mailbox |
83
- | **Search Messages** | Find emails by sender, subject, content |
84
- | **Read Messages** | Get full email content |
84
+ | **List Messages** | List messages with pagination, sender filter, date display |
85
+ | **Search Messages** | Search by sender, subject, content, date range, read/flagged status — across all accounts |
86
+ | **Read Messages** | Get full email content (plain text or HTML) |
85
87
  | **Send Email** | Compose and send new emails |
86
88
  | **Create Draft** | Save emails to Drafts folder |
87
89
  | **Reply** | Reply to messages (with reply-all support) |
88
90
  | **Forward** | Forward messages to new recipients |
89
- | **Mark Read/Unread** | Change read status |
90
- | **Flag/Unflag** | Flag or unflag messages |
91
- | **Delete Messages** | Move messages to trash |
92
- | **Move Messages** | Organize into mailboxes |
93
- | **List Mailboxes** | Show all folders with counts |
91
+ | **Mark Read/Unread** | Change read status (single or batch) |
92
+ | **Flag/Unflag** | Flag or unflag messages (single or batch) |
93
+ | **Delete Messages** | Move messages to trash (single or batch) |
94
+ | **Move Messages** | Organize into mailboxes (single or batch) |
95
+ | **List Attachments** | View attachment metadata (name, type, size) |
96
+ | **Save Attachment** | Save attachments to disk |
97
+
98
+ ### Mailbox & Account Management
99
+
100
+ | Feature | Description |
101
+ |---------|-------------|
102
+ | **List Mailboxes** | Show all folders with message/unread counts |
103
+ | **Create/Delete/Rename Mailbox** | Full mailbox lifecycle management |
94
104
  | **List Accounts** | Show configured accounts |
95
105
  | **Unread Count** | Get unread counts per mailbox |
106
+
107
+ ### Rules, Contacts & Templates
108
+
109
+ | Feature | Description |
110
+ |---------|-------------|
111
+ | **List Rules** | View all mail rules and their enabled status |
112
+ | **Enable/Disable Rules** | Toggle mail rules on or off |
113
+ | **Search Contacts** | Look up contacts from Contacts.app by name |
114
+ | **Email Templates** | Save, list, use, and delete reusable email templates |
115
+
116
+ ### Diagnostics
117
+
118
+ | Feature | Description |
119
+ |---------|-------------|
96
120
  | **Health Check** | Verify Mail.app connectivity |
97
- | **Statistics** | Message and unread counts |
121
+ | **Statistics** | Message and unread counts per account, recently received stats |
122
+ | **Sync Status** | Check if Mail.app is actively syncing |
98
123
 
99
124
  ---
100
125
 
@@ -106,13 +131,19 @@ This section documents all available tools. AI agents should use these tool name
106
131
 
107
132
  #### `search-messages`
108
133
 
109
- Search for messages matching criteria.
134
+ Search for messages matching criteria. Searches all accounts by default.
110
135
 
111
136
  | Parameter | Type | Required | Description |
112
137
  |-----------|------|----------|-------------|
113
138
  | `query` | string | No | Text to search in subject/sender |
139
+ | `from` | string | No | Filter by sender email address |
140
+ | `subject` | string | No | Filter by subject line |
114
141
  | `mailbox` | string | No | Mailbox to search in (default: INBOX) |
115
- | `account` | string | No | Account to search in |
142
+ | `account` | string | No | Account to search in (omit to search all accounts) |
143
+ | `isRead` | boolean | No | Filter by read status |
144
+ | `isFlagged` | boolean | No | Filter by flagged status |
145
+ | `dateFrom` | string | No | Start date filter (e.g., "January 1, 2026") |
146
+ | `dateTo` | string | No | End date filter (e.g., "March 1, 2026") |
116
147
  | `limit` | number | No | Max results (default: 50) |
117
148
 
118
149
  ---
@@ -124,8 +155,9 @@ Get the full content of a message.
124
155
  | Parameter | Type | Required | Description |
125
156
  |-----------|------|----------|-------------|
126
157
  | `id` | string | Yes | Message ID |
158
+ | `preferHtml` | boolean | No | Return HTML source instead of plain text |
127
159
 
128
- **Returns:** Subject line and plain text body of the message.
160
+ **Returns:** Subject line and message body (plain text by default, HTML if `preferHtml` is true and HTML content is available).
129
161
 
130
162
  ---
131
163
 
@@ -138,8 +170,11 @@ List messages in a mailbox.
138
170
  | `mailbox` | string | No | Mailbox name (default: INBOX) |
139
171
  | `account` | string | No | Account name |
140
172
  | `limit` | number | No | Max messages (default: 50) |
173
+ | `offset` | number | No | Number of messages to skip (for pagination) |
174
+ | `from` | string | No | Filter by sender email address or name |
175
+ | `unreadOnly` | boolean | No | Only show unread messages |
141
176
 
142
- **Returns:** List of messages with ID, subject, sender, date, read status, and flagged status.
177
+ **Returns:** List of messages with ID, date, subject, and sender.
143
178
 
144
179
  ---
145
180
 
@@ -271,6 +306,62 @@ Move a message to a different mailbox.
271
306
 
272
307
  ---
273
308
 
309
+ #### `list-attachments`
310
+
311
+ List attachments on a message.
312
+
313
+ | Parameter | Type | Required | Description |
314
+ |-----------|------|----------|-------------|
315
+ | `id` | string | Yes | Message ID |
316
+
317
+ **Returns:** List of attachments with name, MIME type, and size.
318
+
319
+ ---
320
+
321
+ #### `save-attachment`
322
+
323
+ Save a message attachment to disk.
324
+
325
+ | Parameter | Type | Required | Description |
326
+ |-----------|------|----------|-------------|
327
+ | `id` | string | Yes | Message ID |
328
+ | `attachmentName` | string | Yes | Filename of the attachment |
329
+ | `savePath` | string | Yes | Directory to save to |
330
+
331
+ ---
332
+
333
+ ### Batch Operations
334
+
335
+ All batch operations accept an array of message IDs and return per-item success/failure results.
336
+
337
+ #### `batch-delete-messages`
338
+
339
+ | Parameter | Type | Required | Description |
340
+ |-----------|------|----------|-------------|
341
+ | `ids` | string[] | Yes | Message IDs to delete |
342
+
343
+ #### `batch-move-messages`
344
+
345
+ | Parameter | Type | Required | Description |
346
+ |-----------|------|----------|-------------|
347
+ | `ids` | string[] | Yes | Message IDs to move |
348
+ | `mailbox` | string | Yes | Destination mailbox |
349
+ | `account` | string | No | Account containing mailbox |
350
+
351
+ #### `batch-mark-as-read` / `batch-mark-as-unread`
352
+
353
+ | Parameter | Type | Required | Description |
354
+ |-----------|------|----------|-------------|
355
+ | `ids` | string[] | Yes | Message IDs |
356
+
357
+ #### `batch-flag-messages` / `batch-unflag-messages`
358
+
359
+ | Parameter | Type | Required | Description |
360
+ |-----------|------|----------|-------------|
361
+ | `ids` | string[] | Yes | Message IDs |
362
+
363
+ ---
364
+
274
365
  ### Mailbox Operations
275
366
 
276
367
  #### `list-mailboxes`
@@ -281,7 +372,7 @@ List all mailboxes for an account.
281
372
  |-----------|------|----------|-------------|
282
373
  | `account` | string | No | Account to list from |
283
374
 
284
- **Returns:** List of mailbox names with unread counts.
375
+ **Returns:** List of mailbox names with message and unread counts.
285
376
 
286
377
  ---
287
378
 
@@ -296,6 +387,40 @@ Get unread message count.
296
387
 
297
388
  ---
298
389
 
390
+ #### `create-mailbox`
391
+
392
+ Create a new mailbox.
393
+
394
+ | Parameter | Type | Required | Description |
395
+ |-----------|------|----------|-------------|
396
+ | `name` | string | Yes | Mailbox name |
397
+ | `account` | string | No | Account to create in |
398
+
399
+ ---
400
+
401
+ #### `delete-mailbox`
402
+
403
+ Delete a mailbox.
404
+
405
+ | Parameter | Type | Required | Description |
406
+ |-----------|------|----------|-------------|
407
+ | `name` | string | Yes | Mailbox name |
408
+ | `account` | string | No | Account containing mailbox |
409
+
410
+ ---
411
+
412
+ #### `rename-mailbox`
413
+
414
+ Rename a mailbox (creates new, moves messages, deletes old).
415
+
416
+ | Parameter | Type | Required | Description |
417
+ |-----------|------|----------|-------------|
418
+ | `oldName` | string | Yes | Current mailbox name |
419
+ | `newName` | string | Yes | New mailbox name |
420
+ | `account` | string | No | Account containing mailbox |
421
+
422
+ ---
423
+
299
424
  ### Account Operations
300
425
 
301
426
  #### `list-accounts`
@@ -304,7 +429,105 @@ List all configured Mail accounts.
304
429
 
305
430
  **Parameters:** None
306
431
 
307
- **Returns:** List of account names (e.g., "iCloud", "Gmail", "Exchange").
432
+ **Returns:** List of account names and email addresses.
433
+
434
+ ---
435
+
436
+ ### Rules
437
+
438
+ #### `list-rules`
439
+
440
+ List all mail rules.
441
+
442
+ **Parameters:** None
443
+
444
+ **Returns:** List of rule names and enabled status.
445
+
446
+ ---
447
+
448
+ #### `enable-rule` / `disable-rule`
449
+
450
+ Enable or disable a mail rule.
451
+
452
+ | Parameter | Type | Required | Description |
453
+ |-----------|------|----------|-------------|
454
+ | `name` | string | Yes | Rule name |
455
+
456
+ ---
457
+
458
+ ### Contacts
459
+
460
+ #### `search-contacts`
461
+
462
+ Search contacts in Contacts.app.
463
+
464
+ | Parameter | Type | Required | Description |
465
+ |-----------|------|----------|-------------|
466
+ | `query` | string | Yes | Name to search for |
467
+ | `limit` | number | No | Max results (default: 10) |
468
+
469
+ **Returns:** List of contacts with name, email addresses, and phone numbers.
470
+
471
+ ---
472
+
473
+ ### Templates
474
+
475
+ Email templates are stored in memory for the duration of the server session.
476
+
477
+ #### `save-template`
478
+
479
+ Save or update an email template.
480
+
481
+ | Parameter | Type | Required | Description |
482
+ |-----------|------|----------|-------------|
483
+ | `name` | string | Yes | Template name |
484
+ | `subject` | string | Yes | Default subject line |
485
+ | `body` | string | Yes | Template body |
486
+ | `to` | string[] | No | Default recipients |
487
+ | `cc` | string[] | No | Default CC recipients |
488
+ | `id` | string | No | Template ID (for updating) |
489
+
490
+ ---
491
+
492
+ #### `list-templates`
493
+
494
+ List all saved templates.
495
+
496
+ **Parameters:** None
497
+
498
+ ---
499
+
500
+ #### `get-template`
501
+
502
+ Get a template by ID.
503
+
504
+ | Parameter | Type | Required | Description |
505
+ |-----------|------|----------|-------------|
506
+ | `id` | string | Yes | Template ID |
507
+
508
+ ---
509
+
510
+ #### `delete-template`
511
+
512
+ Delete a template.
513
+
514
+ | Parameter | Type | Required | Description |
515
+ |-----------|------|----------|-------------|
516
+ | `id` | string | Yes | Template ID |
517
+
518
+ ---
519
+
520
+ #### `use-template`
521
+
522
+ Create a draft from a template, with optional overrides.
523
+
524
+ | Parameter | Type | Required | Description |
525
+ |-----------|------|----------|-------------|
526
+ | `id` | string | Yes | Template ID |
527
+ | `to` | string[] | No | Override recipients |
528
+ | `cc` | string[] | No | Override CC |
529
+ | `subject` | string | No | Override subject |
530
+ | `body` | string | No | Override body |
308
531
 
309
532
  ---
310
533
 
@@ -322,11 +545,21 @@ Verify Mail.app connectivity and permissions.
322
545
 
323
546
  #### `get-mail-stats`
324
547
 
325
- Get mail statistics (total messages, unread counts per account).
548
+ Get mail statistics.
549
+
550
+ **Parameters:** None
551
+
552
+ **Returns:** Total and per-account message/unread counts, plus recently received stats (24h, 7d, 30d).
553
+
554
+ ---
555
+
556
+ #### `get-sync-status`
557
+
558
+ Check Mail.app sync activity.
326
559
 
327
560
  **Parameters:** None
328
561
 
329
- **Returns:** Total counts and per-account breakdown.
562
+ **Returns:** Whether sync is detected, pending uploads, recent activity, and seconds since last change.
330
563
 
331
564
  ---
332
565
 
@@ -350,7 +583,7 @@ AI: [calls get-message with id="..."]
350
583
 
351
584
  ### Working with Accounts
352
585
 
353
- By default, operations use the first configured account. To work with specific accounts:
586
+ By default, operations use Mail.app's configured default send account. Search operations check all accounts when no account is specified. To work with specific accounts:
354
587
 
355
588
  ```
356
589
  User: "What email accounts do I have?"
@@ -429,10 +662,10 @@ If installed from source, use this configuration:
429
662
  | Limitation | Reason |
430
663
  |------------|--------|
431
664
  | macOS only | Apple Mail and AppleScript are macOS-specific |
432
- | Plain text only | Email body is plain text; HTML formatting not supported |
433
- | No attachments | Cannot add or read attachments via AppleScript |
434
- | Message ID scope | Message IDs are searched across all mailboxes (may be slow with large mailboxes) |
665
+ | No sending HTML email | Emails are sent as plain text; reading HTML content is supported |
666
+ | No adding attachments | Can list and save existing attachments, but cannot attach files to outgoing emails |
435
667
  | No smart mailboxes | Cannot access Smart Mailboxes via AppleScript |
668
+ | In-memory templates | Email templates are not persisted across server restarts |
436
669
 
437
670
  ### Backslash Escaping (Important for AI Agents)
438
671
 
package/build/index.js CHANGED
@@ -86,28 +86,34 @@ server.tool("search-messages", {
86
86
  from: z.string().optional().describe("Filter by sender email address"),
87
87
  subject: z.string().optional().describe("Filter by subject line"),
88
88
  mailbox: z.string().optional().describe("Mailbox to search in (e.g., 'INBOX')"),
89
- account: z.string().optional().describe("Account to search in"),
89
+ account: z.string().optional().describe("Account to search in (omit to search all accounts)"),
90
90
  isRead: z.boolean().optional().describe("Filter by read status"),
91
91
  isFlagged: z.boolean().optional().describe("Filter by flagged status"),
92
+ dateFrom: z.string().optional().describe("Start date filter (e.g., 'January 1, 2026')"),
93
+ dateTo: z.string().optional().describe("End date filter (e.g., 'March 1, 2026')"),
92
94
  limit: z.number().optional().describe("Maximum number of results (default: 50)"),
93
- }, withErrorHandling(({ query, mailbox, account, limit = 50 }) => {
94
- const messages = mailManager.searchMessages(query, mailbox, account, limit);
95
+ }, withErrorHandling(({ query, mailbox, account, limit = 50, dateFrom, dateTo }) => {
96
+ const messages = mailManager.searchMessages(query, mailbox, account, limit, dateFrom, dateTo);
95
97
  if (messages.length === 0) {
96
98
  return successResponse("No messages found matching criteria");
97
99
  }
98
100
  const messageList = messages
99
- .map((m) => ` - ${m.subject} (from: ${m.sender}) [${m.isRead ? "read" : "unread"}]`)
101
+ .map((m) => ` - ID: ${m.id} | ${m.dateReceived.toLocaleDateString()} | ${m.subject} (from: ${m.sender}) [${m.isRead ? "read" : "unread"}]`)
100
102
  .join("\n");
101
103
  return successResponse(`Found ${messages.length} message(s):\n${messageList}`);
102
104
  }, "Error searching messages"));
103
105
  // --- get-message ---
104
106
  server.tool("get-message", {
105
107
  id: z.string().min(1, "Message ID is required"),
106
- }, withErrorHandling(({ id }) => {
108
+ preferHtml: z.boolean().optional().describe("Return HTML source instead of plain text"),
109
+ }, withErrorHandling(({ id, preferHtml }) => {
107
110
  const content = mailManager.getMessageContent(id);
108
111
  if (!content) {
109
112
  return errorResponse(`Message with ID "${id}" not found`);
110
113
  }
114
+ if (preferHtml && content.htmlContent) {
115
+ return successResponse(`Subject: ${content.subject}\n\n${content.htmlContent}`);
116
+ }
111
117
  return successResponse(`Subject: ${content.subject}\n\n${content.plainText}`);
112
118
  }, "Error retrieving message"));
113
119
  // --- list-messages ---
@@ -115,13 +121,17 @@ server.tool("list-messages", {
115
121
  mailbox: z.string().optional().describe("Mailbox to list messages from (default: INBOX)"),
116
122
  account: z.string().optional().describe("Account to list messages from"),
117
123
  limit: z.number().optional().describe("Maximum number of messages (default: 50)"),
124
+ offset: z.number().optional().describe("Number of messages to skip (for pagination)"),
125
+ from: z.string().optional().describe("Filter by sender email address or name"),
118
126
  unreadOnly: z.boolean().optional().describe("Only show unread messages"),
119
- }, withErrorHandling(({ mailbox, account, limit = 50 }) => {
120
- const messages = mailManager.listMessages(mailbox, account, limit);
127
+ }, withErrorHandling(({ mailbox, account, limit = 50, offset = 0, from }) => {
128
+ const messages = mailManager.listMessages(mailbox, account, limit, from, offset);
121
129
  if (messages.length === 0) {
122
130
  return successResponse("No messages found");
123
131
  }
124
- const messageList = messages.map((m) => ` - ${m.subject} (from: ${m.sender})`).join("\n");
132
+ const messageList = messages
133
+ .map((m) => ` - ID: ${m.id} | ${m.dateReceived.toLocaleDateString()} | ${m.subject} (from: ${m.sender})`)
134
+ .join("\n");
125
135
  return successResponse(`Found ${messages.length} message(s):\n${messageList}`);
126
136
  }, "Error listing messages"));
127
137
  // --- send-email ---
@@ -210,6 +220,16 @@ server.tool("flag-message", {
210
220
  }
211
221
  return successResponse("Message flagged");
212
222
  }, "Error flagging message"));
223
+ // --- unflag-message ---
224
+ server.tool("unflag-message", {
225
+ id: z.string().min(1, "Message ID is required"),
226
+ }, withErrorHandling(({ id }) => {
227
+ const success = mailManager.unflagMessage(id);
228
+ if (!success) {
229
+ return errorResponse(`Failed to unflag message "${id}"`);
230
+ }
231
+ return successResponse("Message unflagged");
232
+ }, "Error unflagging message"));
213
233
  // --- delete-message ---
214
234
  server.tool("delete-message", {
215
235
  id: z.string().min(1, "Message ID is required"),
@@ -285,6 +305,57 @@ server.tool("batch-mark-as-read", {
285
305
  return successResponse(`Marked ${successCount} message(s) as read, ${failCount} failed`);
286
306
  }
287
307
  }, "Error batch marking messages as read"));
308
+ // --- batch-mark-as-unread ---
309
+ server.tool("batch-mark-as-unread", {
310
+ ids: z.array(z.string()).min(1, "At least one message ID is required"),
311
+ }, withErrorHandling(({ ids }) => {
312
+ const results = mailManager.batchMarkAsUnread(ids);
313
+ const successCount = results.filter((r) => r.success).length;
314
+ const failCount = results.length - successCount;
315
+ if (failCount === 0) {
316
+ return successResponse(`Successfully marked ${successCount} message(s) as unread`);
317
+ }
318
+ else if (successCount === 0) {
319
+ return errorResponse(`Failed to mark all ${failCount} message(s) as unread`);
320
+ }
321
+ else {
322
+ return successResponse(`Marked ${successCount} message(s) as unread, ${failCount} failed`);
323
+ }
324
+ }, "Error batch marking messages as unread"));
325
+ // --- batch-flag-messages ---
326
+ server.tool("batch-flag-messages", {
327
+ ids: z.array(z.string()).min(1, "At least one message ID is required"),
328
+ }, withErrorHandling(({ ids }) => {
329
+ const results = mailManager.batchFlagMessages(ids);
330
+ const successCount = results.filter((r) => r.success).length;
331
+ const failCount = results.length - successCount;
332
+ if (failCount === 0) {
333
+ return successResponse(`Successfully flagged ${successCount} message(s)`);
334
+ }
335
+ else if (successCount === 0) {
336
+ return errorResponse(`Failed to flag all ${failCount} message(s)`);
337
+ }
338
+ else {
339
+ return successResponse(`Flagged ${successCount} message(s), ${failCount} failed`);
340
+ }
341
+ }, "Error batch flagging messages"));
342
+ // --- batch-unflag-messages ---
343
+ server.tool("batch-unflag-messages", {
344
+ ids: z.array(z.string()).min(1, "At least one message ID is required"),
345
+ }, withErrorHandling(({ ids }) => {
346
+ const results = mailManager.batchUnflagMessages(ids);
347
+ const successCount = results.filter((r) => r.success).length;
348
+ const failCount = results.length - successCount;
349
+ if (failCount === 0) {
350
+ return successResponse(`Successfully unflagged ${successCount} message(s)`);
351
+ }
352
+ else if (successCount === 0) {
353
+ return errorResponse(`Failed to unflag all ${failCount} message(s)`);
354
+ }
355
+ else {
356
+ return successResponse(`Unflagged ${successCount} message(s), ${failCount} failed`);
357
+ }
358
+ }, "Error batch unflagging messages"));
288
359
  // --- list-attachments ---
289
360
  server.tool("list-attachments", {
290
361
  id: z.string().min(1, "Message ID is required"),
@@ -301,6 +372,18 @@ server.tool("list-attachments", {
301
372
  .join("\n");
302
373
  return successResponse(`Found ${attachments.length} attachment(s):\n${attachmentList}`);
303
374
  }, "Error listing attachments"));
375
+ // --- save-attachment ---
376
+ server.tool("save-attachment", {
377
+ id: z.string().min(1, "Message ID is required"),
378
+ attachmentName: z.string().min(1, "Attachment name is required"),
379
+ savePath: z.string().min(1, "Save directory path is required"),
380
+ }, withErrorHandling(({ id, attachmentName, savePath }) => {
381
+ const success = mailManager.saveAttachment(id, attachmentName, savePath);
382
+ if (!success) {
383
+ return errorResponse(`Failed to save attachment "${attachmentName}"`);
384
+ }
385
+ return successResponse(`Attachment "${attachmentName}" saved to ${savePath}`);
386
+ }, "Error saving attachment"));
304
387
  // =============================================================================
305
388
  // Mailbox Tools
306
389
  // =============================================================================
@@ -324,6 +407,40 @@ server.tool("get-unread-count", {
324
407
  const location = mailbox ? ` in "${mailbox}"` : "";
325
408
  return successResponse(`${count} unread message(s)${location}`);
326
409
  }, "Error getting unread count"));
410
+ // --- create-mailbox ---
411
+ server.tool("create-mailbox", {
412
+ name: z.string().min(1, "Mailbox name is required"),
413
+ account: z.string().optional().describe("Account to create the mailbox in"),
414
+ }, withErrorHandling(({ name, account }) => {
415
+ const success = mailManager.createMailbox(name, account);
416
+ if (!success) {
417
+ return errorResponse(`Failed to create mailbox "${name}"`);
418
+ }
419
+ return successResponse(`Mailbox "${name}" created`);
420
+ }, "Error creating mailbox"));
421
+ // --- delete-mailbox ---
422
+ server.tool("delete-mailbox", {
423
+ name: z.string().min(1, "Mailbox name is required"),
424
+ account: z.string().optional().describe("Account containing the mailbox"),
425
+ }, withErrorHandling(({ name, account }) => {
426
+ const success = mailManager.deleteMailbox(name, account);
427
+ if (!success) {
428
+ return errorResponse(`Failed to delete mailbox "${name}"`);
429
+ }
430
+ return successResponse(`Mailbox "${name}" deleted`);
431
+ }, "Error deleting mailbox"));
432
+ // --- rename-mailbox ---
433
+ server.tool("rename-mailbox", {
434
+ oldName: z.string().min(1, "Current mailbox name is required"),
435
+ newName: z.string().min(1, "New mailbox name is required"),
436
+ account: z.string().optional().describe("Account containing the mailbox"),
437
+ }, withErrorHandling(({ oldName, newName, account }) => {
438
+ const success = mailManager.renameMailbox(oldName, newName, account);
439
+ if (!success) {
440
+ return errorResponse(`Failed to rename mailbox "${oldName}" to "${newName}"`);
441
+ }
442
+ return successResponse(`Mailbox renamed from "${oldName}" to "${newName}"`);
443
+ }, "Error renaming mailbox"));
327
444
  // =============================================================================
328
445
  // Account Tools
329
446
  // =============================================================================
@@ -337,6 +454,128 @@ server.tool("list-accounts", {}, withErrorHandling(() => {
337
454
  return successResponse(`Found ${accounts.length} account(s):\n${accountList}`);
338
455
  }, "Error listing accounts"));
339
456
  // =============================================================================
457
+ // Mail Rules Tools
458
+ // =============================================================================
459
+ // --- list-rules ---
460
+ server.tool("list-rules", {}, withErrorHandling(() => {
461
+ const rules = mailManager.listRules();
462
+ if (rules.length === 0) {
463
+ return successResponse("No mail rules found");
464
+ }
465
+ const ruleList = rules
466
+ .map((r) => ` - ${r.name} [${r.enabled ? "enabled" : "disabled"}]`)
467
+ .join("\n");
468
+ return successResponse(`Found ${rules.length} rule(s):\n${ruleList}`);
469
+ }, "Error listing rules"));
470
+ // --- enable-rule ---
471
+ server.tool("enable-rule", {
472
+ name: z.string().min(1, "Rule name is required"),
473
+ }, withErrorHandling(({ name }) => {
474
+ const success = mailManager.setRuleEnabled(name, true);
475
+ if (!success) {
476
+ return errorResponse(`Failed to enable rule "${name}"`);
477
+ }
478
+ return successResponse(`Rule "${name}" enabled`);
479
+ }, "Error enabling rule"));
480
+ // --- disable-rule ---
481
+ server.tool("disable-rule", {
482
+ name: z.string().min(1, "Rule name is required"),
483
+ }, withErrorHandling(({ name }) => {
484
+ const success = mailManager.setRuleEnabled(name, false);
485
+ if (!success) {
486
+ return errorResponse(`Failed to disable rule "${name}"`);
487
+ }
488
+ return successResponse(`Rule "${name}" disabled`);
489
+ }, "Error disabling rule"));
490
+ // =============================================================================
491
+ // Contacts Tools
492
+ // =============================================================================
493
+ // --- search-contacts ---
494
+ server.tool("search-contacts", {
495
+ query: z.string().min(1, "Search query is required"),
496
+ }, withErrorHandling(({ query }) => {
497
+ const contacts = mailManager.searchContacts(query);
498
+ if (contacts.length === 0) {
499
+ return successResponse("No contacts found");
500
+ }
501
+ const contactList = contacts
502
+ .map((c) => {
503
+ const emails = c.emails.length > 0 ? c.emails.join(", ") : "no email";
504
+ return ` - ${c.name} (${emails})`;
505
+ })
506
+ .join("\n");
507
+ return successResponse(`Found ${contacts.length} contact(s):\n${contactList}`);
508
+ }, "Error searching contacts"));
509
+ // =============================================================================
510
+ // Email Template Tools
511
+ // =============================================================================
512
+ // --- save-template ---
513
+ server.tool("save-template", {
514
+ name: z.string().min(1, "Template name is required"),
515
+ subject: z.string().min(1, "Subject is required"),
516
+ body: z.string().min(1, "Body is required"),
517
+ to: z.array(z.string()).optional().describe("Default recipients"),
518
+ cc: z.array(z.string()).optional().describe("Default CC recipients"),
519
+ id: z.string().optional().describe("Template ID (for updating existing template)"),
520
+ }, withErrorHandling(({ name, subject, body, to, cc, id }) => {
521
+ const template = mailManager.saveTemplate(name, subject, body, to, cc, id);
522
+ return successResponse(`Template "${template.name}" saved with ID: ${template.id}`);
523
+ }, "Error saving template"));
524
+ // --- list-templates ---
525
+ server.tool("list-templates", {}, withErrorHandling(() => {
526
+ const templates = mailManager.listTemplates();
527
+ if (templates.length === 0) {
528
+ return successResponse("No templates saved");
529
+ }
530
+ const templateList = templates
531
+ .map((t) => ` - [${t.id}] ${t.name} — "${t.subject}"`)
532
+ .join("\n");
533
+ return successResponse(`Found ${templates.length} template(s):\n${templateList}`);
534
+ }, "Error listing templates"));
535
+ // --- get-template ---
536
+ server.tool("get-template", {
537
+ id: z.string().min(1, "Template ID is required"),
538
+ }, withErrorHandling(({ id }) => {
539
+ const template = mailManager.getTemplate(id);
540
+ if (!template) {
541
+ return errorResponse(`Template "${id}" not found`);
542
+ }
543
+ const lines = [
544
+ `Name: ${template.name}`,
545
+ `Subject: ${template.subject}`,
546
+ template.to ? `To: ${template.to.join(", ")}` : null,
547
+ template.cc ? `CC: ${template.cc.join(", ")}` : null,
548
+ `\n${template.body}`,
549
+ ]
550
+ .filter(Boolean)
551
+ .join("\n");
552
+ return successResponse(lines);
553
+ }, "Error getting template"));
554
+ // --- delete-template ---
555
+ server.tool("delete-template", {
556
+ id: z.string().min(1, "Template ID is required"),
557
+ }, withErrorHandling(({ id }) => {
558
+ const success = mailManager.deleteTemplate(id);
559
+ if (!success) {
560
+ return errorResponse(`Template "${id}" not found`);
561
+ }
562
+ return successResponse(`Template "${id}" deleted`);
563
+ }, "Error deleting template"));
564
+ // --- use-template ---
565
+ server.tool("use-template", {
566
+ id: z.string().min(1, "Template ID is required"),
567
+ to: z.array(z.string()).optional().describe("Override recipients"),
568
+ cc: z.array(z.string()).optional().describe("Override CC recipients"),
569
+ subject: z.string().optional().describe("Override subject"),
570
+ body: z.string().optional().describe("Override body"),
571
+ }, withErrorHandling(({ id, to, cc, subject, body }) => {
572
+ const success = mailManager.useTemplate(id, { to, cc, subject, body });
573
+ if (!success) {
574
+ return errorResponse(`Failed to use template "${id}". Template not found or no recipients.`);
575
+ }
576
+ return successResponse(`Draft created from template "${id}"`);
577
+ }, "Error using template"));
578
+ // =============================================================================
340
579
  // Diagnostics Tools
341
580
  // =============================================================================
342
581
  // --- health-check ---