apple-mail-mcp 2.20.17 → 2.20.19

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 (2) hide show
  1. package/build/index.js +144 -22
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -65193,16 +65193,112 @@ function getHeader(headers, name2) {
65193
65193
  if (!match) return null;
65194
65194
  return match[1].replace(/\r?\n[ \t]+/g, " ").trim();
65195
65195
  }
65196
+ function decodeBytes(bytes, charset) {
65197
+ try {
65198
+ return new TextDecoder(charset.trim().toLowerCase()).decode(bytes);
65199
+ } catch {
65200
+ return bytes.toString("utf8");
65201
+ }
65202
+ }
65203
+ function parseParams(value) {
65204
+ const pieces = [];
65205
+ let current = "";
65206
+ let quoted = false;
65207
+ for (let i = 0; i < value.length; i++) {
65208
+ const ch = value[i];
65209
+ if (ch === "\\" && quoted && i + 1 < value.length) {
65210
+ current += ch + value[++i];
65211
+ continue;
65212
+ }
65213
+ if (ch === '"') quoted = !quoted;
65214
+ if (ch === ";" && !quoted) {
65215
+ pieces.push(current);
65216
+ current = "";
65217
+ } else {
65218
+ current += ch;
65219
+ }
65220
+ }
65221
+ pieces.push(current);
65222
+ const params = /* @__PURE__ */ new Map();
65223
+ for (const piece of pieces.slice(1)) {
65224
+ const eq = piece.indexOf("=");
65225
+ if (eq < 0) continue;
65226
+ let val = piece.slice(eq + 1).trim();
65227
+ if (val.length >= 2 && val.startsWith('"') && val.endsWith('"')) {
65228
+ val = val.slice(1, -1).replace(/\\(.)/g, "$1");
65229
+ }
65230
+ params.set(piece.slice(0, eq).trim().toLowerCase(), val);
65231
+ }
65232
+ return params;
65233
+ }
65234
+ function decodeEncodedWords(text) {
65235
+ return text.replace(/(\?=)\s+(?==\?)/g, "$1").replace(
65236
+ /=\?([^?\s]+)\?([bq])\?([^?]*)\?=/gi,
65237
+ (_all, charset, kind, body) => {
65238
+ const bytes = kind.toLowerCase() === "b" ? Buffer.from(body, "base64") : Buffer.from(
65239
+ body.replace(/_/g, " ").replace(
65240
+ /=([0-9a-f]{2})/gi,
65241
+ (_m, hex) => String.fromCharCode(parseInt(hex, 16))
65242
+ ),
65243
+ "latin1"
65244
+ );
65245
+ return decodeBytes(bytes, charset.split("*")[0]);
65246
+ }
65247
+ );
65248
+ }
65249
+ function decodeExtendedValue(segments) {
65250
+ let charset = "utf-8";
65251
+ const chunks = [];
65252
+ segments.forEach((segment, index) => {
65253
+ let value = segment.value;
65254
+ if (!segment.encoded) {
65255
+ chunks.push(Buffer.from(value, "utf8"));
65256
+ return;
65257
+ }
65258
+ if (index === 0) {
65259
+ const head = value.match(/^([^']*)'[^']*'([\s\S]*)$/);
65260
+ if (head) {
65261
+ charset = head[1] || charset;
65262
+ value = head[2];
65263
+ }
65264
+ }
65265
+ chunks.push(
65266
+ Buffer.from(
65267
+ value.replace(
65268
+ /%([0-9a-f]{2})/gi,
65269
+ (_m, hex) => String.fromCharCode(parseInt(hex, 16))
65270
+ ),
65271
+ "latin1"
65272
+ )
65273
+ );
65274
+ });
65275
+ return decodeBytes(Buffer.concat(chunks), charset);
65276
+ }
65277
+ function paramValue(params, name2) {
65278
+ const extended = params.get(`${name2}*`);
65279
+ if (extended !== void 0) return decodeExtendedValue([{ value: extended, encoded: true }]);
65280
+ const segments = [];
65281
+ for (let i = 0; ; i++) {
65282
+ const encoded = params.get(`${name2}*${i}*`);
65283
+ const plain = params.get(`${name2}*${i}`);
65284
+ if (encoded !== void 0) segments.push({ value: encoded, encoded: true });
65285
+ else if (plain !== void 0) segments.push({ value: plain, encoded: false });
65286
+ else break;
65287
+ }
65288
+ if (segments.length > 0) return decodeExtendedValue(segments);
65289
+ const value = params.get(name2);
65290
+ return value === void 0 ? null : decodeEncodedWords(value);
65291
+ }
65196
65292
  function extractFilename(headers) {
65197
65293
  const dispHeader = getHeader(headers, "Content-Disposition");
65198
65294
  if (dispHeader) {
65199
- const fnMatch = dispHeader.match(/filename="?([^";\r\n]+)"?/i);
65200
- if (fnMatch) return fnMatch[1].trim();
65295
+ const filename = paramValue(parseParams(dispHeader), "filename")?.trim();
65296
+ if (filename) return filename;
65201
65297
  }
65202
65298
  const ctHeader = getHeader(headers, "Content-Type");
65203
65299
  if (ctHeader) {
65204
- const nameMatch = ctHeader.match(/name="?([^";\r\n]+)"?/i);
65205
- if (nameMatch) return nameMatch[1].trim();
65300
+ const name2 = paramValue(parseParams(ctHeader), "name")?.trim();
65301
+ if (name2) return name2;
65206
65302
  }
65207
65303
  return null;
65208
65304
  }
@@ -65217,23 +65313,33 @@ function extractContentId(headers) {
65217
65313
  const id = raw.replace(/^<+/, "").replace(/>+$/, "").trim().toLowerCase();
65218
65314
  return id || null;
65219
65315
  }
65220
- function isEmbeddedResource(headers, htmlBodies) {
65316
+ function isEmbeddedResource(headers, referenced) {
65221
65317
  if (!isInlineDisposition(headers)) return false;
65222
65318
  const cid = extractContentId(headers);
65223
65319
  if (!cid) return false;
65224
- const needle = `cid:${cid}`;
65225
- return htmlBodies.some((html) => html.includes(needle));
65320
+ return referenced().has(cid);
65321
+ }
65322
+ function cidReferences(html) {
65323
+ const refs = /* @__PURE__ */ new Set();
65324
+ for (const match of html.matchAll(/cid:([^\s"'<>()]+)/gi)) {
65325
+ let ref = match[1];
65326
+ try {
65327
+ ref = decodeURIComponent(ref);
65328
+ } catch {
65329
+ }
65330
+ refs.add(ref.trim().toLowerCase());
65331
+ }
65332
+ return refs;
65226
65333
  }
65227
- function collectHtmlBodies(parts) {
65228
- const bodies = [];
65334
+ function collectCidReferences(parts) {
65335
+ const refs = /* @__PURE__ */ new Set();
65229
65336
  for (const part of parts) {
65230
65337
  if (extractMimeType(part.headers) !== "text/html") continue;
65231
65338
  const encoding = getHeader(part.headers, "Content-Transfer-Encoding");
65232
- bodies.push(
65233
- decodePartText(decodeBody(part.body, encoding), extractCharset(part.headers)).toLowerCase()
65234
- );
65339
+ const html = decodePartText(decodeBody(part.body, encoding), extractCharset(part.headers));
65340
+ for (const ref of cidReferences(html)) refs.add(ref);
65235
65341
  }
65236
- return bodies;
65342
+ return refs;
65237
65343
  }
65238
65344
  function extractSize(headers) {
65239
65345
  const dispHeader = getHeader(headers, "Content-Disposition");
@@ -65340,12 +65446,13 @@ function parseMimeAttachments(source) {
65340
65446
  const boundary = extractBoundary(source);
65341
65447
  if (!boundary) return [];
65342
65448
  const parts = walkLeafParts(source, boundary);
65343
- const htmlBodies = collectHtmlBodies(parts);
65449
+ let refs;
65450
+ const referenced = () => refs ??= collectCidReferences(parts);
65344
65451
  const attachments = [];
65345
65452
  for (const part of parts) {
65346
65453
  const filename = extractFilename(part.headers);
65347
65454
  if (!filename) continue;
65348
- if (isEmbeddedResource(part.headers, htmlBodies)) continue;
65455
+ if (isEmbeddedResource(part.headers, referenced)) continue;
65349
65456
  const encoding = getHeader(part.headers, "Content-Transfer-Encoding");
65350
65457
  attachments.push({
65351
65458
  name: filename,
@@ -65432,7 +65539,7 @@ var init_mimeParse = __esm({
65432
65539
  });
65433
65540
 
65434
65541
  // src/utils/headers.ts
65435
- function decodeEncodedWords(value) {
65542
+ function decodeEncodedWords2(value) {
65436
65543
  if (!value.includes("=?")) return value;
65437
65544
  const joined = value.replace(/(\?=)\s+(=\?)/g, "$1$2");
65438
65545
  return joined.replace(
@@ -65522,7 +65629,7 @@ function parseHeaderBlock(input) {
65522
65629
  const all = (name2) => headers.filter((h) => h.name.toLowerCase() === name2.toLowerCase()).map((h) => h.value);
65523
65630
  const decoded = (name2) => {
65524
65631
  const v = first(name2);
65525
- return v === void 0 ? void 0 : decodeEncodedWords(v);
65632
+ return v === void 0 ? void 0 : decodeEncodedWords2(v);
65526
65633
  };
65527
65634
  const dateHeader = first("Date") || void 0;
65528
65635
  let date3;
@@ -84997,9 +85104,13 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
84997
85104
  if (scoped.kind === "hit") return scoped.output;
84998
85105
  if (scoped.kind === "done") return null;
84999
85106
  }
85000
- const result = executeAppleScript(this.unscopedByIdScript(id, innerAction, opts), {
85001
- timeoutMs: READ_ONLY_BYID_TIMEOUT_MS
85002
- });
85107
+ const account = hint?.account && !hint?.mailbox ? hint.account : void 0;
85108
+ const result = executeAppleScript(
85109
+ this.unscopedByIdScript(id, innerAction, { ...opts, account }),
85110
+ {
85111
+ timeoutMs: READ_ONLY_BYID_TIMEOUT_MS
85112
+ }
85113
+ );
85003
85114
  if (!result.success || !result.output.trim()) {
85004
85115
  if (!result.success) {
85005
85116
  if (result.timedOut) this.lastMessageLookupError = timedOutScanningForId(id);
@@ -85116,6 +85227,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
85116
85227
  */
85117
85228
  unscopedByIdScript(id, innerAction, opts = {}) {
85118
85229
  const threshold = getByIdScanThreshold();
85230
+ const scopeAcct = escapeForAppleScript(opts.account ?? "");
85119
85231
  const stalledKeys = threshold > 0 ? this.stalledMailboxes.keys() : [];
85120
85232
  const stalledList = `{${stalledKeys.map((key) => {
85121
85233
  const i = key.indexOf(STALLED_KEY_SEP);
@@ -85175,6 +85287,8 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
85175
85287
  set _probeStalled to false
85176
85288
  set _stopAt to 0
85177
85289
  set _stalledKeys to ${stalledList}
85290
+ set _scopeAcct to "${scopeAcct}"
85291
+ set _acctSeen to false
85178
85292
  set _startedAt to current date
85179
85293
  set _acctNames to {}
85180
85294
  try
@@ -85191,10 +85305,14 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
85191
85305
  set _acctName to ""
85192
85306
  if _ai <= (length of _acctNames) then set _acctName to item _ai of _acctNames
85193
85307
  if _acctName is "" then set _acctName to (name of acct)
85194
- if _budgetHit or _probeStalled then
85308
+ if _scopeAcct is not "" and _acctName is not _scopeAcct then
85309
+ -- Scoped to one account: leave the others alone.
85310
+ set _ai to _ai + 0
85311
+ else if _budgetHit or _probeStalled then
85195
85312
  -- Stopped early: name what is left without asking Mail.app.
85196
85313
  set _unreached to _unreached & _acctName & " (every mailbox)${DIAG_ITEM_SEP}"
85197
85314
  else
85315
+ set _acctSeen to true
85198
85316
  set _mbNames to {}
85199
85317
  try
85200
85318
  set _mbNames to name of mailboxes of acct
@@ -85240,7 +85358,11 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
85240
85358
  -- reach them. Collect into the SAME _hits/_names, which means an id
85241
85359
  -- present both in an account and locally is now correctly reported as
85242
85360
  -- ambiguous rather than silently resolving to the account copy.
85243
- if _budgetHit or _probeStalled then
85361
+ if _scopeAcct is not "" and not _acctSeen then return "${LOOKUP_ERROR_MARKER}Account \\"" & _scopeAcct & "\\" not found \u2014 check the name with list-accounts."
85362
+ if _scopeAcct is not "" then
85363
+ -- Local mailboxes belong to no account.
85364
+ set _ai to _ai + 0
85365
+ else if _budgetHit or _probeStalled then
85244
85366
  set _unreached to _unreached & "${LOCAL_STORE_LABEL} (any local mailboxes)${DIAG_ITEM_SEP}"
85245
85367
  else if not (${hitFound}) then${localMailboxBindingFragment()}
85246
85368
  set _mi to 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.20.17",
3
+ "version": "2.20.19",
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",