apple-mail-mcp 2.9.1 → 2.10.0

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
@@ -78,9 +78,24 @@ The Codex package registers the same `apple-mail` MCP server through `npx -y app
78
78
 
79
79
  ### Other Hosts (Hermes, Antigravity)
80
80
 
81
- Configuration for two more hosts is included — each registers the same `apple-mail` MCP server (`npx -y apple-mail-mcp`):
81
+ Two more hosts can run the same `apple-mail` MCP server (`npx -y apple-mail-mcp`):
82
82
 
83
- - **[Hermes Agent](https://hermes-agent.nousresearch.com/)** (NousResearch) — Hermes has no plugin/marketplace drop-in. Add the server with `hermes mcp add apple-mail --command npx --args -y apple-mail-mcp`, or merge [`.hermes-plugin/config.yaml`](https://github.com/sweetrb/apple-mail-mcp/blob/main/.hermes-plugin/config.yaml) into `~/.hermes/config.yaml`. Details: [`.hermes-plugin/README.md`](https://github.com/sweetrb/apple-mail-mcp/blob/main/.hermes-plugin/README.md).
83
+ - **[Hermes Agent](https://hermes-agent.nousresearch.com/)** (NousResearch) — Hermes has no plugin/marketplace drop-in, so there is nothing in this repo to install from. Register the server with the CLI:
84
+
85
+ ```bash
86
+ hermes mcp add apple-mail --command npx --args -y apple-mail-mcp
87
+ ```
88
+
89
+ Or add it to `~/.hermes/config.yaml` by hand:
90
+
91
+ ```yaml
92
+ mcp_servers:
93
+ apple-mail:
94
+ command: npx
95
+ args: ["-y", "apple-mail-mcp"]
96
+ ```
97
+
98
+ Restart your Hermes session afterward so the tools load.
84
99
  - **[Antigravity](https://antigravity.google/)** (Google) — add the server entry from [`.antigravity-plugin/mcp_config.json`](https://github.com/sweetrb/apple-mail-mcp/blob/main/.antigravity-plugin/mcp_config.json) to `~/.gemini/config/mcp_config.json` (or via Antigravity's MCP settings).
85
100
 
86
101
  ### Manual Installation
package/build/index.js CHANGED
@@ -80234,6 +80234,7 @@ function structuredRow(m, account, path) {
80234
80234
  dateReceived: env.date ? new Date(env.date).toISOString() : "",
80235
80235
  isRead: m.flags?.has("\\Seen") ?? false,
80236
80236
  isFlagged: m.flags?.has("\\Flagged") ?? false,
80237
+ flagColorIndex: mailFlagColorIndex(m.flags),
80237
80238
  mailbox: path,
80238
80239
  account,
80239
80240
  hasAttachments: false,
@@ -80594,6 +80595,28 @@ async function imapFetchMessageId(id, deps = {}) {
80594
80595
  return null;
80595
80596
  }
80596
80597
  }
80598
+ var MAIL_FLAG_BITS = ["$MailFlagBit0", "$MailFlagBit1", "$MailFlagBit2"];
80599
+ function mailFlagBitsFor(colorIndex) {
80600
+ const set = [];
80601
+ const clear = [];
80602
+ for (let b = 0; b < MAIL_FLAG_BITS.length; b++) {
80603
+ (colorIndex >> b & 1 ? set : clear).push(MAIL_FLAG_BITS[b]);
80604
+ }
80605
+ return { set, clear };
80606
+ }
80607
+ function mailFlagColorIndex(flags) {
80608
+ if (!flags) return void 0;
80609
+ const have = new Set(flags);
80610
+ let idx = 0;
80611
+ let any = false;
80612
+ for (let b = 0; b < MAIL_FLAG_BITS.length; b++) {
80613
+ if (have.has(MAIL_FLAG_BITS[b])) {
80614
+ idx |= 1 << b;
80615
+ any = true;
80616
+ }
80617
+ }
80618
+ return any ? idx : void 0;
80619
+ }
80597
80620
  function flagOp(id, flag, add, deps) {
80598
80621
  const ref = decodeImapId(id);
80599
80622
  if (!ref) return Promise.resolve({ success: false, error: `Not an IMAP message id: "${id}".` });
@@ -80613,8 +80636,38 @@ function flagOp(id, flag, add, deps) {
80613
80636
  }
80614
80637
  var imapMarkRead = (id, deps = {}) => flagOp(id, "\\Seen", true, deps);
80615
80638
  var imapMarkUnread = (id, deps = {}) => flagOp(id, "\\Seen", false, deps);
80616
- var imapFlagMessage = (id, deps = {}) => flagOp(id, "\\Flagged", true, deps);
80617
- var imapUnflagMessage = (id, deps = {}) => flagOp(id, "\\Flagged", false, deps);
80639
+ function imapFlagMessage(id, colorIndex, deps = {}) {
80640
+ if (colorIndex === void 0) return flagOp(id, "\\Flagged", true, deps);
80641
+ const ref = decodeImapId(id);
80642
+ if (!ref) return Promise.resolve({ success: false, error: `Not an IMAP message id: "${id}".` });
80643
+ const { set, clear } = mailFlagBitsFor(colorIndex);
80644
+ return withMailbox(ref.path, depsForMessageRef(ref, deps), async (client) => {
80645
+ try {
80646
+ const ok = await client.messageFlagsAdd([ref.uid], ["\\Flagged", ...set], { uid: true });
80647
+ if (!ok)
80648
+ return { success: false, error: `IMAP flag update returned false for UID ${ref.uid}.` };
80649
+ if (clear.length) await client.messageFlagsRemove([ref.uid], clear, { uid: true });
80650
+ return { success: true };
80651
+ } catch (e) {
80652
+ return { success: false, error: `IMAP flag update failed for UID ${ref.uid}: ${errText(e)}` };
80653
+ }
80654
+ });
80655
+ }
80656
+ function imapUnflagMessage(id, deps = {}) {
80657
+ const ref = decodeImapId(id);
80658
+ if (!ref) return Promise.resolve({ success: false, error: `Not an IMAP message id: "${id}".` });
80659
+ return withMailbox(ref.path, depsForMessageRef(ref, deps), async (client) => {
80660
+ try {
80661
+ const ok = await client.messageFlagsRemove([ref.uid], ["\\Flagged", ...MAIL_FLAG_BITS], {
80662
+ uid: true
80663
+ });
80664
+ if (!ok) return { success: false, error: `IMAP unflag returned false for UID ${ref.uid}.` };
80665
+ return { success: true };
80666
+ } catch (e) {
80667
+ return { success: false, error: `IMAP unflag failed for UID ${ref.uid}: ${errText(e)}` };
80668
+ }
80669
+ });
80670
+ }
80618
80671
  async function imapMoveMessageById(id, destMailbox, deps = {}) {
80619
80672
  const ref = decodeImapId(id);
80620
80673
  if (!ref) return { success: false, error: `Not an IMAP message id: "${id}".` };
@@ -80777,11 +80830,17 @@ var imapBatchMarkRead = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids)
80777
80830
  var imapBatchMarkUnread = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
80778
80831
  await c.messageFlagsRemove(uids, ["\\Seen"], { uid: true });
80779
80832
  });
80780
- var imapBatchFlag = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
80781
- await c.messageFlagsAdd(uids, ["\\Flagged"], { uid: true });
80833
+ var imapBatchFlag = (ids, colorIndex, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
80834
+ if (colorIndex === void 0) {
80835
+ await c.messageFlagsAdd(uids, ["\\Flagged"], { uid: true });
80836
+ return;
80837
+ }
80838
+ const { set, clear } = mailFlagBitsFor(colorIndex);
80839
+ await c.messageFlagsAdd(uids, ["\\Flagged", ...set], { uid: true });
80840
+ if (clear.length) await c.messageFlagsRemove(uids, clear, { uid: true });
80782
80841
  });
80783
80842
  var imapBatchUnflag = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
80784
- await c.messageFlagsRemove(uids, ["\\Flagged"], { uid: true });
80843
+ await c.messageFlagsRemove(uids, ["\\Flagged", ...MAIL_FLAG_BITS], { uid: true });
80785
80844
  });
80786
80845
  var imapBatchDelete = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids, path) => {
80787
80846
  await trashUids(c, uids, path);
@@ -82235,7 +82294,7 @@ server.registerTool(
82235
82294
  server.registerTool(
82236
82295
  "flag-message",
82237
82296
  {
82238
- description: "Use when: flagging a single message (by id), optionally with a color (red/orange/yellow/green/blue/purple/gray).\nReturns: a confirmation that the message was flagged (and the color, when applied).\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.\nNote: flag colors are a Mail.app feature applied via AppleScript; for an IMAP-routed id the flag is set but the color is not applied (IMAP flags are colorless).",
82297
+ description: "Use when: flagging a single message (by id), optionally with a color (red/orange/yellow/green/blue/purple/gray).\nReturns: a confirmation that the message was flagged (and the color, when applied).\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.\nNote: the color is applied on both routes \u2014 AppleScript sets the flag index, IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads.",
82239
82298
  inputSchema: {
82240
82299
  id: MESSAGE_ID_SCHEMA,
82241
82300
  color: FLAG_COLOR_SCHEMA
@@ -82250,7 +82309,7 @@ server.registerTool(
82250
82309
  withErrorHandling(({ id, color }) => {
82251
82310
  const colorIndex = color ? FLAG_COLOR_INDEX[color] : void 0;
82252
82311
  return routeMessage(id, {
82253
- imap: () => imapFlagMessage(id),
82312
+ imap: () => imapFlagMessage(id, colorIndex),
82254
82313
  apple: () => mailManager.flagMessage(id, colorIndex) ? successResponse(color ? `Message flagged (${color})` : "Message flagged", {
82255
82314
  ok: true,
82256
82315
  id,
@@ -82467,7 +82526,7 @@ server.registerTool(
82467
82526
  const { success: successCount, fail: failCount } = await hybridBatchCounts(
82468
82527
  ids,
82469
82528
  (n) => mailManager.batchFlagMessages(n, colorIndex),
82470
- (im) => imapBatchFlag(im)
82529
+ (im) => imapBatchFlag(im, colorIndex)
82471
82530
  );
82472
82531
  const structured = { ok: failCount === 0, success: successCount, failed: failCount };
82473
82532
  if (failCount === 0) {
@@ -82510,7 +82569,7 @@ server.registerTool(
82510
82569
  server.registerTool(
82511
82570
  "resolve-message-id",
82512
82571
  {
82513
- description: "Use when: you have `imap:` message id(s) and need the numeric Mail.app id(s) \u2014 most importantly to apply a flag COLOR, which only sticks on the AppleScript numeric-id path (IMAP `\\Flagged` is colorless, so a smart mailbox keyed on flag color never matches an IMAP-flagged message). Each imap: id is resolved via its RFC822 Message-ID.\nReturns: for each input id, its `numericId` (the AppleScript id) or null when it can't be resolved, plus the `messageId` used; and a `resolvedCount`.\nDo not use when: your ids are already numeric (they pass straight through), or you don't need a color \u2014 flag/move/mark tools operate on `imap:` ids directly.",
82572
+ description: "Use when: you have `imap:` message id(s) and genuinely need the numeric Mail.app id(s) \u2014 e.g. for reply-to-message/forward-message, which are numeric-id only. NOTE: as of 2.10.0 you no longer need this to apply a flag COLOR \u2014 flag-message/batch-flag-messages write the color over IMAP directly via Mail.app's $MailFlagBit0/1/2 keywords, so a smart mailbox keyed on flag color matches an IMAP-flagged message. Each imap: id is resolved via its RFC822 Message-ID.\nReturns: for each input id, its `numericId` (the AppleScript id) or null when it can't be resolved, plus the `messageId` used; and a `resolvedCount`.\nDo not use when: your ids are already numeric (they pass straight through), or you don't need a color \u2014 flag/move/mark tools operate on `imap:` ids directly.",
82514
82573
  inputSchema: {
82515
82574
  ids: BATCH_IDS_SCHEMA
82516
82575
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.9.1",
3
+ "version": "2.10.0",
4
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",
@@ -91,6 +91,6 @@
91
91
  "format:check": "prettier --check src",
92
92
  "typecheck": "tsc --noEmit",
93
93
  "sync:skills": "node scripts/sync-skills.mjs",
94
- "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin .agents/plugins codex .hermes-plugin .antigravity-plugin"
94
+ "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin .agents/plugins codex .antigravity-plugin"
95
95
  }
96
96
  }