apple-mail-mcp 2.1.2 → 2.1.3
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 +41 -0
- package/build/index.js +45 -45
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -6,6 +6,10 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that e
|
|
|
6
6
|
[](https://github.com/sweetrb/apple-mail-mcp/actions/workflows/ci.yml)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="codex/assets/screenshot.png" alt="Apple Mail MCP — read, search, send, and organize Apple Mail from Codex, Claude, and other AI assistants" width="680">
|
|
11
|
+
</p>
|
|
12
|
+
|
|
9
13
|
> **Note:** This is the **npm/Node.js** package — install with `npx` or `npm`. There is an unrelated Python project of the same name on PyPI ([`imdinu/apple-mail-mcp`](https://github.com/imdinu/apple-mail-mcp)) installed via `pipx`/`uvx`. If you're using `uvx` and seeing a `cyclopts` dependency error, you're looking for that project, not this one.
|
|
10
14
|
|
|
11
15
|
## What is This?
|
|
@@ -45,6 +49,21 @@ Install as a Claude Code plugin for automatic configuration and enhanced AI beha
|
|
|
45
49
|
|
|
46
50
|
This method also installs a **skill** that teaches Claude when and how to use Apple Mail effectively.
|
|
47
51
|
|
|
52
|
+
### Using the Codex Marketplace
|
|
53
|
+
|
|
54
|
+
Install the same public marketplace in Codex:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
codex plugin marketplace add sweetrb/apple-mail-mcp
|
|
58
|
+
codex plugin add apple-mail@apple-mail-mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The Codex package registers the same `apple-mail` MCP server through `npx -y github:sweetrb/apple-mail-mcp` and includes the Apple Mail skill guidance.
|
|
62
|
+
|
|
63
|
+
### Other Hosts (Hermes, Antigravity)
|
|
64
|
+
|
|
65
|
+
Plugin packaging for the Hermes and Antigravity hosts is also included (`.hermes-plugin/` and `.antigravity-plugin/`). Each registers the same `apple-mail` MCP server (launched via `npx -y github:sweetrb/apple-mail-mcp`) and bundles the Apple Mail skill, so behavior matches the Claude Code and Codex plugins. Install them through each host's plugin/marketplace mechanism pointed at this repository.
|
|
66
|
+
|
|
48
67
|
### Manual Installation
|
|
49
68
|
|
|
50
69
|
**1. Install the server:**
|
|
@@ -218,6 +237,8 @@ List messages in a mailbox.
|
|
|
218
237
|
|
|
219
238
|
Send a new email immediately.
|
|
220
239
|
|
|
240
|
+
**⚠️ Safety:** Sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling.
|
|
241
|
+
|
|
221
242
|
| Parameter | Type | Required | Description |
|
|
222
243
|
|-----------|------|----------|-------------|
|
|
223
244
|
| `to` | string[] | Yes | Recipient addresses |
|
|
@@ -450,6 +471,8 @@ Each recipient object:
|
|
|
450
471
|
|
|
451
472
|
**Returns:** Per-recipient success/failure results with a summary count.
|
|
452
473
|
|
|
474
|
+
**⚠️ Safety:** Sends real mail immediately to every recipient and cannot be unsent. Confirm the recipient list, subject, and body with the user before calling.
|
|
475
|
+
|
|
453
476
|
---
|
|
454
477
|
|
|
455
478
|
#### `create-draft`
|
|
@@ -523,6 +546,8 @@ Reply to an existing message.
|
|
|
523
546
|
}
|
|
524
547
|
```
|
|
525
548
|
|
|
549
|
+
**⚠️ Safety:** With the default `send: true`, sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling (or pass `send: false` to save a draft for review).
|
|
550
|
+
|
|
526
551
|
---
|
|
527
552
|
|
|
528
553
|
#### `forward-message`
|
|
@@ -536,6 +561,8 @@ Forward a message to new recipients.
|
|
|
536
561
|
| `body` | string | No | Message to prepend |
|
|
537
562
|
| `send` | boolean | No | Send immediately (default: true, false = save as draft) |
|
|
538
563
|
|
|
564
|
+
**⚠️ Safety:** With the default `send: true`, sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling (or pass `send: false` to save a draft for review).
|
|
565
|
+
|
|
539
566
|
---
|
|
540
567
|
|
|
541
568
|
#### `mark-as-read` / `mark-as-unread`
|
|
@@ -566,6 +593,8 @@ Delete a message (move to trash).
|
|
|
566
593
|
|-----------|------|----------|-------------|
|
|
567
594
|
| `id` | string | Yes | Message ID |
|
|
568
595
|
|
|
596
|
+
**⚠️ Safety:** Destructive. Requires explicit user confirmation; search/list first to confirm the message id.
|
|
597
|
+
|
|
569
598
|
---
|
|
570
599
|
|
|
571
600
|
#### `move-message`
|
|
@@ -614,6 +643,8 @@ All batch operations accept an array of message IDs (max 100 per batch) and retu
|
|
|
614
643
|
|-----------|------|----------|-------------|
|
|
615
644
|
| `ids` | string[] | Yes | Message IDs to delete (max 100) |
|
|
616
645
|
|
|
646
|
+
**⚠️ Safety:** Destructive. Requires explicit user confirmation; search/list first to confirm the message ids.
|
|
647
|
+
|
|
617
648
|
#### `batch-move-messages`
|
|
618
649
|
|
|
619
650
|
| Parameter | Type | Required | Description |
|
|
@@ -681,6 +712,8 @@ Delete a mailbox.
|
|
|
681
712
|
| `name` | string | Yes | Mailbox name |
|
|
682
713
|
| `account` | string | No | Account containing mailbox |
|
|
683
714
|
|
|
715
|
+
**⚠️ Safety:** Destructive — deletes the mailbox and its contents. Requires explicit user confirmation; list mailboxes first to confirm the name.
|
|
716
|
+
|
|
684
717
|
---
|
|
685
718
|
|
|
686
719
|
#### `rename-mailbox`
|
|
@@ -762,6 +795,8 @@ Delete a mail rule by name.
|
|
|
762
795
|
|-----------|------|----------|-------------|
|
|
763
796
|
| `name` | string | Yes | Rule name |
|
|
764
797
|
|
|
798
|
+
**⚠️ Safety:** Destructive. Requires explicit user confirmation; list rules first to confirm the name.
|
|
799
|
+
|
|
765
800
|
---
|
|
766
801
|
|
|
767
802
|
### Contacts
|
|
@@ -824,6 +859,8 @@ Delete a template.
|
|
|
824
859
|
|-----------|------|----------|-------------|
|
|
825
860
|
| `id` | string | Yes | Template ID |
|
|
826
861
|
|
|
862
|
+
**⚠️ Safety:** Destructive — removes the template from the on-disk store. Requires explicit user confirmation; list templates first to confirm the id.
|
|
863
|
+
|
|
827
864
|
---
|
|
828
865
|
|
|
829
866
|
#### `use-template`
|
|
@@ -1147,3 +1184,7 @@ Part of a family of macOS MCP servers:
|
|
|
1147
1184
|
- [apple-notes-mcp](https://github.com/sweetrb/apple-notes-mcp) — MCP server for Apple Notes (create, search, update, and export notes)
|
|
1148
1185
|
- [apple-numbers-mcp](https://github.com/sweetrb/apple-numbers-mcp) — MCP server for Apple Numbers (read and write .numbers spreadsheets)
|
|
1149
1186
|
- [apple-photos-mcp](https://github.com/sweetrb/apple-photos-mcp) — MCP server for Apple Photos (query metadata and export originals)
|
|
1187
|
+
|
|
1188
|
+
## Recurring macOS permission prompts
|
|
1189
|
+
|
|
1190
|
+
If macOS keeps re-prompting for Full Disk Access or Automation for `node` (often after a `brew upgrade`), see [docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md](docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md) — the fix is to run this server under the official, Developer-ID-signed Node so the grant survives Node updates.
|
package/build/index.js
CHANGED
|
@@ -132,7 +132,7 @@ async function hybridBatchCounts(ids, appleFn, imapFn) {
|
|
|
132
132
|
// Message Tools
|
|
133
133
|
// =============================================================================
|
|
134
134
|
// --- search-messages ---
|
|
135
|
-
server.tool("search-messages", {
|
|
135
|
+
server.tool("search-messages", "Use when: finding messages by query/sender/subject/date/read/flag filters and you need their ids for follow-up operations.\nReturns: matching messages with id, date, subject, sender, and read state (plus partial-coverage diagnostics when some mailboxes were skipped).\nDo not use when: you want a plain mailbox listing without filters (use list-messages), already have an id and want the body (use get-message), or want a whole conversation (use get-thread).\nPrefer this first to obtain the message ids that get-message/mark-as-read/delete-message/move-message and the batch tools require.", {
|
|
136
136
|
query: z.string().optional().describe("Text to search for in subject, sender, or content"),
|
|
137
137
|
from: z
|
|
138
138
|
.string()
|
|
@@ -188,7 +188,7 @@ server.tool("search-messages", {
|
|
|
188
188
|
return successResponse(`Found ${messages.length} message(s):\n${messageList}${coverageBlock}`, structured);
|
|
189
189
|
}, "Error searching messages"));
|
|
190
190
|
// --- get-message ---
|
|
191
|
-
server.tool("get-message", {
|
|
191
|
+
server.tool("get-message", "Use when: reading the full body of one message whose id you already have (numeric or imap:…); set preferHtml to get the HTML body instead of plain text.\nReturns: the message subject and body (plain text by default, HTML when preferHtml is true).\nDo not use when: you don't yet have an id (use search-messages or list-messages first), or you want the whole conversation (use get-thread).", {
|
|
192
192
|
id: MESSAGE_ID_SCHEMA,
|
|
193
193
|
preferHtml: z
|
|
194
194
|
.boolean()
|
|
@@ -215,7 +215,7 @@ server.tool("get-message", {
|
|
|
215
215
|
fail: `Message with ID "${id}" not found`,
|
|
216
216
|
}), "Error retrieving message"));
|
|
217
217
|
// --- get-thread ---
|
|
218
|
-
server.tool("get-thread", {
|
|
218
|
+
server.tool("get-thread", "Use when: you have one message id and want the whole conversation it belongs to, oldest-first. With an imap: id it threads by References/Message-ID; otherwise it groups by normalized subject.\nReturns: the thread's normalized subject and its messages (id, date, subject, sender, read state).\nDo not use when: you only need the single message (use get-message) or are searching by arbitrary criteria (use search-messages).", {
|
|
219
219
|
id: MESSAGE_ID_SCHEMA.describe("A message ID in the conversation (numeric or imap:…)"),
|
|
220
220
|
account: z.string().optional().describe("Account to search (omit to search all)"),
|
|
221
221
|
mailbox: z.string().optional().describe("Mailbox to search (omit to search all)"),
|
|
@@ -273,7 +273,7 @@ server.tool("get-thread", {
|
|
|
273
273
|
return successResponse(`Thread "${base}" — ${ordered.length} message(s), oldest first:\n${list}${coverageBlock}`, structured);
|
|
274
274
|
}, "Error retrieving thread"));
|
|
275
275
|
// --- list-messages ---
|
|
276
|
-
server.tool("list-messages", {
|
|
276
|
+
server.tool("list-messages", "Use when: browsing a mailbox's recent messages (optionally filtered by sender or unread-only) with pagination via limit/offset, and you need their ids.\nReturns: messages with id, date, subject, and sender (plus partial-coverage diagnostics when some mailboxes were skipped).\nDo not use when: you have specific search criteria like subject/date/flags (use search-messages) or already have an id and want the body (use get-message).\nLike search-messages, use this to obtain the ids that read/mark/delete/move and batch tools require.", {
|
|
277
277
|
mailbox: z
|
|
278
278
|
.string()
|
|
279
279
|
.optional()
|
|
@@ -311,7 +311,7 @@ server.tool("list-messages", {
|
|
|
311
311
|
return successResponse(`Found ${messages.length} message(s):\n${messageList}${coverageBlock}`, structured);
|
|
312
312
|
}, "Error listing messages"));
|
|
313
313
|
// --- send-email ---
|
|
314
|
-
server.tool("send-email", {
|
|
314
|
+
server.tool("send-email", "Use when: the user has explicitly confirmed they want to send a single email now to the given recipients (to/cc/bcc are arrays), optionally with attachments and a chosen transport.\nReturns: a confirmation naming the recipients and attachment count.\nDo not use when: the user wants to review first (use create-draft), is replying to or forwarding an existing message (use reply-to-message / forward-message), or wants per-recipient personalized copies (use send-serial-email).\nSafety: this SENDS real email immediately and it cannot be unsent — require explicit user confirmation of the exact recipients, subject, and body before calling. Prefer create-draft when there is any doubt.", {
|
|
315
315
|
to: z.array(z.string()).min(1, "At least one recipient is required"),
|
|
316
316
|
subject: z.string().min(1, "Subject is required"),
|
|
317
317
|
body: z.string().min(1, "Body is required"),
|
|
@@ -341,7 +341,7 @@ server.tool("send-email", {
|
|
|
341
341
|
return successResponse(`Email sent to ${to.join(", ")}${attachInfo}`);
|
|
342
342
|
}, "Error sending email"));
|
|
343
343
|
// --- send-serial-email ---
|
|
344
|
-
server.tool("send-serial-email", {
|
|
344
|
+
server.tool("send-serial-email", "Use when: the user has confirmed a mail-merge — sending individually personalized copies to many recipients (max 100), with {{Key}} placeholders in subject/body replaced per-recipient from each recipient's variables. Recipients do not see each other.\nReturns: a per-recipient sent/failed report with counts.\nDo not use when: sending one message to a shared recipient list (use send-email) or saving for review (use create-draft).\nSafety: this SENDS many real emails immediately and they cannot be unsent — require explicit user confirmation of the recipient list, the subject/body template, and the placeholder substitutions before calling.", {
|
|
345
345
|
recipients: z
|
|
346
346
|
.array(z.object({
|
|
347
347
|
email: z.string().min(1, "Recipient email is required"),
|
|
@@ -385,7 +385,7 @@ server.tool("send-serial-email", {
|
|
|
385
385
|
}
|
|
386
386
|
}, "Error sending serial emails"));
|
|
387
387
|
// --- create-draft ---
|
|
388
|
-
server.tool("create-draft", {
|
|
388
|
+
server.tool("create-draft", "Use when: composing an email the user should review in Mail.app before sending — the safe default for any new message (to/cc/bcc are arrays, optional attachments).\nReturns: a confirmation that the draft was created, with recipients and attachment count.\nDo not use when: the user has already confirmed they want it sent now (use send-email).\nSafety: low risk — creates a draft only and sends nothing; the user must open Mail.app and send it themselves.", {
|
|
389
389
|
to: z.array(z.string()).min(1, "At least one recipient is required"),
|
|
390
390
|
subject: z.string().min(1, "Subject is required"),
|
|
391
391
|
body: z.string().min(1, "Body is required"),
|
|
@@ -402,7 +402,7 @@ server.tool("create-draft", {
|
|
|
402
402
|
return successResponse(`Draft created for ${to.join(", ")}${attachInfo}`);
|
|
403
403
|
}, "Error creating draft"));
|
|
404
404
|
// --- reply-to-message ---
|
|
405
|
-
server.tool("reply-to-message", {
|
|
405
|
+
server.tool("reply-to-message", "Use when: replying to an existing message by id, preserving its threading headers. Set replyAll for all recipients; set send=false to save as a draft instead of sending.\nReturns: a confirmation that the reply was sent or saved as a draft.\nDo not use when: composing a brand-new message (use send-email / create-draft) or forwarding to new recipients (use forward-message).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent — require explicit user confirmation of the recipients and body, or pass send=false to let the user review.", {
|
|
406
406
|
id: MESSAGE_ID_SCHEMA,
|
|
407
407
|
body: z.string().min(1, "Reply body is required"),
|
|
408
408
|
replyAll: z.boolean().optional().default(false).describe("Reply to all recipients"),
|
|
@@ -415,7 +415,7 @@ server.tool("reply-to-message", {
|
|
|
415
415
|
return successResponse(send ? "Reply sent" : "Reply saved as draft");
|
|
416
416
|
}, "Error replying to message"));
|
|
417
417
|
// --- forward-message ---
|
|
418
|
-
server.tool("forward-message", {
|
|
418
|
+
server.tool("forward-message", "Use when: forwarding an existing message (by id) to new recipients (to is an array), with an optional body to prepend. Set send=false to save as a draft.\nReturns: a confirmation that the message was forwarded or saved as a draft.\nDo not use when: replying to the sender/recipients (use reply-to-message) or composing a new message (use send-email / create-draft).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent — require explicit user confirmation of the recipients and any prepended body, or pass send=false to let the user review.", {
|
|
419
419
|
id: MESSAGE_ID_SCHEMA,
|
|
420
420
|
to: z.array(z.string()).min(1, "At least one recipient is required"),
|
|
421
421
|
body: z.string().optional().describe("Optional message to prepend"),
|
|
@@ -428,7 +428,7 @@ server.tool("forward-message", {
|
|
|
428
428
|
return successResponse(send ? `Message forwarded to ${to.join(", ")}` : "Forward saved as draft");
|
|
429
429
|
}, "Error forwarding message"));
|
|
430
430
|
// --- mark-as-read ---
|
|
431
|
-
server.tool("mark-as-read", {
|
|
431
|
+
server.tool("mark-as-read", "Use when: marking a single message (by id) as read.\nReturns: a confirmation that the message was marked read.\nDo not use when: marking several at once (use batch-mark-as-read) or marking unread (use mark-as-unread). Get the id from search-messages or list-messages first.", {
|
|
432
432
|
id: MESSAGE_ID_SCHEMA,
|
|
433
433
|
}, withErrorHandling(({ id }) => routeMessage(id, {
|
|
434
434
|
imap: () => imapMarkRead(id),
|
|
@@ -439,7 +439,7 @@ server.tool("mark-as-read", {
|
|
|
439
439
|
fail: `Failed to mark message "${id}" as read`,
|
|
440
440
|
}), "Error marking message as read"));
|
|
441
441
|
// --- mark-as-unread ---
|
|
442
|
-
server.tool("mark-as-unread", {
|
|
442
|
+
server.tool("mark-as-unread", "Use when: marking a single message (by id) as unread.\nReturns: a confirmation that the message was marked unread.\nDo not use when: marking several at once (use batch-mark-as-unread) or marking read (use mark-as-read). Get the id from search-messages or list-messages first.", {
|
|
443
443
|
id: MESSAGE_ID_SCHEMA,
|
|
444
444
|
}, withErrorHandling(({ id }) => routeMessage(id, {
|
|
445
445
|
imap: () => imapMarkUnread(id),
|
|
@@ -450,7 +450,7 @@ server.tool("mark-as-unread", {
|
|
|
450
450
|
fail: `Failed to mark message "${id}" as unread`,
|
|
451
451
|
}), "Error marking message as unread"));
|
|
452
452
|
// --- flag-message ---
|
|
453
|
-
server.tool("flag-message", {
|
|
453
|
+
server.tool("flag-message", "Use when: flagging a single message (by id).\nReturns: a confirmation that the message was flagged.\nDo not use when: flagging several at once (use batch-flag-messages) or removing a flag (use unflag-message). Get the id from search-messages or list-messages first.", {
|
|
454
454
|
id: MESSAGE_ID_SCHEMA,
|
|
455
455
|
}, withErrorHandling(({ id }) => routeMessage(id, {
|
|
456
456
|
imap: () => imapFlagMessage(id),
|
|
@@ -461,7 +461,7 @@ server.tool("flag-message", {
|
|
|
461
461
|
fail: `Failed to flag message "${id}"`,
|
|
462
462
|
}), "Error flagging message"));
|
|
463
463
|
// --- unflag-message ---
|
|
464
|
-
server.tool("unflag-message", {
|
|
464
|
+
server.tool("unflag-message", "Use when: removing the flag from a single message (by id).\nReturns: a confirmation that the message was unflagged.\nDo not use when: unflagging several at once (use batch-unflag-messages) or adding a flag (use flag-message). Get the id from search-messages or list-messages first.", {
|
|
465
465
|
id: MESSAGE_ID_SCHEMA,
|
|
466
466
|
}, withErrorHandling(({ id }) => routeMessage(id, {
|
|
467
467
|
imap: () => imapUnflagMessage(id),
|
|
@@ -472,7 +472,7 @@ server.tool("unflag-message", {
|
|
|
472
472
|
fail: `Failed to unflag message "${id}"`,
|
|
473
473
|
}), "Error unflagging message"));
|
|
474
474
|
// --- delete-message ---
|
|
475
|
-
server.tool("delete-message", {
|
|
475
|
+
server.tool("delete-message", "Use when: deleting a single message by id (moves it to Trash).\nReturns: a confirmation that the message was deleted.\nDo not use when: deleting several at once (use batch-delete-messages) or just filing it away (use move-message).\nSafety: destructive — require explicit user confirmation, and search-messages/list-messages first to confirm you have the right id before deleting.", {
|
|
476
476
|
id: MESSAGE_ID_SCHEMA,
|
|
477
477
|
}, withErrorHandling(({ id }) => routeMessage(id, {
|
|
478
478
|
imap: () => imapDeleteMessageById(id),
|
|
@@ -486,7 +486,7 @@ server.tool("delete-message", {
|
|
|
486
486
|
fail: `Failed to delete message "${id}"`,
|
|
487
487
|
}), "Error deleting message"));
|
|
488
488
|
// --- move-message ---
|
|
489
|
-
server.tool("move-message", {
|
|
489
|
+
server.tool("move-message", "Use when: moving a single message (by id) into another mailbox/folder, e.g. archiving or filing.\nReturns: a confirmation naming the destination mailbox.\nDo not use when: moving several at once (use batch-move-messages) or deleting (use delete-message). Use list-mailboxes to confirm the destination name exists.\nSafety: moves a real message between folders — confirm the destination mailbox, and search-messages/list-messages first to confirm the id.", {
|
|
490
490
|
id: MESSAGE_ID_SCHEMA,
|
|
491
491
|
mailbox: z.string().min(1, "Destination mailbox is required"),
|
|
492
492
|
account: z.string().optional().describe("Account containing the destination mailbox"),
|
|
@@ -502,7 +502,7 @@ server.tool("move-message", {
|
|
|
502
502
|
fail: `Failed to move message to "${mailbox}"`,
|
|
503
503
|
}), "Error moving message"));
|
|
504
504
|
// --- batch-delete-messages ---
|
|
505
|
-
server.tool("batch-delete-messages", {
|
|
505
|
+
server.tool("batch-delete-messages", "Use when: deleting multiple messages in one call (1–100 ids; moves them to Trash).\nReturns: counts of how many were deleted and how many failed.\nDo not use when: deleting just one (use delete-message) or filing messages away (use batch-move-messages).\nSafety: destructive and applies to many messages at once — require explicit user confirmation, and search-messages/list-messages first to confirm every id is correct before deleting.", {
|
|
506
506
|
ids: BATCH_IDS_SCHEMA,
|
|
507
507
|
}, withErrorHandling(async ({ ids }) => {
|
|
508
508
|
const { success: successCount, fail: failCount } = await hybridBatchCounts(ids, (n) => mailManager.batchDeleteMessages(n), (im) => imapBatchDelete(im));
|
|
@@ -517,7 +517,7 @@ server.tool("batch-delete-messages", {
|
|
|
517
517
|
}
|
|
518
518
|
}, "Error batch deleting messages"));
|
|
519
519
|
// --- batch-move-messages ---
|
|
520
|
-
server.tool("batch-move-messages", {
|
|
520
|
+
server.tool("batch-move-messages", "Use when: moving multiple messages (1–100 ids) into the same destination mailbox/folder in one call, e.g. bulk archiving.\nReturns: counts of how many were moved and how many failed.\nDo not use when: moving just one (use move-message) or deleting (use batch-delete-messages). Use list-mailboxes to confirm the destination name exists.\nSafety: moves many real messages at once — confirm the destination mailbox, and search-messages/list-messages first to confirm the ids.", {
|
|
521
521
|
ids: BATCH_IDS_SCHEMA,
|
|
522
522
|
mailbox: z.string().min(1, "Destination mailbox is required"),
|
|
523
523
|
account: z.string().optional().describe("Account containing the destination mailbox"),
|
|
@@ -534,7 +534,7 @@ server.tool("batch-move-messages", {
|
|
|
534
534
|
}
|
|
535
535
|
}, "Error batch moving messages"));
|
|
536
536
|
// --- batch-mark-as-read ---
|
|
537
|
-
server.tool("batch-mark-as-read", {
|
|
537
|
+
server.tool("batch-mark-as-read", "Use when: marking multiple messages (1–100 ids) as read in one call.\nReturns: counts of how many were marked read and how many failed.\nDo not use when: marking just one (use mark-as-read) or marking unread (use batch-mark-as-unread). Get the ids from search-messages or list-messages first.", {
|
|
538
538
|
ids: BATCH_IDS_SCHEMA,
|
|
539
539
|
}, withErrorHandling(async ({ ids }) => {
|
|
540
540
|
const { success: successCount, fail: failCount } = await hybridBatchCounts(ids, (n) => mailManager.batchMarkAsRead(n), (im) => imapBatchMarkRead(im));
|
|
@@ -549,7 +549,7 @@ server.tool("batch-mark-as-read", {
|
|
|
549
549
|
}
|
|
550
550
|
}, "Error batch marking messages as read"));
|
|
551
551
|
// --- batch-mark-as-unread ---
|
|
552
|
-
server.tool("batch-mark-as-unread", {
|
|
552
|
+
server.tool("batch-mark-as-unread", "Use when: marking multiple messages (1–100 ids) as unread in one call.\nReturns: counts of how many were marked unread and how many failed.\nDo not use when: marking just one (use mark-as-unread) or marking read (use batch-mark-as-read). Get the ids from search-messages or list-messages first.", {
|
|
553
553
|
ids: BATCH_IDS_SCHEMA,
|
|
554
554
|
}, withErrorHandling(async ({ ids }) => {
|
|
555
555
|
const { success: successCount, fail: failCount } = await hybridBatchCounts(ids, (n) => mailManager.batchMarkAsUnread(n), (im) => imapBatchMarkUnread(im));
|
|
@@ -564,7 +564,7 @@ server.tool("batch-mark-as-unread", {
|
|
|
564
564
|
}
|
|
565
565
|
}, "Error batch marking messages as unread"));
|
|
566
566
|
// --- batch-flag-messages ---
|
|
567
|
-
server.tool("batch-flag-messages", {
|
|
567
|
+
server.tool("batch-flag-messages", "Use when: flagging multiple messages (1–100 ids) in one call.\nReturns: counts of how many were flagged and how many failed.\nDo not use when: flagging just one (use flag-message) or removing flags (use batch-unflag-messages). Get the ids from search-messages or list-messages first.", {
|
|
568
568
|
ids: BATCH_IDS_SCHEMA,
|
|
569
569
|
}, withErrorHandling(async ({ ids }) => {
|
|
570
570
|
const { success: successCount, fail: failCount } = await hybridBatchCounts(ids, (n) => mailManager.batchFlagMessages(n), (im) => imapBatchFlag(im));
|
|
@@ -579,7 +579,7 @@ server.tool("batch-flag-messages", {
|
|
|
579
579
|
}
|
|
580
580
|
}, "Error batch flagging messages"));
|
|
581
581
|
// --- batch-unflag-messages ---
|
|
582
|
-
server.tool("batch-unflag-messages", {
|
|
582
|
+
server.tool("batch-unflag-messages", "Use when: removing flags from multiple messages (1–100 ids) in one call.\nReturns: counts of how many were unflagged and how many failed.\nDo not use when: unflagging just one (use unflag-message) or adding flags (use batch-flag-messages). Get the ids from search-messages or list-messages first.", {
|
|
583
583
|
ids: BATCH_IDS_SCHEMA,
|
|
584
584
|
}, withErrorHandling(async ({ ids }) => {
|
|
585
585
|
const { success: successCount, fail: failCount } = await hybridBatchCounts(ids, (n) => mailManager.batchUnflagMessages(n), (im) => imapBatchUnflag(im));
|
|
@@ -594,7 +594,7 @@ server.tool("batch-unflag-messages", {
|
|
|
594
594
|
}
|
|
595
595
|
}, "Error batch unflagging messages"));
|
|
596
596
|
// --- list-attachments ---
|
|
597
|
-
server.tool("list-attachments", {
|
|
597
|
+
server.tool("list-attachments", "Use when: enumerating a message's attachments (by id) to discover their names, MIME types, and sizes — typically before saving or fetching one.\nReturns: each attachment's name, MIME type, and size, plus a count.\nDo not use when: you want the bytes (use fetch-attachment for inline base64, or save-attachment to write to disk). Get the message id from search-messages or list-messages first.", {
|
|
598
598
|
id: MESSAGE_ID_SCHEMA,
|
|
599
599
|
}, withErrorHandling(async ({ id }) => {
|
|
600
600
|
// IMAP (I1): BODYSTRUCTURE enumerates parts (incl. MIME attachments
|
|
@@ -620,7 +620,7 @@ server.tool("list-attachments", {
|
|
|
620
620
|
return successResponse(`Found ${attachments.length} attachment(s):\n${attachmentList}`, structured);
|
|
621
621
|
}, "Error listing attachments"));
|
|
622
622
|
// --- save-attachment ---
|
|
623
|
-
server.tool("save-attachment", {
|
|
623
|
+
server.tool("save-attachment", "Use when: writing one of a message's attachments to disk, by message id and attachmentName, into the savePath directory (saved as savePath/attachmentName).\nReturns: a confirmation of the saved file path.\nDo not use when: you don't know the attachment name (use list-attachments first) or want the bytes inline rather than on disk (use fetch-attachment).\nSafety: writes a file to disk — savePath must be a directory inside the configured allowed roots, and attachmentName may not contain path separators or '..'; calls outside those constraints are rejected.", {
|
|
624
624
|
id: MESSAGE_ID_SCHEMA,
|
|
625
625
|
attachmentName: z.string().min(1, "Attachment name is required"),
|
|
626
626
|
savePath: z.string().min(1, "Save directory path is required"),
|
|
@@ -650,7 +650,7 @@ server.tool("save-attachment", {
|
|
|
650
650
|
return successResponse(`Attachment "${attachmentName}" saved to ${savePath}`);
|
|
651
651
|
}, "Error saving attachment"));
|
|
652
652
|
// --- fetch-attachment ---
|
|
653
|
-
server.tool("fetch-attachment", {
|
|
653
|
+
server.tool("fetch-attachment", "Use when: retrieving an attachment's raw bytes inline as base64 (by message id and attachmentName), e.g. to process its contents without touching disk.\nReturns: the attachment's bytes base64-encoded, with its size and (for IMAP) MIME type.\nDo not use when: you don't know the attachment name (use list-attachments first) or you just want it saved to disk (use save-attachment).", {
|
|
654
654
|
id: MESSAGE_ID_SCHEMA,
|
|
655
655
|
attachmentName: z.string().min(1, "Attachment name is required"),
|
|
656
656
|
}, withErrorHandling(async ({ id, attachmentName }) => {
|
|
@@ -674,7 +674,7 @@ server.tool("fetch-attachment", {
|
|
|
674
674
|
// Mailbox Tools
|
|
675
675
|
// =============================================================================
|
|
676
676
|
// --- list-mailboxes ---
|
|
677
|
-
server.tool("list-mailboxes", {
|
|
677
|
+
server.tool("list-mailboxes", "Use when: discovering the mailbox/folder names (and unread/message counts) available in an account, e.g. before moving messages or searching a specific mailbox.\nReturns: each mailbox's name with its unread (and, for IMAP, total message) count, plus a count.\nDo not use when: you want the messages inside a mailbox (use list-messages or search-messages) or the list of accounts (use list-accounts).", {
|
|
678
678
|
account: z.string().optional().describe("Account to list mailboxes from"),
|
|
679
679
|
}, withErrorHandling(async ({ account }) => {
|
|
680
680
|
// IMAP (I6): LIST + per-mailbox STATUS — sees the true server hierarchy and
|
|
@@ -703,7 +703,7 @@ server.tool("list-mailboxes", {
|
|
|
703
703
|
return successResponse(`Found ${mailboxes.length} mailbox(es):\n${mailboxList}`, structured);
|
|
704
704
|
}, "Error listing mailboxes"));
|
|
705
705
|
// --- get-unread-count ---
|
|
706
|
-
server.tool("get-unread-count", {
|
|
706
|
+
server.tool("get-unread-count", "Use when: you only need the number of unread messages (optionally scoped to one mailbox and/or account), without listing the messages themselves.\nReturns: the unread count for the requested scope.\nDo not use when: you need the actual unread messages and their ids (use list-messages with unreadOnly, or search-messages with isRead=false) or broader totals (use get-mail-stats).", {
|
|
707
707
|
mailbox: z.string().optional().describe("Mailbox to check (default: all)"),
|
|
708
708
|
account: z.string().optional().describe("Account to check"),
|
|
709
709
|
}, withErrorHandling(async ({ mailbox, account }) => {
|
|
@@ -720,7 +720,7 @@ server.tool("get-unread-count", {
|
|
|
720
720
|
});
|
|
721
721
|
}, "Error getting unread count"));
|
|
722
722
|
// --- create-mailbox ---
|
|
723
|
-
server.tool("create-mailbox", {
|
|
723
|
+
server.tool("create-mailbox", "Use when: creating a new mailbox/folder in an account.\nReturns: a confirmation that the mailbox was created.\nDo not use when: renaming an existing one (use rename-mailbox) or deleting one (use delete-mailbox). Use list-mailboxes to see what already exists.\nSafety: creates a real folder in the mail account — confirm the name and target account first.", {
|
|
724
724
|
name: z.string().min(1, "Mailbox name is required"),
|
|
725
725
|
account: z.string().optional().describe("Account to create the mailbox in"),
|
|
726
726
|
}, withErrorHandling(async ({ name, account }) => {
|
|
@@ -739,7 +739,7 @@ server.tool("create-mailbox", {
|
|
|
739
739
|
return successResponse(`Mailbox "${name}" created`);
|
|
740
740
|
}, "Error creating mailbox"));
|
|
741
741
|
// --- delete-mailbox ---
|
|
742
|
-
server.tool("delete-mailbox", {
|
|
742
|
+
server.tool("delete-mailbox", "Use when: deleting a mailbox/folder from an account.\nReturns: a confirmation that the mailbox was deleted.\nDo not use when: renaming it (use rename-mailbox) or deleting messages within it (use delete-message / batch-delete-messages).\nSafety: destructive — deleting a mailbox removes the folder and any messages it contains. Require explicit user confirmation and use list-mailboxes first to confirm the exact name.", {
|
|
743
743
|
name: z.string().min(1, "Mailbox name is required"),
|
|
744
744
|
account: z.string().optional().describe("Account containing the mailbox"),
|
|
745
745
|
}, withErrorHandling(async ({ name, account }) => {
|
|
@@ -756,7 +756,7 @@ server.tool("delete-mailbox", {
|
|
|
756
756
|
return successResponse(`Mailbox "${name}" deleted`);
|
|
757
757
|
}, "Error deleting mailbox"));
|
|
758
758
|
// --- rename-mailbox ---
|
|
759
|
-
server.tool("rename-mailbox", {
|
|
759
|
+
server.tool("rename-mailbox", "Use when: renaming an existing mailbox/folder from oldName to newName within an account.\nReturns: a confirmation naming the old and new mailbox names.\nDo not use when: creating a new folder (use create-mailbox) or deleting one (use delete-mailbox). Use list-mailboxes to confirm the current name.\nSafety: renames a real folder in the mail account — confirm oldName matches exactly (case-sensitive) before calling.", {
|
|
760
760
|
oldName: z.string().min(1, "Current mailbox name is required"),
|
|
761
761
|
newName: z.string().min(1, "New mailbox name is required"),
|
|
762
762
|
account: z.string().optional().describe("Account containing the mailbox"),
|
|
@@ -778,7 +778,7 @@ server.tool("rename-mailbox", {
|
|
|
778
778
|
// Account Tools
|
|
779
779
|
// =============================================================================
|
|
780
780
|
// --- list-accounts ---
|
|
781
|
-
server.tool("list-accounts", {}, withErrorHandling(() => {
|
|
781
|
+
server.tool("list-accounts", "Use when: discovering the configured Mail accounts (e.g. iCloud, Gmail) so you can pass an exact account name to other tools.\nReturns: the account names and a count.\nDo not use when: you want the folders within an account (use list-mailboxes) or messages (use list-messages / search-messages).", {}, withErrorHandling(() => {
|
|
782
782
|
const accounts = mailManager.listAccounts();
|
|
783
783
|
const structured = { accounts, count: accounts.length };
|
|
784
784
|
if (accounts.length === 0) {
|
|
@@ -791,7 +791,7 @@ server.tool("list-accounts", {}, withErrorHandling(() => {
|
|
|
791
791
|
// Mail Rules Tools
|
|
792
792
|
// =============================================================================
|
|
793
793
|
// --- list-rules ---
|
|
794
|
-
server.tool("list-rules", {}, withErrorHandling(() => {
|
|
794
|
+
server.tool("list-rules", "Use when: discovering the Mail rules that exist and whether each is enabled or disabled, e.g. before enabling/disabling/deleting one.\nReturns: each rule's name and enabled/disabled state.\nDo not use when: you want to change a rule (use enable-rule / disable-rule / create-rule / delete-rule).", {}, withErrorHandling(() => {
|
|
795
795
|
const rules = mailManager.listRules();
|
|
796
796
|
if (rules.length === 0) {
|
|
797
797
|
return successResponse("No mail rules found");
|
|
@@ -802,7 +802,7 @@ server.tool("list-rules", {}, withErrorHandling(() => {
|
|
|
802
802
|
return successResponse(`Found ${rules.length} rule(s):\n${ruleList}`);
|
|
803
803
|
}, "Error listing rules"));
|
|
804
804
|
// --- enable-rule ---
|
|
805
|
-
server.tool("enable-rule", {
|
|
805
|
+
server.tool("enable-rule", "Use when: turning on an existing Mail rule by name.\nReturns: a confirmation that the rule was enabled.\nDo not use when: turning a rule off (use disable-rule), creating one (use create-rule), or deleting one (use delete-rule). Use list-rules to confirm the exact rule name.", {
|
|
806
806
|
name: z.string().min(1, "Rule name is required"),
|
|
807
807
|
}, withErrorHandling(({ name }) => {
|
|
808
808
|
const success = mailManager.setRuleEnabled(name, true);
|
|
@@ -812,7 +812,7 @@ server.tool("enable-rule", {
|
|
|
812
812
|
return successResponse(`Rule "${name}" enabled`);
|
|
813
813
|
}, "Error enabling rule"));
|
|
814
814
|
// --- disable-rule ---
|
|
815
|
-
server.tool("disable-rule", {
|
|
815
|
+
server.tool("disable-rule", "Use when: turning off an existing Mail rule by name (without deleting it).\nReturns: a confirmation that the rule was disabled.\nDo not use when: turning a rule on (use enable-rule), creating one (use create-rule), or removing it permanently (use delete-rule). Use list-rules to confirm the exact rule name.", {
|
|
816
816
|
name: z.string().min(1, "Rule name is required"),
|
|
817
817
|
}, withErrorHandling(({ name }) => {
|
|
818
818
|
const success = mailManager.setRuleEnabled(name, false);
|
|
@@ -822,7 +822,7 @@ server.tool("disable-rule", {
|
|
|
822
822
|
return successResponse(`Rule "${name}" disabled`);
|
|
823
823
|
}, "Error disabling rule"));
|
|
824
824
|
// --- create-rule ---
|
|
825
|
-
server.tool("create-rule", {
|
|
825
|
+
server.tool("create-rule", "Use when: creating a new Mail rule with one or more conditions (field/operator/value) and at least one action (markRead, markFlagged, delete, or moveTo). Set matchAll to require all conditions vs. any.\nReturns: a confirmation naming the rule and its condition count.\nDo not use when: toggling an existing rule (use enable-rule / disable-rule) or removing one (use delete-rule). Use list-rules to avoid duplicating an existing rule.\nSafety: creates a rule that automatically acts on real mail (including delete/move actions) on an ongoing basis — confirm the conditions and actions with the user before calling.", {
|
|
826
826
|
name: z.string().min(1, "Rule name is required"),
|
|
827
827
|
conditions: z
|
|
828
828
|
.array(z.object({
|
|
@@ -852,7 +852,7 @@ server.tool("create-rule", {
|
|
|
852
852
|
return successResponse(`Rule "${args.name}" created with ${args.conditions.length} condition(s).`, { name: args.name, created: true });
|
|
853
853
|
}, "Error creating rule"));
|
|
854
854
|
// --- delete-rule ---
|
|
855
|
-
server.tool("delete-rule", {
|
|
855
|
+
server.tool("delete-rule", "Use when: permanently removing a Mail rule by name.\nReturns: a confirmation that the rule was deleted.\nDo not use when: you only want to pause it (use disable-rule) or create one (use create-rule).\nSafety: destructive — the rule is removed permanently. Require explicit user confirmation and use list-rules first to confirm the exact name.", {
|
|
856
856
|
name: z.string().min(1, "Rule name is required"),
|
|
857
857
|
}, withErrorHandling(({ name }) => {
|
|
858
858
|
const success = mailManager.deleteRule(name);
|
|
@@ -865,7 +865,7 @@ server.tool("delete-rule", {
|
|
|
865
865
|
// Contacts Tools
|
|
866
866
|
// =============================================================================
|
|
867
867
|
// --- search-contacts ---
|
|
868
|
-
server.tool("search-contacts", {
|
|
868
|
+
server.tool("search-contacts", "Use when: looking up a person in Contacts.app by name to find their email address(es) before composing or sending mail.\nReturns: matching contacts with their names and email addresses.\nDo not use when: searching email messages (use search-messages) — this queries Contacts, not the mailbox.", {
|
|
869
869
|
query: z.string().min(1, "Search query is required"),
|
|
870
870
|
}, withErrorHandling(({ query }) => {
|
|
871
871
|
const contacts = mailManager.searchContacts(query);
|
|
@@ -884,7 +884,7 @@ server.tool("search-contacts", {
|
|
|
884
884
|
// Email Template Tools
|
|
885
885
|
// =============================================================================
|
|
886
886
|
// --- save-template ---
|
|
887
|
-
server.tool("save-template", {
|
|
887
|
+
server.tool("save-template", "Use when: creating a reusable email template (name, subject, body, optional default to/cc), or updating one by passing its existing id. Subject/body may contain placeholders for later use.\nReturns: the saved template's name and id (reuse the id with use-template / get-template / delete-template).\nDo not use when: composing a one-off message (use create-draft / send-email) or filling in a template to send (use use-template).\nSafety: writes the template to the on-disk templates store (APPLE_MAIL_MCP_TEMPLATES_FILE) and persists across restarts; passing an existing id overwrites that template.", {
|
|
888
888
|
name: z.string().min(1, "Template name is required"),
|
|
889
889
|
subject: z.string().min(1, "Subject is required"),
|
|
890
890
|
body: z.string().min(1, "Body is required"),
|
|
@@ -896,7 +896,7 @@ server.tool("save-template", {
|
|
|
896
896
|
return successResponse(`Template "${template.name}" saved with ID: ${template.id}`);
|
|
897
897
|
}, "Error saving template"));
|
|
898
898
|
// --- list-templates ---
|
|
899
|
-
server.tool("list-templates", {}, withErrorHandling(() => {
|
|
899
|
+
server.tool("list-templates", "Use when: discovering the saved email templates and their ids, e.g. before using or editing one.\nReturns: each template's id, name, and subject.\nDo not use when: you want a single template's full body (use get-template) or want to apply one (use use-template).", {}, withErrorHandling(() => {
|
|
900
900
|
const templates = mailManager.listTemplates();
|
|
901
901
|
if (templates.length === 0) {
|
|
902
902
|
return successResponse("No templates saved");
|
|
@@ -907,7 +907,7 @@ server.tool("list-templates", {}, withErrorHandling(() => {
|
|
|
907
907
|
return successResponse(`Found ${templates.length} template(s):\n${templateList}`);
|
|
908
908
|
}, "Error listing templates"));
|
|
909
909
|
// --- get-template ---
|
|
910
|
-
server.tool("get-template", {
|
|
910
|
+
server.tool("get-template", "Use when: reading the full contents of one saved template by id — its name, subject, default to/cc, and body.\nReturns: the template's name, subject, default recipients, and body text.\nDo not use when: you don't have the id (use list-templates first) or want to apply the template into a draft (use use-template).", {
|
|
911
911
|
id: z.string().min(1, "Template ID is required"),
|
|
912
912
|
}, withErrorHandling(({ id }) => {
|
|
913
913
|
const template = mailManager.getTemplate(id);
|
|
@@ -926,7 +926,7 @@ server.tool("get-template", {
|
|
|
926
926
|
return successResponse(lines);
|
|
927
927
|
}, "Error getting template"));
|
|
928
928
|
// --- delete-template ---
|
|
929
|
-
server.tool("delete-template", {
|
|
929
|
+
server.tool("delete-template", "Use when: permanently removing a saved email template by id.\nReturns: a confirmation that the template was deleted.\nDo not use when: you only want to view it (use get-template) or update it (use save-template with the existing id).\nSafety: destructive — removes the template from the on-disk store permanently. Require explicit user confirmation and use list-templates first to confirm the id.", {
|
|
930
930
|
id: z.string().min(1, "Template ID is required"),
|
|
931
931
|
}, withErrorHandling(({ id }) => {
|
|
932
932
|
const success = mailManager.deleteTemplate(id);
|
|
@@ -936,7 +936,7 @@ server.tool("delete-template", {
|
|
|
936
936
|
return successResponse(`Template "${id}" deleted`);
|
|
937
937
|
}, "Error deleting template"));
|
|
938
938
|
// --- use-template ---
|
|
939
|
-
server.tool("use-template", {
|
|
939
|
+
server.tool("use-template", "Use when: composing a new draft from a saved template (by id), optionally overriding the recipients, subject, or body. Creates a draft in Mail.app for the user to review and send.\nReturns: a confirmation that a draft was created from the template.\nDo not use when: you want to inspect the template without composing (use get-template) or send immediately without a draft (use send-email).", {
|
|
940
940
|
id: z.string().min(1, "Template ID is required"),
|
|
941
941
|
to: z.array(z.string()).optional().describe("Override recipients"),
|
|
942
942
|
cc: z.array(z.string()).optional().describe("Override CC recipients"),
|
|
@@ -953,7 +953,7 @@ server.tool("use-template", {
|
|
|
953
953
|
// Diagnostics Tools
|
|
954
954
|
// =============================================================================
|
|
955
955
|
// --- health-check ---
|
|
956
|
-
server.tool("health-check", {}, withErrorHandling(() => {
|
|
956
|
+
server.tool("health-check", "Use when: doing a quick check that Mail.app is reachable and the server's basic checks pass.\nReturns: an overall healthy/unhealthy status with a pass/fail line per check.\nDo not use when: you need detailed permission/account/IMAP/SMTP diagnostics with remediation steps (use doctor).", {}, withErrorHandling(() => {
|
|
957
957
|
const result = mailManager.healthCheck();
|
|
958
958
|
const statusIcon = result.healthy ? "✓" : "✗";
|
|
959
959
|
const statusText = result.healthy ? "All checks passed" : "Issues detected";
|
|
@@ -966,14 +966,14 @@ server.tool("health-check", {}, withErrorHandling(() => {
|
|
|
966
966
|
return successResponse(`${statusIcon} ${statusText}\n\n${checkLines}`);
|
|
967
967
|
}, "Error running health check"));
|
|
968
968
|
// --- doctor ---
|
|
969
|
-
server.tool("doctor", {}, withErrorHandling(async () => {
|
|
969
|
+
server.tool("doctor", "Use when: troubleshooting setup problems — diagnoses Mail.app automation permissions, account state, and the IMAP/SMTP backends with actionable remediation messages.\nReturns: a detailed diagnostic report (formatted text plus structured checks).\nDo not use when: you just want a quick up/down status (use health-check) or message counts (use get-mail-stats).", {}, withErrorHandling(async () => {
|
|
970
970
|
// Diagnoses Mail.app permission, account state, and the IMAP/SMTP backends
|
|
971
971
|
// with actionable messages (C3). structuredContent carries the raw checks.
|
|
972
972
|
const report = await runDoctor(mailManager);
|
|
973
973
|
return successResponse(formatDoctorReport(report), { ...report });
|
|
974
974
|
}, "Error running doctor"));
|
|
975
975
|
// --- get-mail-stats ---
|
|
976
|
-
server.tool("get-mail-stats", {
|
|
976
|
+
server.tool("get-mail-stats", "Use when: you want aggregate mailbox statistics — total and unread message counts, recently-received counts (last 24h/7d/30d), and (for the all-accounts path) a per-account breakdown.\nReturns: totals, unread counts, recent-activity counts, and per-account figures.\nDo not use when: you only need a single unread number (use get-unread-count) or want to list the messages themselves (use list-messages / search-messages).", {
|
|
977
977
|
account: z
|
|
978
978
|
.string()
|
|
979
979
|
.optional()
|
|
@@ -1019,7 +1019,7 @@ server.tool("get-mail-stats", {
|
|
|
1019
1019
|
return successResponse(lines.join("\n"), { ...stats });
|
|
1020
1020
|
}, "Error getting mail statistics"));
|
|
1021
1021
|
// --- get-sync-status ---
|
|
1022
|
-
server.tool("get-sync-status", {}, withErrorHandling(() => {
|
|
1022
|
+
server.tool("get-sync-status", "Use when: checking whether Mail.app is running and actively syncing, e.g. to explain why new mail hasn't appeared yet.\nReturns: whether Mail.app is running and whether sync activity was detected.\nDo not use when: you need message counts (use get-mail-stats) or a full setup diagnosis (use doctor).", {}, withErrorHandling(() => {
|
|
1023
1023
|
const status = mailManager.getSyncStatus();
|
|
1024
1024
|
const lines = [];
|
|
1025
1025
|
lines.push(`🔄 Mail Sync Status`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apple-mail-mcp",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude",
|
|
3
|
+
"version": "2.1.3",
|
|
4
|
+
"description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude and other AI assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
7
7
|
"types": "build/index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"format": "prettier --write src",
|
|
29
29
|
"format:check": "prettier --check src",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
|
-
"version": "node -
|
|
31
|
+
"version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin .agents/plugins codex .hermes-plugin .antigravity-plugin",
|
|
32
32
|
"prepublishOnly": "npm run lint && npm run test && npm run build",
|
|
33
33
|
"prepare": "husky; npm run build"
|
|
34
34
|
},
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"mcp",
|
|
37
37
|
"apple-mail",
|
|
38
38
|
"claude",
|
|
39
|
+
"codex",
|
|
39
40
|
"ai",
|
|
40
41
|
"applescript",
|
|
41
42
|
"macos",
|
|
@@ -82,7 +83,7 @@
|
|
|
82
83
|
"vitest": "^4.1.9"
|
|
83
84
|
},
|
|
84
85
|
"volta": {
|
|
85
|
-
"node": "
|
|
86
|
+
"node": "24.17.0"
|
|
86
87
|
},
|
|
87
88
|
"lint-staged": {
|
|
88
89
|
"src/**/*.ts": [
|