Triggers Apple Mail mailbox index rebuild by removing the envelope index file, forcing Mail to rebuild on next launch. Use when Mail is slow, showing wrong message counts, or missing messages. macOS only.
/** * mcp/skills/rebuildMailIndex.ts — rebuild_mail_index skill * * Triggers Apple Mail mailbox index rebuild by removing the envelope index * file, forcing Mail to rebuild on next launch. Use when Mail is slow, * showing wrong message counts, or missing messages. * * Platform strategy * ----------------- * darwin Removes ~/Library/Mail/V10/MailData/Envelope Index (+ -shm, -wal) * after optionally quitting Mail via AppleScript. * win32 Not supported — Apple Mail is macOS only. * * Smoke test * npx tsx -r dotenv/config mcp/skills/rebuildMailIndex.ts */import * as os from "os";import * as nodePath from "path";import * as fs from "fs/promises";import { exec } from "child_process";import { promisify } from "util";import { z } from "zod";const execAsync = promisify(exec);// -- Meta ---------------------------------------------------------------------export const meta = { name: "rebuild_mail_index", description: "Triggers Apple Mail mailbox index rebuild by removing the envelope index " + "file, forcing Mail to rebuild on next launch. " + "Use when Mail is slow, showing wrong message counts, or missing messages. " + "macOS only.", riskLevel: "medium", destructive: false, requiresConsent: true, supportsDryRun: true, affectedScope: ["user"], auditRequired: true, schema: { dryRun: z .boolean() .optional() .describe("If true, show what would be removed without removing. Default: true"), },} as const;// -- Types --------------------------------------------------------------------interface RebuildMailIndexResult { filesFound: string[]; filesRemoved: string[]; mailWasRunning: boolean; dryRun: boolean; message: string;}// -- darwin implementation ----------------------------------------------------async function rebuildMailIndexDarwin(dryRun: boolean): Promise<RebuildMailIndexResult> { const home = os.homedir(); // Check if Mail is running let mailWasRunning = false; try { const { stdout } = await execAsync("pgrep -x Mail", { timeout: 3_000 }); mailWasRunning = stdout.trim().length > 0; } catch { // pgrep exits 1 if no match — Mail not running mailWasRunning = false; } // Quit Mail if running if (mailWasRunning && !dryRun) { try { await execAsync("osascript -e 'tell application \"Mail\" to quit'", { timeout: 10_000 }); // Brief pause to let Mail finish writing await new Promise((resolve) => setTimeout(resolve, 2000)); } catch { // Non-fatal — continue even if quit fails } } // Find envelope index files — try V10, V9, V8 in order const filesFound: string[] = []; for (const version of ["V10", "V9", "V8"]) { const baseDir = nodePath.join(home, "Library", "Mail", version, "MailData"); const variants = [ nodePath.join(baseDir, "Envelope Index"), nodePath.join(baseDir, "Envelope Index-shm"), nodePath.join(baseDir, "Envelope Index-wal"), ]; for (const f of variants) { try { await fs.access(f); filesFound.push(f); } catch { // File does not exist — skip } } if (filesFound.length > 0) break; // Stop at first version that has files } const filesRemoved: string[] = []; if (!dryRun) { for (const f of filesFound) { try { await fs.unlink(f); filesRemoved.push(f); } catch (err) { // Collect errors but continue } } } const message = dryRun ? filesFound.length > 0 ? `Found ${filesFound.length} envelope index file(s). Run with dryRun=false to remove them and trigger a rebuild.` : "No envelope index files found. Mail index may already be absent or stored in an unexpected location." : filesRemoved.length > 0 ? `Removed ${filesRemoved.length} file(s). Mail will rebuild its index on next launch.` : "No files were removed. Check that Mail is not running and the files exist."; return { filesFound, filesRemoved, mailWasRunning, dryRun, message };}// -- win32 implementation -----------------------------------------------------async function rebuildMailIndexWin32(_dryRun: boolean): Promise<RebuildMailIndexResult> { return { filesFound: [], filesRemoved: [], mailWasRunning: false, dryRun: _dryRun, message: "Apple Mail is macOS only. For Outlook on Windows, use repair_outlook_database.", };}// -- Exported run function ----------------------------------------------------export async function run({ dryRun = true,}: { dryRun?: boolean;} = {}) { const platform = os.platform(); return platform === "win32" ? rebuildMailIndexWin32(dryRun) : rebuildMailIndexDarwin(dryRun);}// -- Smoke test ---------------------------------------------------------------if (false) { run({}) .then(r => console.log(JSON.stringify(r, null, 2))) .catch((err: Error) => { console.error(err.message); process.exit(1); });}