airmail-mcp 1.0.20 → 1.0.22

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/dist/index.js CHANGED
@@ -57,39 +57,85 @@ const AUTO_LAUNCH_AIRMAIL = envFlag("AIRMAIL_MCP_AUTO_LAUNCH");
57
57
  let parentCodeSignTeamID = null;
58
58
  let parentPhysicalIdentity = null;
59
59
  let parentBundleIdentifier = null;
60
- function resolveParentCodeSign() {
60
+ function processInfo(pid) {
61
61
  try {
62
- const ppid = process.ppid;
63
- // Get parent executable path ppid is always numeric, safe for arg
64
- const parentPath = execFileSync("ps", ["-p", String(ppid), "-o", "comm="], { encoding: "utf-8" }).trim();
65
- if (!parentPath)
62
+ const ppidText = execFileSync("ps", ["-p", String(pid), "-o", "ppid="], { encoding: "utf-8" }).trim();
63
+ const command = execFileSync("ps", ["-p", String(pid), "-o", "comm="], { encoding: "utf-8" }).trim();
64
+ const ppid = parseInt(ppidText, 10);
65
+ if (!command || isNaN(ppid))
66
+ return null;
67
+ return { pid, ppid, command };
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ function appBundlePath(executablePath) {
74
+ const appIdx = executablePath.indexOf(".app/");
75
+ return appIdx === -1 ? executablePath : executablePath.slice(0, appIdx + 4);
76
+ }
77
+ function physicalIdentityForPath(path) {
78
+ return path.endsWith(".app") ? `app:${path}` : `path:${path}`;
79
+ }
80
+ function codesignInfo(path) {
81
+ const result = spawnSync("codesign", ["-dv", "--verbose=2", path], {
82
+ encoding: "utf-8",
83
+ stdio: ["pipe", "pipe", "pipe"],
84
+ });
85
+ const output = (result.stdout || "") + (result.stderr || "");
86
+ const identifier = output.match(/Identifier=(\S+)/)?.[1] ?? null;
87
+ const rawTeam = output.match(/TeamIdentifier=(.+)/)?.[1]?.trim() ?? null;
88
+ const teamID = rawTeam && rawTeam !== "not set" && rawTeam !== "not" ? rawTeam : null;
89
+ return { identifier, teamID };
90
+ }
91
+ function assignParentIdentity(path, sign) {
92
+ parentPhysicalIdentity = physicalIdentityForPath(path);
93
+ parentBundleIdentifier = sign.identifier;
94
+ parentCodeSignTeamID = sign.teamID;
95
+ if (parentBundleIdentifier) {
96
+ log(`Parent identity: ${parentBundleIdentifier} (${parentPhysicalIdentity})`);
97
+ }
98
+ if (parentCodeSignTeamID) {
99
+ log(`Parent code sign: Team ID ${parentCodeSignTeamID}`);
100
+ }
101
+ else {
102
+ log(`Parent code sign: unsigned (${parentPhysicalIdentity})`);
103
+ }
104
+ }
105
+ function resolveParentCodeSign() {
106
+ let fallback = null;
107
+ let unsignedApp = null;
108
+ for (let pid = process.ppid, depth = 0; pid > 1 && depth < 16; depth++) {
109
+ const info = processInfo(pid);
110
+ if (!info)
111
+ break;
112
+ const path = appBundlePath(info.command);
113
+ const sign = codesignInfo(path);
114
+ if (!fallback) {
115
+ fallback = { path, sign };
116
+ }
117
+ // Prefer a signed app bundle. With npx, the immediate parent is usually
118
+ // node/npm, so the real client identity is one or more ancestors above it.
119
+ if (path.endsWith(".app") && sign.teamID) {
120
+ assignParentIdentity(path, sign);
66
121
  return;
67
- // Walk up to find .app bundle (if any)
68
- let appPath = parentPath;
69
- const appIdx = parentPath.indexOf(".app/");
70
- if (appIdx !== -1) {
71
- appPath = parentPath.slice(0, appIdx + 4);
72
122
  }
73
- parentPhysicalIdentity = appIdx !== -1 ? `app:${appPath}` : `path:${parentPath}`;
74
- // codesign writes everything to stderr — use spawnSync to capture it
75
- const result = spawnSync("codesign", ["-dv", "--verbose=2", appPath], {
76
- encoding: "utf-8",
77
- stdio: ["pipe", "pipe", "pipe"],
78
- });
79
- const output = (result.stdout || "") + (result.stderr || "");
80
- const identifierMatch = output.match(/Identifier=(\S+)/);
81
- if (identifierMatch) {
82
- parentBundleIdentifier = identifierMatch[1];
83
- log(`Parent identity: ${parentBundleIdentifier} (${parentPhysicalIdentity})`);
123
+ // If no signed bundle exists, an app bundle is still more stable than
124
+ // transient npm/node helper paths.
125
+ if (path.endsWith(".app") && !unsignedApp) {
126
+ unsignedApp = { path, sign };
84
127
  }
85
- const match = output.match(/TeamIdentifier=(\S+)/);
86
- if (match && match[1] !== "not" && match[1] !== "not set") {
87
- parentCodeSignTeamID = match[1];
88
- log(`Parent code sign: Team ID ${parentCodeSignTeamID}`);
128
+ // Signed command-line tools are useful, but less specific than app bundles.
129
+ if (!path.endsWith(".app") && sign.teamID) {
130
+ fallback = { path, sign };
89
131
  }
132
+ pid = info.ppid;
90
133
  }
91
- catch {
92
- // Not code-signed or codesign not available — leave as null
134
+ if (unsignedApp) {
135
+ assignParentIdentity(unsignedApp.path, unsignedApp.sign);
136
+ }
137
+ else if (fallback) {
138
+ assignParentIdentity(fallback.path, fallback.sign);
93
139
  }
94
140
  }
95
141
  // ---------------------------------------------------------------------------
package/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "airmail-mcp",
4
4
  "display_name": "Airmail",
5
- "version": "1.0.20",
5
+ "version": "1.0.22",
6
6
  "description": "Manage emails, calendars, contacts, and more from Claude using Airmail's MCP server.",
7
7
  "long_description": "Airmail MCP connects Claude to the Airmail email client for macOS. Read, search, compose, and organize emails. Manage calendars and reminders. Search contacts. The bridge connects locally to Airmail on your Mac. Data retrieved by AI tools is processed by your chosen AI provider.",
8
8
  "author": {
@@ -309,19 +309,19 @@
309
309
  },
310
310
  {
311
311
  "name": "list_starred",
312
- "description": "List starred/flagged messages."
312
+ "description": "List starred/flagged messages (date desc)."
313
313
  },
314
314
  {
315
315
  "name": "list_sent",
316
- "description": "List sent messages."
316
+ "description": "List sent messages (date desc)."
317
317
  },
318
318
  {
319
319
  "name": "list_trash",
320
- "description": "List trash messages."
320
+ "description": "List trash messages (date desc)."
321
321
  },
322
322
  {
323
323
  "name": "list_spam",
324
- "description": "List spam/junk messages."
324
+ "description": "List spam/junk messages (date desc)."
325
325
  },
326
326
  {
327
327
  "name": "search_messages",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airmail-mcp",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "mcpName": "io.github.airmail/airmail-mcp",
5
5
  "description": "Manage emails, calendars, contacts, and more from Claude using Airmail's MCP server.",
6
6
  "main": "dist/index.js",