apple-mail-mcp 2.8.5 → 2.8.7
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 +26 -14
- package/build/cli.js +11 -4
- package/build/index.js +175 -104
- package/docs/IMAP-SETUP.md +333 -0
- package/docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md +109 -0
- package/docs/STABILITY-PERF-AUDIT-2026-06-17.md +131 -0
- package/package.json +2 -1
package/build/index.js
CHANGED
|
@@ -20707,7 +20707,7 @@ var require_thread_stream = __commonJS({
|
|
|
20707
20707
|
var { version: version3 } = require_package2();
|
|
20708
20708
|
var { EventEmitter } = __require("events");
|
|
20709
20709
|
var { Worker } = __require("worker_threads");
|
|
20710
|
-
var { join:
|
|
20710
|
+
var { join: join6 } = __require("path");
|
|
20711
20711
|
var { pathToFileURL } = __require("url");
|
|
20712
20712
|
var { wait } = require_wait();
|
|
20713
20713
|
var {
|
|
@@ -20758,7 +20758,7 @@ var require_thread_stream = __commonJS({
|
|
|
20758
20758
|
function createWorker(stream, opts) {
|
|
20759
20759
|
const { filename, workerData } = opts;
|
|
20760
20760
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
20761
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
20761
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join6(__dirname, "lib", "worker.js");
|
|
20762
20762
|
const worker = new Worker(toExecute, {
|
|
20763
20763
|
...opts.workerOpts,
|
|
20764
20764
|
name: opts.workerOpts?.name || "thread-stream",
|
|
@@ -21224,9 +21224,9 @@ var require_transport = __commonJS({
|
|
|
21224
21224
|
"node_modules/.pnpm/pino@10.3.1/node_modules/pino/lib/transport.js"(exports, module) {
|
|
21225
21225
|
"use strict";
|
|
21226
21226
|
var { createRequire: createRequire2 } = __require("module");
|
|
21227
|
-
var { existsSync:
|
|
21227
|
+
var { existsSync: existsSync6 } = __require("node:fs");
|
|
21228
21228
|
var getCallers = require_caller();
|
|
21229
|
-
var { join:
|
|
21229
|
+
var { join: join6, isAbsolute: isAbsolute3, sep: sep2 } = __require("node:path");
|
|
21230
21230
|
var { fileURLToPath } = __require("node:url");
|
|
21231
21231
|
var sleep2 = require_atomic_sleep();
|
|
21232
21232
|
var onExit = require_on_exit_leak_free();
|
|
@@ -21298,7 +21298,7 @@ var require_transport = __commonJS({
|
|
|
21298
21298
|
return false;
|
|
21299
21299
|
}
|
|
21300
21300
|
}
|
|
21301
|
-
return isAbsolute3(path) && !
|
|
21301
|
+
return isAbsolute3(path) && !existsSync6(path);
|
|
21302
21302
|
}
|
|
21303
21303
|
function stripQuotes(value) {
|
|
21304
21304
|
const first = value[0];
|
|
@@ -21379,7 +21379,7 @@ var require_transport = __commonJS({
|
|
|
21379
21379
|
throw new Error("only one of target or targets can be specified");
|
|
21380
21380
|
}
|
|
21381
21381
|
if (targets) {
|
|
21382
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
21382
|
+
target = bundlerOverrides["pino-worker"] || join6(__dirname, "worker.js");
|
|
21383
21383
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
21384
21384
|
return {
|
|
21385
21385
|
...dest,
|
|
@@ -21397,7 +21397,7 @@ var require_transport = __commonJS({
|
|
|
21397
21397
|
});
|
|
21398
21398
|
});
|
|
21399
21399
|
} else if (pipeline) {
|
|
21400
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
21400
|
+
target = bundlerOverrides["pino-worker"] || join6(__dirname, "worker.js");
|
|
21401
21401
|
options.pipelines = [pipeline.map((dest) => {
|
|
21402
21402
|
return {
|
|
21403
21403
|
...dest,
|
|
@@ -21420,7 +21420,7 @@ var require_transport = __commonJS({
|
|
|
21420
21420
|
return origin;
|
|
21421
21421
|
}
|
|
21422
21422
|
if (origin === "pino/file") {
|
|
21423
|
-
return
|
|
21423
|
+
return join6(__dirname, "..", "file.js");
|
|
21424
21424
|
}
|
|
21425
21425
|
let fixTarget2;
|
|
21426
21426
|
for (const filePath of callers) {
|
|
@@ -22400,7 +22400,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
22400
22400
|
return circularValue;
|
|
22401
22401
|
}
|
|
22402
22402
|
let res = "";
|
|
22403
|
-
let
|
|
22403
|
+
let join6 = ",";
|
|
22404
22404
|
const originalIndentation = indentation;
|
|
22405
22405
|
if (Array.isArray(value)) {
|
|
22406
22406
|
if (value.length === 0) {
|
|
@@ -22414,7 +22414,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
22414
22414
|
indentation += spacer;
|
|
22415
22415
|
res += `
|
|
22416
22416
|
${indentation}`;
|
|
22417
|
-
|
|
22417
|
+
join6 = `,
|
|
22418
22418
|
${indentation}`;
|
|
22419
22419
|
}
|
|
22420
22420
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -22422,13 +22422,13 @@ ${indentation}`;
|
|
|
22422
22422
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
22423
22423
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
22424
22424
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
22425
|
-
res +=
|
|
22425
|
+
res += join6;
|
|
22426
22426
|
}
|
|
22427
22427
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
22428
22428
|
res += tmp !== void 0 ? tmp : "null";
|
|
22429
22429
|
if (value.length - 1 > maximumBreadth) {
|
|
22430
22430
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
22431
|
-
res += `${
|
|
22431
|
+
res += `${join6}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
22432
22432
|
}
|
|
22433
22433
|
if (spacer !== "") {
|
|
22434
22434
|
res += `
|
|
@@ -22449,7 +22449,7 @@ ${originalIndentation}`;
|
|
|
22449
22449
|
let separator = "";
|
|
22450
22450
|
if (spacer !== "") {
|
|
22451
22451
|
indentation += spacer;
|
|
22452
|
-
|
|
22452
|
+
join6 = `,
|
|
22453
22453
|
${indentation}`;
|
|
22454
22454
|
whitespace = " ";
|
|
22455
22455
|
}
|
|
@@ -22463,13 +22463,13 @@ ${indentation}`;
|
|
|
22463
22463
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
22464
22464
|
if (tmp !== void 0) {
|
|
22465
22465
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
22466
|
-
separator =
|
|
22466
|
+
separator = join6;
|
|
22467
22467
|
}
|
|
22468
22468
|
}
|
|
22469
22469
|
if (keyLength > maximumBreadth) {
|
|
22470
22470
|
const removedKeys = keyLength - maximumBreadth;
|
|
22471
22471
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
22472
|
-
separator =
|
|
22472
|
+
separator = join6;
|
|
22473
22473
|
}
|
|
22474
22474
|
if (spacer !== "" && separator.length > 1) {
|
|
22475
22475
|
res = `
|
|
@@ -22510,7 +22510,7 @@ ${originalIndentation}`;
|
|
|
22510
22510
|
}
|
|
22511
22511
|
const originalIndentation = indentation;
|
|
22512
22512
|
let res = "";
|
|
22513
|
-
let
|
|
22513
|
+
let join6 = ",";
|
|
22514
22514
|
if (Array.isArray(value)) {
|
|
22515
22515
|
if (value.length === 0) {
|
|
22516
22516
|
return "[]";
|
|
@@ -22523,7 +22523,7 @@ ${originalIndentation}`;
|
|
|
22523
22523
|
indentation += spacer;
|
|
22524
22524
|
res += `
|
|
22525
22525
|
${indentation}`;
|
|
22526
|
-
|
|
22526
|
+
join6 = `,
|
|
22527
22527
|
${indentation}`;
|
|
22528
22528
|
}
|
|
22529
22529
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -22531,13 +22531,13 @@ ${indentation}`;
|
|
|
22531
22531
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
22532
22532
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
22533
22533
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
22534
|
-
res +=
|
|
22534
|
+
res += join6;
|
|
22535
22535
|
}
|
|
22536
22536
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
22537
22537
|
res += tmp !== void 0 ? tmp : "null";
|
|
22538
22538
|
if (value.length - 1 > maximumBreadth) {
|
|
22539
22539
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
22540
|
-
res += `${
|
|
22540
|
+
res += `${join6}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
22541
22541
|
}
|
|
22542
22542
|
if (spacer !== "") {
|
|
22543
22543
|
res += `
|
|
@@ -22550,7 +22550,7 @@ ${originalIndentation}`;
|
|
|
22550
22550
|
let whitespace = "";
|
|
22551
22551
|
if (spacer !== "") {
|
|
22552
22552
|
indentation += spacer;
|
|
22553
|
-
|
|
22553
|
+
join6 = `,
|
|
22554
22554
|
${indentation}`;
|
|
22555
22555
|
whitespace = " ";
|
|
22556
22556
|
}
|
|
@@ -22559,7 +22559,7 @@ ${indentation}`;
|
|
|
22559
22559
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
22560
22560
|
if (tmp !== void 0) {
|
|
22561
22561
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
22562
|
-
separator =
|
|
22562
|
+
separator = join6;
|
|
22563
22563
|
}
|
|
22564
22564
|
}
|
|
22565
22565
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -22617,20 +22617,20 @@ ${originalIndentation}`;
|
|
|
22617
22617
|
indentation += spacer;
|
|
22618
22618
|
let res2 = `
|
|
22619
22619
|
${indentation}`;
|
|
22620
|
-
const
|
|
22620
|
+
const join7 = `,
|
|
22621
22621
|
${indentation}`;
|
|
22622
22622
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
22623
22623
|
let i = 0;
|
|
22624
22624
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
22625
22625
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
22626
22626
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
22627
|
-
res2 +=
|
|
22627
|
+
res2 += join7;
|
|
22628
22628
|
}
|
|
22629
22629
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
22630
22630
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
22631
22631
|
if (value.length - 1 > maximumBreadth) {
|
|
22632
22632
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
22633
|
-
res2 += `${
|
|
22633
|
+
res2 += `${join7}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
22634
22634
|
}
|
|
22635
22635
|
res2 += `
|
|
22636
22636
|
${originalIndentation}`;
|
|
@@ -22646,16 +22646,16 @@ ${originalIndentation}`;
|
|
|
22646
22646
|
return '"[Object]"';
|
|
22647
22647
|
}
|
|
22648
22648
|
indentation += spacer;
|
|
22649
|
-
const
|
|
22649
|
+
const join6 = `,
|
|
22650
22650
|
${indentation}`;
|
|
22651
22651
|
let res = "";
|
|
22652
22652
|
let separator = "";
|
|
22653
22653
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
22654
22654
|
if (isTypedArrayWithEntries(value)) {
|
|
22655
|
-
res += stringifyTypedArray(value,
|
|
22655
|
+
res += stringifyTypedArray(value, join6, maximumBreadth);
|
|
22656
22656
|
keys = keys.slice(value.length);
|
|
22657
22657
|
maximumPropertiesToStringify -= value.length;
|
|
22658
|
-
separator =
|
|
22658
|
+
separator = join6;
|
|
22659
22659
|
}
|
|
22660
22660
|
if (deterministic) {
|
|
22661
22661
|
keys = sort(keys, comparator);
|
|
@@ -22666,13 +22666,13 @@ ${indentation}`;
|
|
|
22666
22666
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
22667
22667
|
if (tmp !== void 0) {
|
|
22668
22668
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
22669
|
-
separator =
|
|
22669
|
+
separator = join6;
|
|
22670
22670
|
}
|
|
22671
22671
|
}
|
|
22672
22672
|
if (keyLength > maximumBreadth) {
|
|
22673
22673
|
const removedKeys = keyLength - maximumBreadth;
|
|
22674
22674
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
22675
|
-
separator =
|
|
22675
|
+
separator = join6;
|
|
22676
22676
|
}
|
|
22677
22677
|
if (separator !== "") {
|
|
22678
22678
|
res = `
|
|
@@ -76147,9 +76147,9 @@ var StdioServerTransport = class {
|
|
|
76147
76147
|
|
|
76148
76148
|
// src/services/appleMailManager.ts
|
|
76149
76149
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
76150
|
-
import { existsSync as
|
|
76151
|
-
import { isAbsolute, resolve, sep, join as
|
|
76152
|
-
import { homedir as
|
|
76150
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync2, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
|
|
76151
|
+
import { isAbsolute, resolve, sep, join as join4 } from "path";
|
|
76152
|
+
import { homedir as homedir3 } from "os";
|
|
76153
76153
|
|
|
76154
76154
|
// src/utils/applescript.ts
|
|
76155
76155
|
import { execSync, spawnSync } from "child_process";
|
|
@@ -76219,7 +76219,7 @@ var ERROR_MAPPINGS = [
|
|
|
76219
76219
|
// Permission errors
|
|
76220
76220
|
{
|
|
76221
76221
|
pattern: /not authorized|not permitted|access.*denied/i,
|
|
76222
|
-
message: "Permission denied. Grant automation access in System
|
|
76222
|
+
message: "Permission denied. Grant automation access in System Settings > Privacy & Security > Automation."
|
|
76223
76223
|
},
|
|
76224
76224
|
// Application not running
|
|
76225
76225
|
{
|
|
@@ -76685,6 +76685,114 @@ function materializeAttachments(attachments) {
|
|
|
76685
76685
|
};
|
|
76686
76686
|
}
|
|
76687
76687
|
|
|
76688
|
+
// src/utils/contactsDb.ts
|
|
76689
|
+
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
76690
|
+
import { join as join3 } from "path";
|
|
76691
|
+
import { homedir as homedir2 } from "os";
|
|
76692
|
+
function loadSqlite() {
|
|
76693
|
+
try {
|
|
76694
|
+
const mod = __require("node:sqlite");
|
|
76695
|
+
if (!mod || typeof mod.DatabaseSync !== "function") {
|
|
76696
|
+
console.error(
|
|
76697
|
+
"[contactsDb] node:sqlite unavailable (no DatabaseSync export); returning no contacts"
|
|
76698
|
+
);
|
|
76699
|
+
return null;
|
|
76700
|
+
}
|
|
76701
|
+
return mod;
|
|
76702
|
+
} catch (err) {
|
|
76703
|
+
console.error(
|
|
76704
|
+
`[contactsDb] node:sqlite not available (requires Node >= 22.5); returning no contacts: ${err instanceof Error ? err.message : String(err)}`
|
|
76705
|
+
);
|
|
76706
|
+
return null;
|
|
76707
|
+
}
|
|
76708
|
+
}
|
|
76709
|
+
function resolveContactsDbPaths(baseDir) {
|
|
76710
|
+
const root = baseDir ?? join3(homedir2(), "Library", "Application Support", "AddressBook");
|
|
76711
|
+
const paths = [];
|
|
76712
|
+
const topLevel = join3(root, "AddressBook-v22.abcddb");
|
|
76713
|
+
if (existsSync2(topLevel)) paths.push(topLevel);
|
|
76714
|
+
const sourcesDir = join3(root, "Sources");
|
|
76715
|
+
if (existsSync2(sourcesDir)) {
|
|
76716
|
+
let entries = [];
|
|
76717
|
+
try {
|
|
76718
|
+
entries = readdirSync(sourcesDir);
|
|
76719
|
+
} catch {
|
|
76720
|
+
entries = [];
|
|
76721
|
+
}
|
|
76722
|
+
for (const entry of entries) {
|
|
76723
|
+
const candidate = join3(sourcesDir, entry, "AddressBook-v22.abcddb");
|
|
76724
|
+
if (existsSync2(candidate)) paths.push(candidate);
|
|
76725
|
+
}
|
|
76726
|
+
}
|
|
76727
|
+
return paths;
|
|
76728
|
+
}
|
|
76729
|
+
var RECORD_SQL = `
|
|
76730
|
+
SELECT r.Z_PK pk,
|
|
76731
|
+
TRIM(COALESCE(r.ZFIRSTNAME,'')||' '||COALESCE(r.ZLASTNAME,'')) fullname,
|
|
76732
|
+
r.ZORGANIZATION org, r.ZNICKNAME nick
|
|
76733
|
+
FROM ZABCDRECORD r
|
|
76734
|
+
WHERE lower(TRIM(COALESCE(r.ZFIRSTNAME,'')||' '||COALESCE(r.ZLASTNAME,''))) LIKE ?
|
|
76735
|
+
OR lower(COALESCE(r.ZORGANIZATION,'')) LIKE ?
|
|
76736
|
+
OR lower(COALESCE(r.ZNICKNAME,'')) LIKE ?
|
|
76737
|
+
OR r.Z_PK IN (SELECT ZOWNER FROM ZABCDEMAILADDRESS WHERE lower(COALESCE(ZADDRESS,'')) LIKE ?)
|
|
76738
|
+
`;
|
|
76739
|
+
var EMAILS_SQL = `SELECT ZADDRESS AS v FROM ZABCDEMAILADDRESS WHERE ZOWNER = ? AND ZADDRESS IS NOT NULL`;
|
|
76740
|
+
var PHONES_SQL = `SELECT ZFULLNUMBER AS v FROM ZABCDPHONENUMBER WHERE ZOWNER = ? AND ZFULLNUMBER IS NOT NULL`;
|
|
76741
|
+
function displayName(row) {
|
|
76742
|
+
const fullname = typeof row.fullname === "string" ? row.fullname.trim() : "";
|
|
76743
|
+
if (fullname) return fullname;
|
|
76744
|
+
const org = typeof row.org === "string" ? row.org.trim() : "";
|
|
76745
|
+
if (org) return org;
|
|
76746
|
+
const nick = typeof row.nick === "string" ? row.nick.trim() : "";
|
|
76747
|
+
if (nick) return nick;
|
|
76748
|
+
return "(no name)";
|
|
76749
|
+
}
|
|
76750
|
+
function searchOneDb(sqlite, dbPath, like) {
|
|
76751
|
+
let db = null;
|
|
76752
|
+
try {
|
|
76753
|
+
db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
|
76754
|
+
const records = db.prepare(RECORD_SQL).all(like, like, like, like);
|
|
76755
|
+
const emailStmt = db.prepare(EMAILS_SQL);
|
|
76756
|
+
const phoneStmt = db.prepare(PHONES_SQL);
|
|
76757
|
+
const contacts = [];
|
|
76758
|
+
for (const rec of records) {
|
|
76759
|
+
const pk = rec.pk;
|
|
76760
|
+
const emails = emailStmt.all(pk).map((r) => r.v).filter((v) => typeof v === "string" && v.length > 0);
|
|
76761
|
+
const phones = phoneStmt.all(pk).map((r) => r.v).filter((v) => typeof v === "string" && v.length > 0);
|
|
76762
|
+
contacts.push({ name: displayName(rec), emails, phones });
|
|
76763
|
+
}
|
|
76764
|
+
return contacts;
|
|
76765
|
+
} catch (err) {
|
|
76766
|
+
console.error(
|
|
76767
|
+
`[contactsDb] skipping unreadable Contacts DB ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
76768
|
+
);
|
|
76769
|
+
return [];
|
|
76770
|
+
} finally {
|
|
76771
|
+
try {
|
|
76772
|
+
db?.close();
|
|
76773
|
+
} catch {
|
|
76774
|
+
}
|
|
76775
|
+
}
|
|
76776
|
+
}
|
|
76777
|
+
function searchContactsDb(query, opts) {
|
|
76778
|
+
if (!query || !query.trim()) return [];
|
|
76779
|
+
const sqlite = loadSqlite();
|
|
76780
|
+
if (!sqlite) return [];
|
|
76781
|
+
const dbPaths = opts?.dbPaths ?? resolveContactsDbPaths();
|
|
76782
|
+
const like = `%${query.toLowerCase()}%`;
|
|
76783
|
+
const seen = /* @__PURE__ */ new Set();
|
|
76784
|
+
const results = [];
|
|
76785
|
+
for (const dbPath of dbPaths) {
|
|
76786
|
+
for (const contact of searchOneDb(sqlite, dbPath, like)) {
|
|
76787
|
+
const key = `${contact.name} ${[...contact.emails].sort().join(",")}`;
|
|
76788
|
+
if (seen.has(key)) continue;
|
|
76789
|
+
seen.add(key);
|
|
76790
|
+
results.push(contact);
|
|
76791
|
+
}
|
|
76792
|
+
}
|
|
76793
|
+
return results;
|
|
76794
|
+
}
|
|
76795
|
+
|
|
76688
76796
|
// src/services/appleMailManager.ts
|
|
76689
76797
|
function getMailboxScanThreshold() {
|
|
76690
76798
|
const raw = process.env.APPLE_MAIL_MAX_SEARCH_MAILBOX;
|
|
@@ -76740,7 +76848,7 @@ function splitSearchDiagnostics(output, account) {
|
|
|
76740
76848
|
}
|
|
76741
76849
|
return { payload, diagnostics };
|
|
76742
76850
|
}
|
|
76743
|
-
var ALLOWED_SAVE_ROOTS = [
|
|
76851
|
+
var ALLOWED_SAVE_ROOTS = [homedir3(), "/tmp", "/private/tmp", "/Volumes"];
|
|
76744
76852
|
function isPathWithinAllowedRoots(resolvedPath) {
|
|
76745
76853
|
return ALLOWED_SAVE_ROOTS.some((root) => {
|
|
76746
76854
|
const base = root.endsWith(sep) ? root.slice(0, -1) : root;
|
|
@@ -76784,7 +76892,7 @@ function buildAttachmentCommands(attachments) {
|
|
|
76784
76892
|
if (!isAbsolute(filePath)) {
|
|
76785
76893
|
throw new Error(`Attachment path must be absolute: "${filePath}"`);
|
|
76786
76894
|
}
|
|
76787
|
-
if (!
|
|
76895
|
+
if (!existsSync3(filePath)) {
|
|
76788
76896
|
throw new Error(`Attachment file not found: "${filePath}"`);
|
|
76789
76897
|
}
|
|
76790
76898
|
}
|
|
@@ -78606,7 +78714,7 @@ var AppleMailManager = class {
|
|
|
78606
78714
|
let dir = null;
|
|
78607
78715
|
try {
|
|
78608
78716
|
dir = mkdtempSync2("/private/tmp/amcp-fetch-");
|
|
78609
|
-
const dest =
|
|
78717
|
+
const dest = join4(dir, attachmentName.replace(/[/\\]/g, "_"));
|
|
78610
78718
|
const ok = this.saveAttachment(id, attachmentName, dest);
|
|
78611
78719
|
if (!ok) {
|
|
78612
78720
|
return {
|
|
@@ -79040,60 +79148,16 @@ ${actionStmts.join("\n")}
|
|
|
79040
79148
|
// Contacts Integration
|
|
79041
79149
|
// ===========================================================================
|
|
79042
79150
|
/**
|
|
79043
|
-
* Search contacts by name or email.
|
|
79151
|
+
* Search contacts by name, organization, nickname, or email address.
|
|
79152
|
+
*
|
|
79153
|
+
* Reads the macOS Contacts (AddressBook) SQLite databases directly via Full
|
|
79154
|
+
* Disk Access — it does NOT drive Contacts.app over AppleScript, so it raises
|
|
79155
|
+
* no Automation / Apple-Events permission prompt (the old path would hang on
|
|
79156
|
+
* that unanswerable prompt on headless/scheduled hosts). See
|
|
79157
|
+
* {@link searchContactsDb}.
|
|
79044
79158
|
*/
|
|
79045
79159
|
searchContacts(query) {
|
|
79046
|
-
|
|
79047
|
-
const script = `
|
|
79048
|
-
tell application "Contacts"
|
|
79049
|
-
set matchedContacts to {}
|
|
79050
|
-
set foundPeople to (every person whose name contains "${safeQuery}") & (every person whose value of emails contains "${safeQuery}")
|
|
79051
|
-
|
|
79052
|
-
-- Deduplicate by tracking IDs
|
|
79053
|
-
set seenIds to {}
|
|
79054
|
-
repeat with p in foundPeople
|
|
79055
|
-
set pid to id of p
|
|
79056
|
-
if seenIds does not contain pid then
|
|
79057
|
-
set end of seenIds to pid
|
|
79058
|
-
-- Per-person try: one malformed contact (e.g. no emails) must not
|
|
79059
|
-
-- abort the whole search and return an empty list (audit finding #13).
|
|
79060
|
-
try
|
|
79061
|
-
set pName to name of p
|
|
79062
|
-
set pEmails to ""
|
|
79063
|
-
repeat with e in emails of p
|
|
79064
|
-
if pEmails is not "" then set pEmails to pEmails & ","
|
|
79065
|
-
set pEmails to pEmails & (value of e)
|
|
79066
|
-
end repeat
|
|
79067
|
-
set pPhones to ""
|
|
79068
|
-
repeat with ph in phones of p
|
|
79069
|
-
if pPhones is not "" then set pPhones to pPhones & ","
|
|
79070
|
-
set pPhones to pPhones & (value of ph)
|
|
79071
|
-
end repeat
|
|
79072
|
-
set end of matchedContacts to pName & "${FIELD_SEP}" & pEmails & "${FIELD_SEP}" & pPhones
|
|
79073
|
-
end try
|
|
79074
|
-
end if
|
|
79075
|
-
end repeat
|
|
79076
|
-
|
|
79077
|
-
set AppleScript's text item delimiters to "${RECORD_SEP}"
|
|
79078
|
-
return matchedContacts as text
|
|
79079
|
-
end tell
|
|
79080
|
-
`;
|
|
79081
|
-
const result = executeAppleScript(script);
|
|
79082
|
-
if (!result.success || !result.output.trim()) {
|
|
79083
|
-
return [];
|
|
79084
|
-
}
|
|
79085
|
-
const items = result.output.split(RECORD_SEP);
|
|
79086
|
-
const contacts = [];
|
|
79087
|
-
for (const item of items) {
|
|
79088
|
-
const parts = item.split(FIELD_SEP);
|
|
79089
|
-
if (parts.length < 3) continue;
|
|
79090
|
-
contacts.push({
|
|
79091
|
-
name: parts[0],
|
|
79092
|
-
emails: parts[1] ? parts[1].split(",").filter(Boolean) : [],
|
|
79093
|
-
phones: parts[2] ? parts[2].split(",").filter(Boolean) : []
|
|
79094
|
-
});
|
|
79095
|
-
}
|
|
79096
|
-
return contacts;
|
|
79160
|
+
return searchContactsDb(query);
|
|
79097
79161
|
}
|
|
79098
79162
|
// ===========================================================================
|
|
79099
79163
|
// Email Templates
|
|
@@ -79153,7 +79217,7 @@ ${actionStmts.join("\n")}
|
|
|
79153
79217
|
message: "Mail.app is accessible"
|
|
79154
79218
|
});
|
|
79155
79219
|
} else {
|
|
79156
|
-
const errorHint = mailCheck.error?.includes("not authorized") ? " (check
|
|
79220
|
+
const errorHint = mailCheck.error?.includes("not authorized") ? " (check System Settings > Privacy & Security > Automation)" : "";
|
|
79157
79221
|
checks.push({
|
|
79158
79222
|
name: "mail_app",
|
|
79159
79223
|
passed: false,
|
|
@@ -79173,7 +79237,7 @@ ${actionStmts.join("\n")}
|
|
|
79173
79237
|
checks.push({
|
|
79174
79238
|
name: "permissions",
|
|
79175
79239
|
passed: !isPermError,
|
|
79176
|
-
message: isPermError ? "AppleScript permissions denied. Grant access in System
|
|
79240
|
+
message: isPermError ? "AppleScript permissions denied. Grant access in System Settings > Privacy & Security > Automation" : `Permission check returned: ${permCheck.error}`
|
|
79177
79241
|
});
|
|
79178
79242
|
if (isPermError) {
|
|
79179
79243
|
return { healthy: false, checks };
|
|
@@ -79408,7 +79472,13 @@ import { resolve as resolvePath, join as joinPath } from "path";
|
|
|
79408
79472
|
var import_nodemailer = __toESM(require_nodemailer(), 1);
|
|
79409
79473
|
import { execFileSync } from "child_process";
|
|
79410
79474
|
import { isAbsolute as isAbsolute2 } from "path";
|
|
79411
|
-
import { existsSync as
|
|
79475
|
+
import { existsSync as existsSync4 } from "fs";
|
|
79476
|
+
|
|
79477
|
+
// src/utils/docsUrls.ts
|
|
79478
|
+
var SETUP_GUIDE_URL = "https://github.com/sweetrb/apple-mail-mcp/blob/main/docs/IMAP-SETUP.md";
|
|
79479
|
+
var SETUP_HINT = `Setup guide: ${SETUP_GUIDE_URL} \u2014 run the "doctor" tool to check your setup.`;
|
|
79480
|
+
|
|
79481
|
+
// src/services/smtpMailer.ts
|
|
79412
79482
|
var SMTP_ENV = {
|
|
79413
79483
|
host: "APPLE_MAIL_MCP_SMTP_HOST",
|
|
79414
79484
|
port: "APPLE_MAIL_MCP_SMTP_PORT",
|
|
@@ -79451,7 +79521,7 @@ function resolveSmtpConfig(env = process.env) {
|
|
|
79451
79521
|
if (!user) missing.push(SMTP_ENV.user);
|
|
79452
79522
|
if (missing.length > 0) {
|
|
79453
79523
|
throw new Error(
|
|
79454
|
-
`SMTP transport is not configured. Set ${missing.join(" and ")} (plus a password via ${SMTP_ENV.password} or the Keychain).
|
|
79524
|
+
`SMTP transport is not configured. Set ${missing.join(" and ")} (plus a password via ${SMTP_ENV.password} or the Keychain). ` + SETUP_HINT
|
|
79455
79525
|
);
|
|
79456
79526
|
}
|
|
79457
79527
|
const secure = /^(1|true|yes)$/i.test(env[SMTP_ENV.secure]?.trim() ?? "");
|
|
@@ -79468,7 +79538,7 @@ function resolveSmtpConfig(env = process.env) {
|
|
|
79468
79538
|
}
|
|
79469
79539
|
if (!pass) {
|
|
79470
79540
|
throw new Error(
|
|
79471
|
-
`No SMTP password found. Set ${SMTP_ENV.password}, or store an internet password in the Keychain for service "${env[SMTP_ENV.keychainService]?.trim() || host}" / account "${env[SMTP_ENV.keychainAccount]?.trim() || user}"
|
|
79541
|
+
`No SMTP password found. Set ${SMTP_ENV.password}, or store an internet password in the Keychain for service "${env[SMTP_ENV.keychainService]?.trim() || host}" / account "${env[SMTP_ENV.keychainAccount]?.trim() || user}". ` + SETUP_HINT
|
|
79472
79542
|
);
|
|
79473
79543
|
}
|
|
79474
79544
|
return { host, port, secure, user, pass, from };
|
|
@@ -79478,7 +79548,7 @@ function buildAttachments(attachments) {
|
|
|
79478
79548
|
return attachments.map((a) => {
|
|
79479
79549
|
if (typeof a === "string") {
|
|
79480
79550
|
if (!isAbsolute2(a)) throw new Error(`Attachment path must be absolute: "${a}"`);
|
|
79481
|
-
if (!
|
|
79551
|
+
if (!existsSync4(a)) throw new Error(`Attachment file not found: "${a}"`);
|
|
79482
79552
|
return { path: a };
|
|
79483
79553
|
}
|
|
79484
79554
|
if (!a.filename || !a.contentBase64) {
|
|
@@ -79811,7 +79881,9 @@ function resolveImapConfigs(env = process.env) {
|
|
|
79811
79881
|
function resolveImapConfig(env = process.env, account) {
|
|
79812
79882
|
const specs = listImapAccountSpecs(env);
|
|
79813
79883
|
if (specs.length === 0) {
|
|
79814
|
-
throw new Error(
|
|
79884
|
+
throw new Error(
|
|
79885
|
+
`IMAP not configured. Set ${IMAP_ENV.user} (login address) to enable it. ${SETUP_HINT}`
|
|
79886
|
+
);
|
|
79815
79887
|
}
|
|
79816
79888
|
let spec;
|
|
79817
79889
|
if (account) {
|
|
@@ -80757,7 +80829,6 @@ async function routeMessage(id, opts) {
|
|
|
80757
80829
|
}
|
|
80758
80830
|
|
|
80759
80831
|
// src/tools/doctor.ts
|
|
80760
|
-
var SETUP_GUIDE = "https://github.com/sweetrb/apple-mail-mcp/blob/main/docs/IMAP-SETUP.md";
|
|
80761
80832
|
var CONFIG_FILE_HINT = "If your MCP host ignores the server 'env' block (e.g. Claude Desktop), put these in ~/Library/Application Support/apple-mail-mcp/config.json instead";
|
|
80762
80833
|
async function runDoctor(mailManager2) {
|
|
80763
80834
|
const checks = [];
|
|
@@ -80790,7 +80861,7 @@ async function runDoctor(mailManager2) {
|
|
|
80790
80861
|
checks.push({
|
|
80791
80862
|
name: "IMAP backend",
|
|
80792
80863
|
status: "warn",
|
|
80793
|
-
detail: `not configured \u2014 AppleScript is used for all accounts. Set ${IMAP_ENV.user} (+ Keychain/password), or ${IMAP_ENV.accounts} for multiple accounts, to enable server-side search and server-mailbox ops. ${CONFIG_FILE_HINT}. Setup guide: ${
|
|
80864
|
+
detail: `not configured \u2014 AppleScript is used for all accounts. Set ${IMAP_ENV.user} (+ Keychain/password), or ${IMAP_ENV.accounts} for multiple accounts, to enable server-side search and server-mailbox ops. ${CONFIG_FILE_HINT}. Setup guide: ${SETUP_GUIDE_URL}`
|
|
80794
80865
|
});
|
|
80795
80866
|
} else {
|
|
80796
80867
|
for (const label of imapAccounts) {
|
|
@@ -80806,7 +80877,7 @@ async function runDoctor(mailManager2) {
|
|
|
80806
80877
|
checks.push({
|
|
80807
80878
|
name: "SMTP transport",
|
|
80808
80879
|
status: isSmtpConfigured() ? "ok" : "warn",
|
|
80809
|
-
detail: isSmtpConfigured() ? `configured (${smtpHost}); send-email auto-prefers clean SMTP (no Mail.app Sent-folder copy; a non-email "account" label still routes to AppleScript). Pass transport:"applescript" to force Mail.app. The apple-mail-send CLI is also available.` : `not configured \u2014 send-email uses AppleScript (subject to macOS 15+ blockquote wrapping). Set ${SMTP_ENV.host} and ${SMTP_ENV.user} (+ password via Keychain) to enable. ${CONFIG_FILE_HINT}. Setup guide: ${
|
|
80880
|
+
detail: isSmtpConfigured() ? `configured (${smtpHost}); send-email auto-prefers clean SMTP (no Mail.app Sent-folder copy; a non-email "account" label still routes to AppleScript). Pass transport:"applescript" to force Mail.app. The apple-mail-send CLI is also available.` : `not configured \u2014 send-email uses AppleScript (subject to macOS 15+ blockquote wrapping). Set ${SMTP_ENV.host} and ${SMTP_ENV.user} (+ password via Keychain) to enable. ${CONFIG_FILE_HINT}. Setup guide: ${SETUP_GUIDE_URL}`
|
|
80810
80881
|
});
|
|
80811
80882
|
const healthy = !checks.some((c) => c.status === "fail");
|
|
80812
80883
|
return { healthy, checks };
|
|
@@ -81071,18 +81142,18 @@ var ImapIdleWatcher = class {
|
|
|
81071
81142
|
};
|
|
81072
81143
|
|
|
81073
81144
|
// src/services/fileConfig.ts
|
|
81074
|
-
import { existsSync as
|
|
81075
|
-
import { join as
|
|
81076
|
-
import { homedir as
|
|
81145
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
81146
|
+
import { join as join5 } from "path";
|
|
81147
|
+
import { homedir as homedir4 } from "os";
|
|
81077
81148
|
function fileConfigPath(env = process.env) {
|
|
81078
81149
|
const override = env.APPLE_MAIL_MCP_CONFIG_FILE;
|
|
81079
81150
|
if (override && override.trim()) return override.trim();
|
|
81080
|
-
return
|
|
81151
|
+
return join5(homedir4(), "Library", "Application Support", "apple-mail-mcp", "config.json");
|
|
81081
81152
|
}
|
|
81082
81153
|
function loadFileConfig(env = process.env, path = fileConfigPath(env)) {
|
|
81083
81154
|
const applied = [];
|
|
81084
81155
|
try {
|
|
81085
|
-
if (!
|
|
81156
|
+
if (!existsSync5(path)) return applied;
|
|
81086
81157
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
81087
81158
|
if (!parsed || typeof parsed !== "object") return applied;
|
|
81088
81159
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -82690,7 +82761,7 @@ server.registerTool(
|
|
|
82690
82761
|
server.registerTool(
|
|
82691
82762
|
"search-contacts",
|
|
82692
82763
|
{
|
|
82693
|
-
description: "Use when: looking up a person in Contacts
|
|
82764
|
+
description: "Use when: looking up a person in Contacts by name, organization, nickname, or email to find their email address(es)/phone(s) before composing or sending mail. Reads the macOS Contacts database directly (needs Full Disk Access; does NOT require Contacts.app to be running or an Automation / Apple-Events grant).\nReturns: matching contacts with their names, email addresses, and phone numbers.\nDo not use when: searching email messages (use search-messages) \u2014 this queries Contacts, not the mailbox.",
|
|
82694
82765
|
inputSchema: {
|
|
82695
82766
|
query: external_exports.string().min(1, "Search query is required")
|
|
82696
82767
|
},
|