airmail-mcp 1.0.7 → 1.0.13

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 +39 -0
  2. package/dist/index.js +30 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -172,6 +172,45 @@ To find your token: open Airmail → **Preferences → MCP** → copy the **Auth
172
172
  ### Meta
173
173
  `manage_capabilities` — enable/disable tool groups to reduce context usage
174
174
 
175
+ ## Tool groups
176
+
177
+ Tools are organized into capability groups that can be enabled or disabled at runtime via `manage_capabilities`. This reduces context usage by hiding tools you don't need.
178
+
179
+ | Group | Tools | Default |
180
+ |-------|-------|---------|
181
+ | mail | Email read, actions, compose | Always on |
182
+ | profile | User profile, triage, behavior stats | On |
183
+ | folders | Folder CRUD | On |
184
+ | semantic | Semantic search, index status | On |
185
+ | calendar | Calendar events, reminders | On |
186
+ | contacts | Address book | On |
187
+ | preferences | App preferences | On |
188
+ | rules | Email rules | On |
189
+ | lists | VIP & blocked senders | On |
190
+ | smartfolders | Smart folder CRUD | On |
191
+ | signatures | Email signatures | On |
192
+ | aliases | Email aliases | On |
193
+ | accountsettings | Per-account settings, vacation | On |
194
+
195
+ To enable all groups, ask the AI to call `manage_capabilities` with `enable: ["preferences", "rules", "lists", "smartfolders", "signatures", "aliases", "accountsettings"]`.
196
+
197
+ ## Deep links
198
+
199
+ MCP tool responses include `airmail://` deep links that open Airmail directly to the relevant content.
200
+
201
+ | Command | URL | Description |
202
+ |---------|-----|-------------|
203
+ | `message` | `airmail://message?mail=...&messageid=...` | Select message in main window |
204
+ | `open` | `airmail://open?mail=...&messageid=...` | Open message in reader window |
205
+ | `compose` | `airmail://compose?to=...&subject=...` | Open composer with pre-filled content |
206
+ | `reply` | `airmail://reply?mail=...&messageid=...` | Reply to a message |
207
+ | `draft` | `airmail://draft?mail=...&messageid=...` | Open draft in composer |
208
+ | `archive` | `airmail://archive?mail=...&messageid=...` | Archive a message |
209
+ | `delete` | `airmail://delete?mail=...&messageid=...` | Move message to trash |
210
+ | `view` | `airmail://view?mail=...&folder=...` | Navigate to account/folder |
211
+ | `attachment` | `airmail://attachment?mail=...&messageid=...&index=0` | Open an attachment |
212
+ | `settings` | `airmail://settings?pref=mcp_server` | Open Preferences pane |
213
+
175
214
  ## How it works
176
215
 
177
216
  ```
package/dist/index.js CHANGED
@@ -11,8 +11,10 @@
11
11
  * collect all data before the FIN arrives.
12
12
  */
13
13
  import { execFileSync, spawnSync } from "child_process";
14
- import { writeSync } from "fs";
14
+ import { readFileSync, writeSync } from "fs";
15
15
  import * as net from "net";
16
+ import { dirname, join } from "path";
17
+ import { fileURLToPath } from "url";
16
18
  // ---------------------------------------------------------------------------
17
19
  // Configuration
18
20
  // ---------------------------------------------------------------------------
@@ -29,7 +31,15 @@ const AIRMAIL_PORT = (() => {
29
31
  return p;
30
32
  })();
31
33
  const AIRMAIL_PATH = "/mcp";
32
- const VERSION = "1.0.0";
34
+ const VERSION = (() => {
35
+ try {
36
+ const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
37
+ return pkg.version ?? "0.0.0";
38
+ }
39
+ catch {
40
+ return "0.0.0";
41
+ }
42
+ })();
33
43
  let currentToken = "";
34
44
  const RETRY_DELAY_MS = 2000;
35
45
  const MAX_LAUNCH_RETRIES = 5;
@@ -286,7 +296,7 @@ function forward(body, clientName, token, hasId) {
286
296
  reqHeaders += `Connection: close\r\n`;
287
297
  reqHeaders += `User-Agent: airmail-mcp/1.0\r\n`;
288
298
  if (token) {
289
- reqHeaders += `Authorization: Bearer ${token}\r\n`;
299
+ reqHeaders += `Authorization: Bearer ${sanitizeHeaderValue(token)}\r\n`;
290
300
  }
291
301
  reqHeaders += `X-MCP-Client: ${safeClient}\r\n`;
292
302
  if (parentCodeSignTeamID) {
@@ -485,7 +495,23 @@ async function main() {
485
495
  }
486
496
  pendingAfterInit = [];
487
497
  })
488
- .catch((err) => log(`Initialize error: ${err}`));
498
+ .catch((err) => {
499
+ log(`Initialize error: ${err}`);
500
+ // Send error responses for queued requests so clients don't hang
501
+ for (const queued of pendingAfterInit) {
502
+ try {
503
+ const q = JSON.parse(queued);
504
+ if (q.id !== undefined) {
505
+ process.stdout.write(JSON.stringify({
506
+ jsonrpc: "2.0", id: q.id,
507
+ error: { code: -32002, message: "Server failed to initialize" },
508
+ }) + "\n");
509
+ }
510
+ }
511
+ catch { /* not valid JSON, skip */ }
512
+ }
513
+ pendingAfterInit = [];
514
+ });
489
515
  inflight.add(p);
490
516
  p.finally(() => {
491
517
  inflight.delete(p);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airmail-mcp",
3
- "version": "1.0.7",
3
+ "version": "1.0.13",
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",