apple-mail-mcp 1.8.0 → 1.9.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
@@ -270,24 +270,31 @@ Then send:
270
270
 
271
271
  The default `applescript` transport is unchanged; SMTP is opt-in per call.
272
272
 
273
- ##### IMAP backend (read/search) — opt-in, Phase 1
273
+ ##### IMAP backend — opt-in
274
274
 
275
275
  AppleScript runs `search`/`list` predicates client-side over the Apple Event
276
276
  bridge, which is slow and can time out (false-empty) on large Gmail/IMAP
277
- mailboxes (see [#24](https://github.com/sweetrb/apple-mail-mcp/issues/24)). When
278
- an account is configured for IMAP, `search-messages` and `list-messages` instead
279
- run a **server-side IMAP search** ([#43](https://github.com/sweetrb/apple-mail-mcp/issues/43))
280
- typically sub-second and correct on the same mailbox where AppleScript times out.
281
- This is **opt-in and additive**: any account without IMAP configured behaves
282
- exactly as before (AppleScript). When an account is IMAP-configured,
283
- `search-messages`/`list-messages` (read) and `create-mailbox`/`rename-mailbox`/
284
- `delete-mailbox` (folder ops) route to IMAP. The folder ops are the key win for
285
- server accounts: IMAP's `CREATE`/`RENAME`/`DELETE` succeed on exactly the
286
- iCloud/Gmail/Workspace/Exchange mailboxes where Mail.app's AppleScript bridge
287
- can't (#42). `get-message` and message-level mutations (mark/flag/move/delete-
288
- message) stay on AppleScript for nowthey key off a message id, and the IMAP
289
- read rows report **UIDs** (a different, per-mailbox namespace), so routing them
290
- safely needs a UID-aware design (tracked on #43).
277
+ mailboxes (see [#24](https://github.com/sweetrb/apple-mail-mcp/issues/24)), and
278
+ its `delete`/`rename mailbox` and draft handlers don't work on server-side
279
+ accounts at all (#42). When an account is configured for IMAP, the MCP routes to
280
+ a server-side IMAP backend ([#43](https://github.com/sweetrb/apple-mail-mcp/issues/43))
281
+ that is fast and correct on exactly those mailboxes. This is **opt-in and
282
+ additive**: any account without IMAP configured behaves exactly as before
283
+ (AppleScript).
284
+
285
+ What routes to IMAP when an account is IMAP-configured:
286
+
287
+ - **Read:** `search-messages`, `list-messages` (server-side `SEARCH`, typically sub-second), and `get-message`.
288
+ - **Folder ops:** `create-mailbox`, `rename-mailbox`, `delete-mailbox`IMAP's `CREATE`/`RENAME`/`DELETE` succeed on the iCloud/Gmail/Workspace/Exchange mailboxes Mail.app's AppleScript bridge can't touch (#42).
289
+ - **Message mutations:** `mark-as-read`/`unread`, `flag-message`/`unflag-message`, `move-message`, `delete-message`.
290
+
291
+ **Message ids are backend-tagged.** The IMAP read path emits self-describing ids
292
+ of the form `imap:<token>` (the token encodes the account, mailbox path, and
293
+ UID). Pass that id back to `get-message` or any message mutation and it routes to
294
+ IMAP automatically; bare numeric ids continue to use AppleScript. So an agent
295
+ never has to know which backend a message came from — the id carries it. (Batch
296
+ operations remain AppleScript-only and accept numeric ids; use the single-message
297
+ tools for IMAP ids.)
291
298
 
292
299
  Routing is conservative: only a call whose explicit `account` matches the
293
300
  configured IMAP account goes to IMAP; everything else falls through to
@@ -309,10 +316,8 @@ config. Gmail label semantics: common names (`All Mail`, `Sent`, `Trash`,
309
316
  `Spam`, `Important`, …) map to their `[Gmail]/…` IMAP paths automatically.
310
317
 
311
318
  > Note: each call currently opens its own IMAP connection (no pooling yet), so
312
- > expect a few seconds of connection overhead per call. Phase 2 added the folder
313
- > ops (create/rename/delete-mailbox) — resolving the IMAP slice of
314
- > [#42](https://github.com/sweetrb/apple-mail-mcp/issues/42). IMAP-backed
315
- > message-level mutations are still future work (see #43).
319
+ > expect a few seconds of connection overhead per call the one remaining
320
+ > follow-up on [#43](https://github.com/sweetrb/apple-mail-mcp/issues/43).
316
321
  >
317
322
  > **iCloud:** set `APPLE_MAIL_MCP_IMAP_HOST=imap.mail.me.com`, `APPLE_MAIL_MCP_IMAP_USER`
318
323
  > to your iCloud address, `APPLE_MAIL_MCP_IMAP_ACCOUNT` to the Mail account name
@@ -848,8 +853,8 @@ The entrypoint is written as:
848
853
  | No sending HTML email | Emails are sent as plain text; reading HTML content is supported |
849
854
  | Attachments require absolute paths | File attachments must use full absolute paths (e.g., `/Users/me/file.pdf`) |
850
855
  | No smart mailboxes | Cannot access Smart Mailboxes via AppleScript |
851
- | Very large mailboxes not searchable | Apple Mail's AppleScript bridge times out on mailboxes with tens of thousands of messages, so unscoped `search-messages` skips mailboxes above `APPLE_MAIL_MAX_SEARCH_MAILBOX` (default 5000) and reports them as a partial result. Scope with `mailbox` + a date window to search inside one. ([#24](https://github.com/sweetrb/apple-mail-mcp/issues/24)) |
852
- | Can't delete/rename server-side mailboxes or mutate drafts | Mail.app's AppleScript bridge can only `delete`/`rename` **local "On My Mac"** mailboxes and cannot delete/move drafts — it throws `AppleEvent handler failed` for IMAP/Gmail/Workspace/iCloud/Exchange mailboxes and drafts (the GUI can do it). `delete-mailbox`/`rename-mailbox`/`delete-message`/`move-message` now return a clear "do it in Mail.app directly" error in that case instead of a generic failure. ([#42](https://github.com/sweetrb/apple-mail-mcp/issues/42)) |
856
+ | Very large mailboxes not searchable *via AppleScript* | Apple Mail's AppleScript bridge times out on mailboxes with tens of thousands of messages, so unscoped `search-messages` skips mailboxes above `APPLE_MAIL_MAX_SEARCH_MAILBOX` (default 5000) and reports them as a partial result. Scope with `mailbox` + a date window or configure the [IMAP backend](#imap-backend--opt-in), which searches these server-side in well under a second. ([#24](https://github.com/sweetrb/apple-mail-mcp/issues/24)) |
857
+ | Can't delete/rename server-side mailboxes or mutate drafts *via AppleScript* | Mail.app's AppleScript bridge can only `delete`/`rename` **local "On My Mac"** mailboxes and cannot delete/move drafts — it throws `AppleEvent handler failed` for IMAP/Gmail/Workspace/iCloud/Exchange mailboxes (the GUI can do it). Without IMAP configured, `delete-mailbox`/`rename-mailbox`/`delete-message`/`move-message` return a clear "do it in Mail.app directly" error instead of a generic failure. With the [IMAP backend](#imap-backend--opt-in) configured for the account, these operations run via IMAP and succeed. ([#42](https://github.com/sweetrb/apple-mail-mcp/issues/42)) |
853
858
  | In-memory templates | Email templates are not persisted across server restarts |
854
859
  | Numeric-only message IDs | Message IDs must contain only digits (validated by schema) |
855
860
  | Batch size cap | Batch operations are limited to 100 messages per request |
package/build/index.js CHANGED
@@ -25,17 +25,25 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
25
25
  import { z } from "zod";
26
26
  import { AppleMailManager } from "./services/appleMailManager.js";
27
27
  import { sendViaSmtp } from "./services/smtpMailer.js";
28
- import { isImapAccount, imapSearchMessages, imapListMessages, imapCreateMailbox, imapDeleteMailbox, imapRenameMailbox, } from "./services/imapClient.js";
28
+ import { isImapAccount, imapSearchMessages, imapListMessages, imapCreateMailbox, imapDeleteMailbox, imapRenameMailbox, decodeImapId, imapGetMessage, imapMarkRead, imapMarkUnread, imapFlagMessage, imapUnflagMessage, imapMoveMessageById, imapDeleteMessageById, } from "./services/imapClient.js";
29
29
  import { createSerialGate } from "./utils/serialize.js";
30
30
  // =============================================================================
31
31
  // Shared Validation Schemas
32
32
  // =============================================================================
33
- /** Message IDs in Apple Mail are always numeric. Enforce this at the schema level
34
- * to prevent AppleScript injection via the `whose id is ${id}` interpolation. */
35
- const MESSAGE_ID_SCHEMA = z.string().regex(/^\d+$/, "Message ID must be numeric");
36
- /** Batch operations are capped to prevent unbounded loops / DoS. */
33
+ /** AppleScript message IDs are always numeric. Enforced to prevent AppleScript
34
+ * injection via the `whose id is ${id}` interpolation. */
35
+ const NUMERIC_MESSAGE_ID_SCHEMA = z.string().regex(/^\d+$/, "Message ID must be numeric");
36
+ /** A single-message id is EITHER an AppleScript numeric id OR an IMAP composite
37
+ * token (`imap:<base64url>`, emitted by the IMAP read path). The IMAP form is
38
+ * base64url so it stays injection-safe; it never reaches AppleScript (it's
39
+ * decoded and routed to IMAP instead). */
40
+ const MESSAGE_ID_SCHEMA = z
41
+ .string()
42
+ .regex(/^(\d+|imap:[A-Za-z0-9_-]+)$/, "Message ID must be numeric or an IMAP id (imap:…)");
43
+ /** Batch operations are AppleScript-only and capped to prevent unbounded loops /
44
+ * DoS. IMAP ids are rejected here — use the single-message tools for those. */
37
45
  const BATCH_IDS_SCHEMA = z
38
- .array(MESSAGE_ID_SCHEMA)
46
+ .array(NUMERIC_MESSAGE_ID_SCHEMA)
39
47
  .min(1, "At least one message ID is required")
40
48
  .max(100, "Cannot process more than 100 messages in a single batch");
41
49
  /** Date filter strings must look like natural-language dates (e.g. "March 1, 2026").
@@ -195,7 +203,14 @@ server.tool("get-message", {
195
203
  .boolean()
196
204
  .optional()
197
205
  .describe("Return the HTML body (extracted from the message source) instead of plain text"),
198
- }, withErrorHandling(({ id, preferHtml }) => {
206
+ }, withErrorHandling(async ({ id, preferHtml }) => {
207
+ // IMAP id (imap:…) → fetch via IMAP (#43 Phase 3); else AppleScript.
208
+ if (decodeImapId(id)) {
209
+ const r = await imapGetMessage(id, preferHtml === true);
210
+ return r.success
211
+ ? successResponse(r.info ?? "")
212
+ : errorResponse(r.error ?? `Message with ID "${id}" not found`);
213
+ }
199
214
  // Only fetch/parse the raw source when HTML is actually requested (#32).
200
215
  const content = mailManager.getMessageContent(id, preferHtml === true);
201
216
  if (!content) {
@@ -364,7 +379,13 @@ server.tool("forward-message", {
364
379
  // --- mark-as-read ---
365
380
  server.tool("mark-as-read", {
366
381
  id: MESSAGE_ID_SCHEMA,
367
- }, withErrorHandling(({ id }) => {
382
+ }, withErrorHandling(async ({ id }) => {
383
+ if (decodeImapId(id)) {
384
+ const r = await imapMarkRead(id);
385
+ return r.success
386
+ ? successResponse("Message marked as read")
387
+ : errorResponse(r.error ?? `Failed to mark message "${id}" as read`);
388
+ }
368
389
  const success = mailManager.markAsRead(id);
369
390
  if (!success) {
370
391
  return errorResponse(`Failed to mark message "${id}" as read`);
@@ -374,7 +395,13 @@ server.tool("mark-as-read", {
374
395
  // --- mark-as-unread ---
375
396
  server.tool("mark-as-unread", {
376
397
  id: MESSAGE_ID_SCHEMA,
377
- }, withErrorHandling(({ id }) => {
398
+ }, withErrorHandling(async ({ id }) => {
399
+ if (decodeImapId(id)) {
400
+ const r = await imapMarkUnread(id);
401
+ return r.success
402
+ ? successResponse("Message marked as unread")
403
+ : errorResponse(r.error ?? `Failed to mark message "${id}" as unread`);
404
+ }
378
405
  const success = mailManager.markAsUnread(id);
379
406
  if (!success) {
380
407
  return errorResponse(`Failed to mark message "${id}" as unread`);
@@ -384,7 +411,13 @@ server.tool("mark-as-unread", {
384
411
  // --- flag-message ---
385
412
  server.tool("flag-message", {
386
413
  id: MESSAGE_ID_SCHEMA,
387
- }, withErrorHandling(({ id }) => {
414
+ }, withErrorHandling(async ({ id }) => {
415
+ if (decodeImapId(id)) {
416
+ const r = await imapFlagMessage(id);
417
+ return r.success
418
+ ? successResponse("Message flagged")
419
+ : errorResponse(r.error ?? `Failed to flag message "${id}"`);
420
+ }
388
421
  const success = mailManager.flagMessage(id);
389
422
  if (!success) {
390
423
  return errorResponse(`Failed to flag message "${id}"`);
@@ -394,7 +427,13 @@ server.tool("flag-message", {
394
427
  // --- unflag-message ---
395
428
  server.tool("unflag-message", {
396
429
  id: MESSAGE_ID_SCHEMA,
397
- }, withErrorHandling(({ id }) => {
430
+ }, withErrorHandling(async ({ id }) => {
431
+ if (decodeImapId(id)) {
432
+ const r = await imapUnflagMessage(id);
433
+ return r.success
434
+ ? successResponse("Message unflagged")
435
+ : errorResponse(r.error ?? `Failed to unflag message "${id}"`);
436
+ }
398
437
  const success = mailManager.unflagMessage(id);
399
438
  if (!success) {
400
439
  return errorResponse(`Failed to unflag message "${id}"`);
@@ -404,7 +443,13 @@ server.tool("unflag-message", {
404
443
  // --- delete-message ---
405
444
  server.tool("delete-message", {
406
445
  id: MESSAGE_ID_SCHEMA,
407
- }, withErrorHandling(({ id }) => {
446
+ }, withErrorHandling(async ({ id }) => {
447
+ if (decodeImapId(id)) {
448
+ const r = await imapDeleteMessageById(id);
449
+ return r.success
450
+ ? successResponse("Message deleted")
451
+ : errorResponse(r.error ?? `Failed to delete message "${id}"`);
452
+ }
408
453
  const { success, error } = mailManager.deleteMessage(id);
409
454
  if (!success) {
410
455
  return errorResponse(error || `Failed to delete message "${id}"`);
@@ -416,7 +461,13 @@ server.tool("move-message", {
416
461
  id: MESSAGE_ID_SCHEMA,
417
462
  mailbox: z.string().min(1, "Destination mailbox is required"),
418
463
  account: z.string().optional().describe("Account containing the destination mailbox"),
419
- }, withErrorHandling(({ id, mailbox, account }) => {
464
+ }, withErrorHandling(async ({ id, mailbox, account }) => {
465
+ if (decodeImapId(id)) {
466
+ const r = await imapMoveMessageById(id, mailbox);
467
+ return r.success
468
+ ? successResponse(`Message moved to "${mailbox}"`)
469
+ : errorResponse(r.error ?? `Failed to move message to "${mailbox}"`);
470
+ }
420
471
  const { success, error } = mailManager.moveMessage(id, mailbox, account);
421
472
  if (!success) {
422
473
  return errorResponse(error || `Failed to move message to "${mailbox}"`);
@@ -54,6 +54,25 @@ export declare function isPathWithinAllowedRoots(resolvedPath: string): boolean;
54
54
  * Exported for unit testing.
55
55
  */
56
56
  export declare function describeMailboxOpError(op: "delete" | "rename", raw: string): string;
57
+ /** Env var to pin the default account (matched by account name or email). */
58
+ export declare const DEFAULT_ACCOUNT_ENV = "APPLE_MAIL_MCP_DEFAULT_ACCOUNT";
59
+ /**
60
+ * Choose the account to use when a tool call omits `account`.
61
+ *
62
+ * Priority: explicit `override` (by name or email) → Mail's default-send
63
+ * account *if enabled* → first enabled account → first account → null. The key
64
+ * guarantee (issue #47): a **disabled** account is never chosen implicitly — it
65
+ * can only be selected via an explicit override (deliberate user intent) or as
66
+ * a last resort when no account is enabled. This prevents operations silently
67
+ * landing in a configured-but-disabled account (e.g. an unused iCloud account
68
+ * that's still addressable via AppleScript).
69
+ *
70
+ * Pure/exported for unit testing.
71
+ */
72
+ export declare function chooseDefaultAccount(accounts: Account[], opts?: {
73
+ override?: string;
74
+ defaultSendEmail?: string;
75
+ }): string | null;
57
76
  export declare function escapeForAppleScript(text: string): string;
58
77
  /**
59
78
  * Emits AppleScript that builds a date into the variable `varName` from numeric
@@ -135,9 +154,11 @@ export declare class AppleMailManager {
135
154
  */
136
155
  private invalidateCache;
137
156
  /**
138
- * Resolves the account to use for an operation.
139
- * Queries Mail.app's configured default send account, then falls back
140
- * to the first available account.
157
+ * Resolves the account to use for an operation when the caller omits one.
158
+ *
159
+ * Order (see chooseDefaultAccount): the APPLE_MAIL_MCP_DEFAULT_ACCOUNT env
160
+ * override → Mail.app's configured default-send account (if enabled) → the
161
+ * first enabled account. A disabled account is never chosen implicitly (#47).
141
162
  */
142
163
  private resolveAccount;
143
164
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
@@ -181,6 +181,41 @@ export function describeMailboxOpError(op, raw) {
181
181
  }
182
182
  return trimmed || `Failed to ${op} mailbox`;
183
183
  }
184
+ /** Env var to pin the default account (matched by account name or email). */
185
+ export const DEFAULT_ACCOUNT_ENV = "APPLE_MAIL_MCP_DEFAULT_ACCOUNT";
186
+ /**
187
+ * Choose the account to use when a tool call omits `account`.
188
+ *
189
+ * Priority: explicit `override` (by name or email) → Mail's default-send
190
+ * account *if enabled* → first enabled account → first account → null. The key
191
+ * guarantee (issue #47): a **disabled** account is never chosen implicitly — it
192
+ * can only be selected via an explicit override (deliberate user intent) or as
193
+ * a last resort when no account is enabled. This prevents operations silently
194
+ * landing in a configured-but-disabled account (e.g. an unused iCloud account
195
+ * that's still addressable via AppleScript).
196
+ *
197
+ * Pure/exported for unit testing.
198
+ */
199
+ export function chooseDefaultAccount(accounts, opts = {}) {
200
+ const norm = (s) => s.trim().toLowerCase();
201
+ const override = opts.override?.trim();
202
+ if (override) {
203
+ const o = norm(override);
204
+ const m = accounts.find((a) => norm(a.name) === o || norm(a.email) === o);
205
+ if (m)
206
+ return m.name; // honor an explicit pin even if that account is disabled
207
+ }
208
+ if (opts.defaultSendEmail) {
209
+ const e = norm(opts.defaultSendEmail);
210
+ const m = accounts.find((a) => norm(a.email) === e);
211
+ if (m && m.enabled)
212
+ return m.name;
213
+ }
214
+ const firstEnabled = accounts.find((a) => a.enabled);
215
+ if (firstEnabled)
216
+ return firstEnabled.name;
217
+ return accounts[0]?.name ?? null;
218
+ }
184
219
  export function escapeForAppleScript(text) {
185
220
  if (!text)
186
221
  return "";
@@ -396,16 +431,20 @@ export class AppleMailManager {
396
431
  this.cache.mailboxNames.clear();
397
432
  }
398
433
  /**
399
- * Resolves the account to use for an operation.
400
- * Queries Mail.app's configured default send account, then falls back
401
- * to the first available account.
434
+ * Resolves the account to use for an operation when the caller omits one.
435
+ *
436
+ * Order (see chooseDefaultAccount): the APPLE_MAIL_MCP_DEFAULT_ACCOUNT env
437
+ * override → Mail.app's configured default-send account (if enabled) → the
438
+ * first enabled account. A disabled account is never chosen implicitly (#47).
402
439
  */
403
440
  resolveAccount(account) {
404
441
  if (account)
405
442
  return account;
406
443
  if (this.defaultAccount)
407
444
  return this.defaultAccount;
408
- // Query Mail.app's default send account by inspecting a temporary outgoing message
445
+ const accounts = this.getCachedAccounts();
446
+ // Mail.app's default send account (inspect a throwaway outgoing message).
447
+ let defaultSendEmail;
409
448
  const defaultResult = executeAppleScript(buildAppLevelScript(`
410
449
  set newMsg to make new outgoing message
411
450
  set fromAddr to sender of newMsg
@@ -413,24 +452,22 @@ export class AppleMailManager {
413
452
  return fromAddr
414
453
  `));
415
454
  if (defaultResult.success && defaultResult.output.trim()) {
416
- // sender returns "Name <email>" — match to account by email address
455
+ // sender returns "Name <email>" — pull out the address
417
456
  const senderOutput = defaultResult.output.trim();
418
457
  const emailMatch = senderOutput.match(/<([^>]+)>/);
419
- const defaultEmail = emailMatch ? emailMatch[1] : senderOutput;
420
- const accounts = this.getCachedAccounts();
421
- const matchedAccount = accounts.find((a) => a.email.toLowerCase() === defaultEmail.toLowerCase());
422
- if (matchedAccount) {
423
- this.defaultAccount = matchedAccount.name;
424
- return this.defaultAccount;
425
- }
458
+ defaultSendEmail = emailMatch ? emailMatch[1] : senderOutput;
426
459
  }
427
- // Fall back to first available account
428
- const accounts = this.getCachedAccounts();
429
- if (accounts.length > 0) {
430
- this.defaultAccount = accounts[0].name;
431
- return this.defaultAccount;
460
+ const chosen = chooseDefaultAccount(accounts, {
461
+ override: process.env[DEFAULT_ACCOUNT_ENV],
462
+ defaultSendEmail,
463
+ });
464
+ if (chosen) {
465
+ this.defaultAccount = chosen;
466
+ return chosen;
432
467
  }
433
- return "iCloud"; // Last resort fallback
468
+ // No accounts at all — return something rather than throw; downstream
469
+ // AppleScript will surface a clear "account not found".
470
+ return accounts[0]?.name ?? "iCloud";
434
471
  }
435
472
  /**
436
473
  * Resolves a mailbox name to its actual name in the account.
@@ -42,6 +42,7 @@ interface ImapMessage {
42
42
  uid: number;
43
43
  envelope?: ImapEnvelope;
44
44
  flags?: Set<string>;
45
+ source?: Buffer | string;
45
46
  }
46
47
  interface MailboxLock {
47
48
  release: () => void;
@@ -50,6 +51,9 @@ interface ImapMailboxListing {
50
51
  path: string;
51
52
  name: string;
52
53
  }
54
+ type FlagOpts = {
55
+ uid: boolean;
56
+ };
53
57
  export interface ImapClientLike {
54
58
  connect(): Promise<void>;
55
59
  getMailboxLock(path: string): Promise<MailboxLock>;
@@ -59,6 +63,9 @@ export interface ImapClientLike {
59
63
  fetch(range: string, query: Record<string, unknown>, opts: {
60
64
  uid: true;
61
65
  }): AsyncIterable<ImapMessage>;
66
+ fetchOne(range: string, query: Record<string, unknown>, opts: {
67
+ uid: true;
68
+ }): Promise<ImapMessage | false>;
62
69
  list(): Promise<ImapMailboxListing[]>;
63
70
  mailboxCreate(path: string): Promise<{
64
71
  path: string;
@@ -71,8 +78,18 @@ export interface ImapClientLike {
71
78
  mailboxDelete(path: string): Promise<{
72
79
  path: string;
73
80
  }>;
81
+ messageFlagsAdd(range: number[], flags: string[], opts: FlagOpts): Promise<boolean>;
82
+ messageFlagsRemove(range: number[], flags: string[], opts: FlagOpts): Promise<boolean>;
83
+ messageMove(range: number[], destination: string, opts: FlagOpts): Promise<unknown>;
84
+ messageDelete(range: number[], opts: FlagOpts): Promise<boolean>;
74
85
  logout(): Promise<void>;
75
86
  }
87
+ export declare function encodeImapId(account: string, path: string, uid: number): string;
88
+ export declare function decodeImapId(id: string): {
89
+ account: string;
90
+ path: string;
91
+ uid: number;
92
+ } | null;
76
93
  export type ImapConnect = (cfg: ImapConfig) => Promise<ImapClientLike>;
77
94
  /** True only when IMAP is configured AND the explicit `account` matches it. */
78
95
  export declare function isImapAccount(account: string | undefined, env?: NodeJS.ProcessEnv): boolean;
@@ -104,5 +121,22 @@ export declare function imapRenameMailbox(oldName: string, newName: string, deps
104
121
  connect?: ImapConnect;
105
122
  config?: ImapConfig;
106
123
  }): Promise<ImapOpResult>;
124
+ /** Read a message by composite IMAP id; returns "Subject: …\n\n<body>". */
125
+ export declare function imapGetMessage(id: string, preferHtml: boolean, deps?: {
126
+ connect?: ImapConnect;
127
+ config?: ImapConfig;
128
+ }): Promise<ImapOpResult>;
129
+ export declare const imapMarkRead: (id: string, deps?: {}) => Promise<ImapOpResult>;
130
+ export declare const imapMarkUnread: (id: string, deps?: {}) => Promise<ImapOpResult>;
131
+ export declare const imapFlagMessage: (id: string, deps?: {}) => Promise<ImapOpResult>;
132
+ export declare const imapUnflagMessage: (id: string, deps?: {}) => Promise<ImapOpResult>;
133
+ export declare function imapMoveMessageById(id: string, destMailbox: string, deps?: {
134
+ connect?: ImapConnect;
135
+ config?: ImapConfig;
136
+ }): Promise<ImapOpResult>;
137
+ export declare function imapDeleteMessageById(id: string, deps?: {
138
+ connect?: ImapConnect;
139
+ config?: ImapConfig;
140
+ }): Promise<ImapOpResult>;
107
141
  export {};
108
142
  //# sourceMappingURL=imapClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"imapClient.d.ts","sourceRoot":"","sources":["../../src/services/imapClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"imapClient.d.ts","sourceRoot":"","sources":["../../src/services/imapClient.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,16 @@
1
1
  /**
2
- * IMAP backend for read/search (issue #43, Phase 1).
2
+ * IMAP backend (issue #43).
3
3
  *
4
4
  * AppleScript-over-Mail.app is the default. When an account is explicitly
5
- * configured for IMAP (env below), `search-messages` and `list-messages` route
6
- * here instead, running a SERVER-SIDE search orders of magnitude faster than
7
- * AppleScript's client-side `whose` enumeration on large Gmail mailboxes, and
8
- * correct (no false-empty on timeout). Read-only; mutations stay on AppleScript.
5
+ * configured for IMAP (env below), operations route here instead:
6
+ * - read: search-messages / list-messages (server-side SEARCH, orders of
7
+ * magnitude faster and correct on large Gmail mailboxes where
8
+ * AppleScript times out with a false-empty) and get-message;
9
+ * - folders: create / rename / delete-mailbox (work on the server hierarchy
10
+ * that AppleScript can't touch — #42);
11
+ * - message: mark/flag/move/delete, keyed by the composite `imap:` id the
12
+ * read path emits (see encodeImapId/decodeImapId below).
13
+ * Everything is opt-in and additive; un-configured accounts use AppleScript.
9
14
  *
10
15
  * Opt-in via env (mirrors the SMTP transport pattern):
11
16
  * APPLE_MAIL_MCP_IMAP_USER (required — enables IMAP; the login address)
@@ -19,6 +24,7 @@
19
24
  */
20
25
  import { ImapFlow } from "imapflow";
21
26
  import { readKeychainPassword } from "../services/smtpMailer.js";
27
+ import { extractHtmlBody, extractTextBody } from "../utils/mimeParse.js";
22
28
  export const IMAP_ENV = {
23
29
  user: "APPLE_MAIL_MCP_IMAP_USER",
24
30
  account: "APPLE_MAIL_MCP_IMAP_ACCOUNT",
@@ -28,6 +34,30 @@ export const IMAP_ENV = {
28
34
  keychainService: "APPLE_MAIL_MCP_IMAP_KEYCHAIN_SERVICE",
29
35
  keychainAccount: "APPLE_MAIL_MCP_IMAP_KEYCHAIN_ACCOUNT",
30
36
  };
37
+ // ---------------------------------------------------------------------------
38
+ // Composite IMAP message id (Phase 3): a self-describing token the IMAP read
39
+ // path emits so the same id round-trips back to get-message and the message
40
+ // mutations. AppleScript message ids are bare numbers; an IMAP id is
41
+ // `imap:<base64url({a:account,p:mailboxPath,u:uid})>`. UIDs are per-mailbox, so
42
+ // the mailbox path must travel with the uid. base64url keeps it schema-safe.
43
+ // ---------------------------------------------------------------------------
44
+ export function encodeImapId(account, path, uid) {
45
+ const payload = Buffer.from(JSON.stringify({ a: account, p: path, u: uid }), "utf8").toString("base64url");
46
+ return `imap:${payload}`;
47
+ }
48
+ export function decodeImapId(id) {
49
+ if (!id || !id.startsWith("imap:"))
50
+ return null;
51
+ try {
52
+ const obj = JSON.parse(Buffer.from(id.slice("imap:".length), "base64url").toString("utf8"));
53
+ if (typeof obj.u !== "number" || typeof obj.p !== "string")
54
+ return null;
55
+ return { account: String(obj.a ?? ""), path: obj.p, uid: obj.u };
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
31
61
  /** True only when IMAP is configured AND the explicit `account` matches it. */
32
62
  export function isImapAccount(account, env = process.env) {
33
63
  const user = env[IMAP_ENV.user]?.trim();
@@ -119,7 +149,7 @@ function buildCriteria(a, listMode) {
119
149
  c.all = true;
120
150
  return c;
121
151
  }
122
- function formatRow(m) {
152
+ function formatRow(m, account, path) {
123
153
  const env = m.envelope ?? {};
124
154
  const subject = env.subject || "(no subject)";
125
155
  const a = env.from?.[0];
@@ -130,7 +160,9 @@ function formatRow(m) {
130
160
  : "(unknown)";
131
161
  const date = env.date ? new Date(env.date).toLocaleDateString() : "";
132
162
  const read = m.flags?.has("\\Seen") ? "read" : "unread";
133
- return ` - UID: ${m.uid} | ${date} | ${subject} (from: ${from}) [${read}]`;
163
+ // Emit the self-describing IMAP id so get-message and the message mutations
164
+ // can route this row back to IMAP (Phase 3).
165
+ return ` - ID: ${encodeImapId(account, path, m.uid)} | ${date} | ${subject} (from: ${from}) [${read}]`;
134
166
  }
135
167
  async function run(args, listMode, deps) {
136
168
  const cfg = deps.config ?? resolveImapConfig();
@@ -153,13 +185,13 @@ async function run(args, listMode, deps) {
153
185
  .slice(offset, offset + limit);
154
186
  const byUid = new Map();
155
187
  for await (const msg of client.fetch(newest.join(","), { envelope: true, flags: true }, { uid: true })) {
156
- byUid.set(msg.uid, formatRow(msg));
188
+ byUid.set(msg.uid, formatRow(msg, cfg.accountLabel, path));
157
189
  }
158
190
  const rows = newest.map((u) => byUid.get(u)).filter((r) => Boolean(r));
159
191
  const verb = listMode ? "listed" : "matched";
160
192
  return (`Found ${rows.length} message(s) via IMAP (server-side, account ${cfg.accountLabel}, mailbox "${path}"; ${uids.length} total ${verb}):\n` +
161
193
  rows.join("\n") +
162
- `\n\nNote: IDs are IMAP UIDs. get-message and message mutations still use the AppleScript path (Phase 1 is read/search only).`);
194
+ `\n\nNote: these IMAP IDs (imap:…) work with get-message and the message mutations (mark/flag/move/delete-message), which route back to IMAP.`);
163
195
  }
164
196
  finally {
165
197
  lock.release();
@@ -258,3 +290,100 @@ export function imapRenameMailbox(oldName, newName, deps = {}) {
258
290
  }
259
291
  });
260
292
  }
293
+ // ===========================================================================
294
+ // Phase 3 — message-level operations by composite IMAP id (issue #43)
295
+ //
296
+ // get-message / mark / flag / move / delete-message route here when the message
297
+ // id is an `imap:` token (emitted by the IMAP read path). The token carries the
298
+ // mailbox path + UID, so the op opens that mailbox and acts on the UID.
299
+ // ===========================================================================
300
+ /** Connect, open the message's mailbox, run `fn`, release + log out. */
301
+ async function withMailbox(path, deps, fn) {
302
+ return withClient(deps, async (client) => {
303
+ const lock = await client.getMailboxLock(path);
304
+ try {
305
+ return await fn(client);
306
+ }
307
+ finally {
308
+ lock.release();
309
+ }
310
+ });
311
+ }
312
+ /** Read a message by composite IMAP id; returns "Subject: …\n\n<body>". */
313
+ export async function imapGetMessage(id, preferHtml, deps = {}) {
314
+ const ref = decodeImapId(id);
315
+ if (!ref)
316
+ return { success: false, error: `Not an IMAP message id: "${id}".` };
317
+ return withMailbox(ref.path, deps, async (client) => {
318
+ const msg = await client.fetchOne(String(ref.uid), { envelope: true, source: true }, { uid: true });
319
+ if (!msg)
320
+ return { success: false, error: `IMAP message UID ${ref.uid} not found in "${ref.path}".` };
321
+ const subject = msg.envelope?.subject || "(no subject)";
322
+ const src = msg.source ? msg.source.toString() : "";
323
+ const body = (preferHtml ? extractHtmlBody(src) : extractTextBody(src)) ??
324
+ extractTextBody(src) ??
325
+ extractHtmlBody(src) ??
326
+ "(no readable body)";
327
+ return { success: true, info: `Subject: ${subject}\n\n${body}` };
328
+ });
329
+ }
330
+ function flagOp(id, flag, add, deps) {
331
+ const ref = decodeImapId(id);
332
+ if (!ref)
333
+ return Promise.resolve({ success: false, error: `Not an IMAP message id: "${id}".` });
334
+ return withMailbox(ref.path, deps, async (client) => {
335
+ try {
336
+ const ok = add
337
+ ? await client.messageFlagsAdd([ref.uid], [flag], { uid: true })
338
+ : await client.messageFlagsRemove([ref.uid], [flag], { uid: true });
339
+ if (!ok)
340
+ return { success: false, error: `IMAP flag update returned false for UID ${ref.uid}.` };
341
+ return { success: true };
342
+ }
343
+ catch (e) {
344
+ return { success: false, error: `IMAP flag update failed for UID ${ref.uid}: ${errText(e)}` };
345
+ }
346
+ });
347
+ }
348
+ export const imapMarkRead = (id, deps = {}) => flagOp(id, "\\Seen", true, deps);
349
+ export const imapMarkUnread = (id, deps = {}) => flagOp(id, "\\Seen", false, deps);
350
+ export const imapFlagMessage = (id, deps = {}) => flagOp(id, "\\Flagged", true, deps);
351
+ export const imapUnflagMessage = (id, deps = {}) => flagOp(id, "\\Flagged", false, deps);
352
+ export async function imapMoveMessageById(id, destMailbox, deps = {}) {
353
+ const ref = decodeImapId(id);
354
+ if (!ref)
355
+ return { success: false, error: `Not an IMAP message id: "${id}".` };
356
+ return withClient(deps, async (client) => {
357
+ const destPath = (await findMailboxPath(client, destMailbox)) ?? resolveMailboxPath(destMailbox, "list");
358
+ const lock = await client.getMailboxLock(ref.path);
359
+ try {
360
+ await client.messageMove([ref.uid], destPath, { uid: true });
361
+ return { success: true, info: `Moved UID ${ref.uid} to "${destPath}" via IMAP.` };
362
+ }
363
+ catch (e) {
364
+ return {
365
+ success: false,
366
+ error: `IMAP move failed for UID ${ref.uid} -> "${destPath}": ${errText(e)}`,
367
+ };
368
+ }
369
+ finally {
370
+ lock.release();
371
+ }
372
+ });
373
+ }
374
+ export async function imapDeleteMessageById(id, deps = {}) {
375
+ const ref = decodeImapId(id);
376
+ if (!ref)
377
+ return { success: false, error: `Not an IMAP message id: "${id}".` };
378
+ return withMailbox(ref.path, deps, async (client) => {
379
+ try {
380
+ const ok = await client.messageDelete([ref.uid], { uid: true });
381
+ if (!ok)
382
+ return { success: false, error: `IMAP delete returned false for UID ${ref.uid}.` };
383
+ return { success: true, info: `Deleted UID ${ref.uid} from "${ref.path}" via IMAP.` };
384
+ }
385
+ catch (e) {
386
+ return { success: false, error: `IMAP delete failed for UID ${ref.uid}: ${errText(e)}` };
387
+ }
388
+ });
389
+ }
@@ -43,6 +43,12 @@ export declare function parseMimeAttachments(source: string): MimeAttachmentInfo
43
43
  * @returns The decoded HTML body, or null if the message has no text/html part
44
44
  */
45
45
  export declare function extractHtmlBody(source: string): string | null;
46
+ /**
47
+ * Extract the decoded `text/plain` body from raw MIME source. Mirror of
48
+ * extractHtmlBody; used by the IMAP get-message path (#43 Phase 3) to render a
49
+ * message fetched by UID. Returns null when there's no text/plain part.
50
+ */
51
+ export declare function extractTextBody(source: string): string | null;
46
52
  /**
47
53
  * Extract and decode a specific attachment from MIME source by filename.
48
54
  * Supports base64, quoted-printable, and 7bit/8bit/binary transfer encodings.
@@ -1 +1 @@
1
- {"version":3,"file":"mimeParse.d.ts","sourceRoot":"","sources":["../../src/utils/mimeParse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAyLD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAyBzE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,kBAAkB,GAAG,IAAI,CAwB3B"}
1
+ {"version":3,"file":"mimeParse.d.ts","sourceRoot":"","sources":["../../src/utils/mimeParse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAyLD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAyBzE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB7D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,kBAAkB,GAAG,IAAI,CAwB3B"}
@@ -243,6 +243,36 @@ export function extractHtmlBody(source) {
243
243
  const encoding = getHeader(headers, "Content-Transfer-Encoding");
244
244
  return decodeBody(body, encoding).toString("utf8");
245
245
  }
246
+ /**
247
+ * Extract the decoded `text/plain` body from raw MIME source. Mirror of
248
+ * extractHtmlBody; used by the IMAP get-message path (#43 Phase 3) to render a
249
+ * message fetched by UID. Returns null when there's no text/plain part.
250
+ */
251
+ export function extractTextBody(source) {
252
+ if (!source || !source.trim())
253
+ return null;
254
+ const boundary = extractBoundary(source);
255
+ if (boundary) {
256
+ for (const part of walkLeafParts(source, boundary)) {
257
+ if (extractMimeType(part.headers) === "text/plain") {
258
+ const encoding = getHeader(part.headers, "Content-Transfer-Encoding");
259
+ return decodeBody(part.body, encoding).toString("utf8");
260
+ }
261
+ }
262
+ return null;
263
+ }
264
+ // Non-multipart: treat as text/plain unless the Content-Type says otherwise.
265
+ const blankLineIdx = source.search(/\r?\n\r?\n/);
266
+ if (blankLineIdx === -1)
267
+ return null;
268
+ const headers = source.substring(0, blankLineIdx);
269
+ const ct = extractMimeType(headers);
270
+ if (ct !== "text/plain" && getHeader(headers, "Content-Type") !== null)
271
+ return null;
272
+ const body = source.substring(blankLineIdx).replace(/^\r?\n\r?\n/, "");
273
+ const encoding = getHeader(headers, "Content-Transfer-Encoding");
274
+ return decodeBody(body, encoding).toString("utf8");
275
+ }
246
276
  /**
247
277
  * Extract and decode a specific attachment from MIME source by filename.
248
278
  * Supports base64, quoted-printable, and 7bit/8bit/binary transfer encodings.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude",
5
5
  "type": "module",
6
6
  "main": "build/index.js",