Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

mail-mcp: resolve binaries via PATH + account filter on mail_inbox

Make the mail MCP work on macOS without hardcoded /usr/bin paths:
- Resolve mu, mbsync, msmtp via \`which\` with /opt/homebrew fallbacks
- Use \$HOME for maildir + xapian db
- Honor AC_MAIL_* env overrides so hosts with unusual layouts still work

Also add an account='ac-mail'|'jas-mail'|'all' filter to mail_inbox so both
accounts share one tool entry rather than requiring two separate calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+39 -12
+39 -12
ants/mail-mcp/server.mjs
··· 1 1 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 2 import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 3 import { z } from "zod"; 4 - import { execFile as execFileCb, spawn as spawnCb } from "node:child_process"; 4 + import { execFile as execFileCb, spawn as spawnCb, execFileSync } from "node:child_process"; 5 5 import { promisify } from "node:util"; 6 6 import { access, readFile, stat } from "node:fs/promises"; 7 - import { basename } from "node:path"; 7 + import { basename, join } from "node:path"; 8 + import { homedir } from "node:os"; 8 9 9 10 const execFile = promisify(execFileCb); 10 11 11 - const MU = "/usr/bin/mu"; 12 - const MBSYNC = "/usr/bin/mbsync"; 13 - const MSMTP = "/usr/bin/msmtp"; 14 - const MAILDIR = "/home/me/.mail-all"; 15 - const MU_DB = "/home/me/.cache/mu/xapian"; 12 + function resolveBin(name, fallbacks) { 13 + if (process.env[`AC_MAIL_${name.toUpperCase()}`]) { 14 + return process.env[`AC_MAIL_${name.toUpperCase()}`]; 15 + } 16 + try { 17 + return execFileSync("which", [name], { encoding: "utf8" }).trim() || fallbacks[0]; 18 + } catch { 19 + for (const p of fallbacks) { 20 + try { execFileSync("test", ["-x", p]); return p; } catch {} 21 + } 22 + return name; 23 + } 24 + } 25 + 26 + const HOME = homedir(); 27 + const MU = resolveBin("mu", ["/opt/homebrew/bin/mu", "/usr/bin/mu"]); 28 + const MBSYNC = resolveBin("mbsync", ["/opt/homebrew/bin/mbsync", "/usr/bin/mbsync"]); 29 + const MSMTP = resolveBin("msmtp", ["/opt/homebrew/bin/msmtp", "/usr/bin/msmtp"]); 30 + const MAILDIR = process.env.AC_MAIL_MAILDIR || join(HOME, ".mail-all"); 31 + const MU_DB = process.env.AC_MAIL_MU_DB || join(HOME, ".cache", "mu", "xapian"); 16 32 const ACCOUNTS = { 17 33 "ac-mail": "mail@aesthetic.computer", 18 34 "jas-mail": "me@jas.life", ··· 233 249 // --- mail_inbox --- 234 250 server.tool( 235 251 "mail_inbox", 236 - "List recent inbox messages sorted by date descending", 252 + "List recent inbox messages sorted by date descending. Default covers both accounts; pass account='ac-mail' or 'jas-mail' to filter.", 237 253 { 238 254 count: z 239 255 .number() 240 256 .optional() 241 257 .default(20) 242 258 .describe("Number of messages to return (default 20)"), 259 + account: z 260 + .enum(["ac-mail", "jas-mail", "all"]) 261 + .optional() 262 + .default("all") 263 + .describe("Which account inbox to list (default all)"), 243 264 }, 244 - async ({ count }) => { 265 + async ({ count, account }) => { 245 266 try { 246 267 await ensureMuIndex(); 268 + const query = 269 + account === "ac-mail" 270 + ? "maildir:/ac-mail/INBOX" 271 + : account === "jas-mail" 272 + ? "maildir:/jas-mail/INBOX" 273 + : "(maildir:/ac-mail/INBOX OR maildir:/jas-mail/INBOX)"; 247 274 const { stdout } = await run(MU, [ 248 275 "find", 249 276 "--format=json", 250 277 "--sortfield=date", 251 278 "--reverse", 252 279 `--maxnum=${count}`, 253 - "maildir:/INBOX", 280 + query, 254 281 ]); 255 282 return { content: [{ type: "text", text: stdout || "Inbox is empty." }] }; 256 283 } catch (err) { ··· 400 427 // --- mail_count --- 401 428 server.tool( 402 429 "mail_count", 403 - "Return count of unread messages", 430 + "Return count of unread inbox messages (both accounts, avoids Gmail All Mail duplication)", 404 431 {}, 405 432 async () => { 406 433 try { ··· 409 436 "find", 410 437 "--format=plain", 411 438 "--fields=l", 412 - "flag:unread", 439 + "flag:unread AND (maildir:/ac-mail/INBOX OR maildir:/jas-mail/INBOX)", 413 440 ]); 414 441 const count = stdout ? stdout.split("\n").filter(Boolean).length : 0; 415 442 return {