apple-mail-mcp 2.9.1 → 2.10.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.
Files changed (3) hide show
  1. package/README.md +19 -4
  2. package/build/index.js +81 -17
  3. package/package.json +2 -2
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
@@ -731,7 +746,7 @@ Flag or unflag a message. `flag-message` optionally takes a flag **color**; `unf
731
746
  | `id` | string | Yes | Message ID |
732
747
  | `color` | string | No | (`flag-message` only) Flag color: `red`, `orange`, `yellow`, `green`, `blue`, `purple`, `gray` (`grey` accepted). Omit for Mail's default flag. |
733
748
 
734
- **Flag colors** are an Apple Mail feature, applied via AppleScript as the message's `flag index` (0 red, 1 orange, 2 yellow, 3 green, 4 blue, 5 purple, 6 gray) the same property a Mail smart mailbox can match on. For an **IMAP-routed** message id (`imap:…`) the flag is still set, but the color is **not** applied, because IMAP's `\Flagged` flag is colorless. To color a flag, use the message's AppleScript (numeric) id.
749
+ **Flag colors** are an Apple Mail feature the message's `flag index` (0 red, 1 orange, 2 yellow, 3 green, 4 blue, 5 purple, 6 gray), which is the property a Mail smart mailbox can match on. **The color is applied on both routes** (since 2.10.0): AppleScript sets the flag index directly, and for an **IMAP-routed** id (`imap:…`) the color is written as Mail.app's `$MailFlagBit0/1/2` keywords a 3-bit field holding the same palette index. `\Flagged` on its own really is colorless, but those keywords ride alongside it in an ordinary `UID STORE`, so a smart mailbox keyed on flag color matches an IMAP-flagged message too. You do **not** need to resolve to a numeric id just to color a flag.
735
750
 
736
751
  ---
737
752
 
@@ -814,7 +829,7 @@ All batch operations accept an array of message IDs (max 100 per batch) and retu
814
829
  | Parameter | Type | Required | Description |
815
830
  |-----------|------|----------|-------------|
816
831
  | `ids` | string[] | Yes | Message IDs (max 100) |
817
- | `color` | string | No | (`batch-flag-messages` only) Flag color applied to AppleScript (numeric) ids — see [`flag-message`](#flag-message--unflag-message). Any `imap:` ids in the batch are flagged but not colored. |
832
+ | `color` | string | No | (`batch-flag-messages` only) Flag color — see [`flag-message`](#flag-message--unflag-message). Applied on both routes, so a mixed batch of numeric and `imap:` ids all end up colored. |
818
833
 
819
834
  ---
820
835
 
package/build/index.js CHANGED
@@ -78120,9 +78120,13 @@ var AppleMailManager = class {
78120
78120
  /**
78121
78121
  * Resolve a message's numeric Mail.app id from its RFC822 Message-ID (the
78122
78122
  * backend-independent join key). This bridges an `imap:` id to the numeric id
78123
- * required to apply a flag *color* IMAP flags are colorless, so a smart
78124
- * mailbox keyed on flag color can only ever match a message flagged via the
78125
- * AppleScript numeric-id path.
78123
+ * required by the AppleScript-only tools`reply-to-message` and
78124
+ * `forward-message`.
78125
+ *
78126
+ * Note: flag *color* no longer needs this (since 2.10.0). Mail.app stores the
78127
+ * color as the `$MailFlagBit0/1/2` keywords, which ride alongside `\Flagged`
78128
+ * in an ordinary `UID STORE`, so flag-message/batch-flag-messages color an
78129
+ * `imap:` id directly and a smart mailbox keyed on flag color matches it.
78126
78130
  *
78127
78131
  * The Message-ID is matched both bracketless and `<bracketed>` (Mail returns
78128
78132
  * it bracketless; IMAP envelopes carry the brackets). When `accountName` is
@@ -80234,6 +80238,7 @@ function structuredRow(m, account, path) {
80234
80238
  dateReceived: env.date ? new Date(env.date).toISOString() : "",
80235
80239
  isRead: m.flags?.has("\\Seen") ?? false,
80236
80240
  isFlagged: m.flags?.has("\\Flagged") ?? false,
80241
+ flagColorIndex: mailFlagColorIndex(m.flags),
80237
80242
  mailbox: path,
80238
80243
  account,
80239
80244
  hasAttachments: false,
@@ -80594,6 +80599,28 @@ async function imapFetchMessageId(id, deps = {}) {
80594
80599
  return null;
80595
80600
  }
80596
80601
  }
80602
+ var MAIL_FLAG_BITS = ["$MailFlagBit0", "$MailFlagBit1", "$MailFlagBit2"];
80603
+ function mailFlagBitsFor(colorIndex) {
80604
+ const set = [];
80605
+ const clear = [];
80606
+ for (let b = 0; b < MAIL_FLAG_BITS.length; b++) {
80607
+ (colorIndex >> b & 1 ? set : clear).push(MAIL_FLAG_BITS[b]);
80608
+ }
80609
+ return { set, clear };
80610
+ }
80611
+ function mailFlagColorIndex(flags) {
80612
+ if (!flags) return void 0;
80613
+ const have = new Set(flags);
80614
+ let idx = 0;
80615
+ let any = false;
80616
+ for (let b = 0; b < MAIL_FLAG_BITS.length; b++) {
80617
+ if (have.has(MAIL_FLAG_BITS[b])) {
80618
+ idx |= 1 << b;
80619
+ any = true;
80620
+ }
80621
+ }
80622
+ return any ? idx : void 0;
80623
+ }
80597
80624
  function flagOp(id, flag, add, deps) {
80598
80625
  const ref = decodeImapId(id);
80599
80626
  if (!ref) return Promise.resolve({ success: false, error: `Not an IMAP message id: "${id}".` });
@@ -80613,8 +80640,38 @@ function flagOp(id, flag, add, deps) {
80613
80640
  }
80614
80641
  var imapMarkRead = (id, deps = {}) => flagOp(id, "\\Seen", true, deps);
80615
80642
  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);
80643
+ function imapFlagMessage(id, colorIndex, deps = {}) {
80644
+ if (colorIndex === void 0) return flagOp(id, "\\Flagged", true, deps);
80645
+ const ref = decodeImapId(id);
80646
+ if (!ref) return Promise.resolve({ success: false, error: `Not an IMAP message id: "${id}".` });
80647
+ const { set, clear } = mailFlagBitsFor(colorIndex);
80648
+ return withMailbox(ref.path, depsForMessageRef(ref, deps), async (client) => {
80649
+ try {
80650
+ const ok = await client.messageFlagsAdd([ref.uid], ["\\Flagged", ...set], { uid: true });
80651
+ if (!ok)
80652
+ return { success: false, error: `IMAP flag update returned false for UID ${ref.uid}.` };
80653
+ if (clear.length) await client.messageFlagsRemove([ref.uid], clear, { uid: true });
80654
+ return { success: true };
80655
+ } catch (e) {
80656
+ return { success: false, error: `IMAP flag update failed for UID ${ref.uid}: ${errText(e)}` };
80657
+ }
80658
+ });
80659
+ }
80660
+ function imapUnflagMessage(id, deps = {}) {
80661
+ const ref = decodeImapId(id);
80662
+ if (!ref) return Promise.resolve({ success: false, error: `Not an IMAP message id: "${id}".` });
80663
+ return withMailbox(ref.path, depsForMessageRef(ref, deps), async (client) => {
80664
+ try {
80665
+ const ok = await client.messageFlagsRemove([ref.uid], ["\\Flagged", ...MAIL_FLAG_BITS], {
80666
+ uid: true
80667
+ });
80668
+ if (!ok) return { success: false, error: `IMAP unflag returned false for UID ${ref.uid}.` };
80669
+ return { success: true };
80670
+ } catch (e) {
80671
+ return { success: false, error: `IMAP unflag failed for UID ${ref.uid}: ${errText(e)}` };
80672
+ }
80673
+ });
80674
+ }
80618
80675
  async function imapMoveMessageById(id, destMailbox, deps = {}) {
80619
80676
  const ref = decodeImapId(id);
80620
80677
  if (!ref) return { success: false, error: `Not an IMAP message id: "${id}".` };
@@ -80777,11 +80834,17 @@ var imapBatchMarkRead = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids)
80777
80834
  var imapBatchMarkUnread = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
80778
80835
  await c.messageFlagsRemove(uids, ["\\Seen"], { uid: true });
80779
80836
  });
80780
- var imapBatchFlag = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
80781
- await c.messageFlagsAdd(uids, ["\\Flagged"], { uid: true });
80837
+ var imapBatchFlag = (ids, colorIndex, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
80838
+ if (colorIndex === void 0) {
80839
+ await c.messageFlagsAdd(uids, ["\\Flagged"], { uid: true });
80840
+ return;
80841
+ }
80842
+ const { set, clear } = mailFlagBitsFor(colorIndex);
80843
+ await c.messageFlagsAdd(uids, ["\\Flagged", ...set], { uid: true });
80844
+ if (clear.length) await c.messageFlagsRemove(uids, clear, { uid: true });
80782
80845
  });
80783
80846
  var imapBatchUnflag = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
80784
- await c.messageFlagsRemove(uids, ["\\Flagged"], { uid: true });
80847
+ await c.messageFlagsRemove(uids, ["\\Flagged", ...MAIL_FLAG_BITS], { uid: true });
80785
80848
  });
80786
80849
  var imapBatchDelete = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids, path) => {
80787
80850
  await trashUids(c, uids, path);
@@ -81431,7 +81494,7 @@ var FLAG_COLOR_INDEX = {
81431
81494
  grey: 6
81432
81495
  };
81433
81496
  var FLAG_COLOR_SCHEMA = external_exports.enum(["red", "orange", "yellow", "green", "blue", "purple", "gray", "grey"]).optional().describe(
81434
- "Optional flag color (Apple Mail palette: red, orange, yellow, green, blue, purple, gray \u2014 'grey' accepted). Omit for Mail's default flag. Colors are applied via Mail.app (AppleScript); for an IMAP-routed message id the flag is set but the color is not applied (IMAP flags are colorless)."
81497
+ "Optional flag color (Apple Mail palette: red, orange, yellow, green, blue, purple, gray \u2014 'grey' accepted). Omit for Mail's default flag. The color is applied on both routes: AppleScript sets the flag index, and IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads \u2014 so a smart mailbox keyed on flag color matches either way."
81435
81498
  );
81436
81499
  var DATE_FILTER_SCHEMA = external_exports.string().regex(
81437
81500
  /^[a-zA-Z0-9 ,/\-:]+$/,
@@ -82235,7 +82298,7 @@ server.registerTool(
82235
82298
  server.registerTool(
82236
82299
  "flag-message",
82237
82300
  {
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).",
82301
+ 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
82302
  inputSchema: {
82240
82303
  id: MESSAGE_ID_SCHEMA,
82241
82304
  color: FLAG_COLOR_SCHEMA
@@ -82250,16 +82313,17 @@ server.registerTool(
82250
82313
  withErrorHandling(({ id, color }) => {
82251
82314
  const colorIndex = color ? FLAG_COLOR_INDEX[color] : void 0;
82252
82315
  return routeMessage(id, {
82253
- imap: () => imapFlagMessage(id),
82316
+ imap: () => imapFlagMessage(id, colorIndex),
82254
82317
  apple: () => mailManager.flagMessage(id, colorIndex) ? successResponse(color ? `Message flagged (${color})` : "Message flagged", {
82255
82318
  ok: true,
82256
82319
  id,
82257
82320
  ...color ? { color, colorApplied: true } : {}
82258
82321
  }) : errorResponse(`Failed to flag message "${id}"`),
82259
- // IMAP path: the flag is set, but flag colors are a Mail.app-only feature.
82260
- ok: color ? `Message flagged. Note: the "${color}" color was not applied \u2014 this is an IMAP-routed message and IMAP flags are colorless.` : "Message flagged",
82322
+ // IMAP path: imapFlagMessage writes the color as $MailFlagBit0/1/2 keywords,
82323
+ // so the outcome matches the AppleScript route above.
82324
+ ok: color ? `Message flagged (${color})` : "Message flagged",
82261
82325
  fail: `Failed to flag message "${id}"`,
82262
- structured: color ? { ok: true, id, color, colorApplied: false } : { ok: true, id }
82326
+ structured: color ? { ok: true, id, color, colorApplied: true } : { ok: true, id }
82263
82327
  });
82264
82328
  }, "Error flagging message")
82265
82329
  );
@@ -82455,7 +82519,7 @@ server.registerTool(
82455
82519
  server.registerTool(
82456
82520
  "batch-flag-messages",
82457
82521
  {
82458
- description: "Use when: flagging multiple messages (1\u2013100 ids) in one call, optionally with a color (red/orange/yellow/green/blue/purple/gray).\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.\nNote: flag colors are applied via Mail.app (AppleScript); any IMAP-routed ids in the batch are flagged but not colored (IMAP flags are colorless).",
82522
+ description: "Use when: flagging multiple messages (1\u2013100 ids) in one call, optionally with a color (red/orange/yellow/green/blue/purple/gray).\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.\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 \u2014 so a mixed batch of numeric and `imap:` ids all end up colored.",
82459
82523
  inputSchema: {
82460
82524
  ids: BATCH_IDS_SCHEMA,
82461
82525
  color: FLAG_COLOR_SCHEMA
@@ -82467,7 +82531,7 @@ server.registerTool(
82467
82531
  const { success: successCount, fail: failCount } = await hybridBatchCounts(
82468
82532
  ids,
82469
82533
  (n) => mailManager.batchFlagMessages(n, colorIndex),
82470
- (im) => imapBatchFlag(im)
82534
+ (im) => imapBatchFlag(im, colorIndex)
82471
82535
  );
82472
82536
  const structured = { ok: failCount === 0, success: successCount, failed: failCount };
82473
82537
  if (failCount === 0) {
@@ -82510,7 +82574,7 @@ server.registerTool(
82510
82574
  server.registerTool(
82511
82575
  "resolve-message-id",
82512
82576
  {
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.",
82577
+ 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
82578
  inputSchema: {
82515
82579
  ids: BATCH_IDS_SCHEMA
82516
82580
  },
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.1",
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
  }