Apple Mail について
Apple Mailは、macOSのMailアプリに同期済みのメールへアクセスするMCPサーバーです。メールの認証情報やネットワーク通信を使わず、インデックス検索、本文の読み取り、操作を提供し、書き込みツールは既定で登録されません。
主な機能
- 01アカウント、メールボックス、メールの一覧表示
- 02メールと本文の検索
- 03スレッドと添付ファイルの読み取り
- 04メール総数と未読数の集計
- 05既定で書き込み操作を無効化
活用シーン
- 01条件や本文を使ったメール検索
- 02メールスレッドと添付ファイルの確認
- 03メールボックスと未読数の確認
- 04書き込みを有効にするまでメール操作を無効に維持
MCP 対応
MCPクライアントにApple Mailのアカウント、メール、検索、読み取り、操作のツールを公開します。変更ツールは `APPLE_MAIL_ALLOW_WRITES` が有効な場合にのみ登録されます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
npx -y @mgcrea/mcp-apple-mailAPPLE_MAIL_ALLOW_WRITESRegister the mutating tools. Off by default, and the gate decides whether the write tools exist at all rather than merely refusing — an agent with writes off cannot see them.APPLE_MAIL_ACCOUNTSRestrict every read and write to these Mail accounts, by name. Bounds reading, not just writing.APPLE_MAIL_ALLOW_FILE_SENDLet the compose tools attach a local file and send in the same call. Attaching to a draft needs only writes; this is off by default because an immediate send puts the file in someone's inbox with nobody reviewing it.ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
@mgcrea/mcp-apple-mail
macOS の Apple Mail アプリ向け Model Context Protocol サーバーです。Mac にすでに同期されているメールを読み取り、検索し、操作できます。IMAP の認証情報も、OAuth も不要で、メールがマシンの外に送信されることもありません。
非公式です。 Apple とは提携していません。Mac にすでにインストールされている Mail アプリを操作します。
ステータス
検索、一覧表示、件数取得、スレッド表示、本文の読み取り、本文の 検索、およびすべての変更操作が実装されています。
仕組み
Apple Mail には大きく異なる 2 つのインターフェースがあり、このサーバーはそれぞれに適した用途で使い分けます。この分担は好みで決めたものではなく、29,617 件のメールがある実際のメールボックスでの計測結果に基づいています。
| 操作 | Apple Events(AppleScript/JXA) | 判定 |
|---|---|---|
| 4 つのアカウントとすべてのメールボックスを一覧表示 | 0.6 秒 | 問題なし |
| メールボックスの合計/未読件数 | 295 ms / 76 ms | 問題なし |
| 29,000 件のメールボックスで ID から 1 件のメールを特定 | 0.10 秒 | 問題なし |
| N 件のメールをフィールドごとに取得 | 約 130 ms + 1 件あたり 42 ms | 約 50 件まで実用的 |
| ループ内でメールごとに 1 つのプロパティを読み取る | 約 250 ms/件 | 絶対に避けてください |
messages whose read status is false | 74 秒 | 実用不可 |
そのため、検索には Apple Events をまったく使えません。Mail 自身が検索に使っている、Mail 独自の SQLite インデックス(~/Library/Mail/V10/MailData/Envelope Index)を読み取る必要があります。これにより、次の 3 つの処理経路が使われます。
- AppleScript 経路 — アカウント、メールボックス、件数、ID によるメールの取得、およびすべての変更操作に使います。これは 正規の情報源 としても機能します。書き込み後に返される結果は、インデックスが示す内容ではなく、Mail が再読み込みした内容です。
- インデックス経路 — 検索とフィルタリングに使う読み取り専用の SQLite です。フルディスクアクセスが必要です。
- 本文経路 — メール本文と添付ファイルの取得に使う、ディスク上の
.emlxファイルです。フルディスクアクセスが必要で、メールごとの Apple Event にフォールバックします。
インデックスに書き込むことはありません。インデックスは Mail が所有し、開いたまま保持して IMAP と照合します。インデックスに書き込むと、遅れて不具合が表面化する破損につながります。
権限
macOS の権限が 2 つ必要で、それぞれ役割が異なります。どちらも Mail.app に付与するものではありません。権限が必要なのは Mail ではなく、読み取り側 です。
| 権限 | 必要となる機能 | 権限がない場合 |
|---|---|---|
| オートメーション → Mail | すべての機能 | サーバーは何もできず、-1743 エラーが発生します |
| フルディスクアクセス | 検索、メール本文、添付ファイル | サーバーは引き続き動作し、アカウント、メールボックス、件数、上限付き一覧表示はすべて使えます |
オートメーション は、サーバーを起動するアプリ(Terminal、iTerm、VS Code、Claude…)に付与されます。最初の Apple Event の実行時に macOS が許可を求めるので、通常は「許可」をクリックするだけです。
フルディスクアクセス は少し厄介です。
エディターにそのまま権限を付与すべきでない理由
macOS は、ファイルアクセスをプロセスの 責任プロセス 、つまり起動チェーンの最上位にあるアプリに紐付けます。Claude がこのサーバーを起動する場合、チェーンは次のようになります。
launchd → Visual Studio Code.app → Code Helper → claude → node dist/cli.js
そのため、権限は VS Code に付与する必要があります。すると、そのエディターが実行するすべての拡張機能、タスク、ターミナルコマンドにも、ディスク全体への読み取りアクセスが与えられてしまいます。対象には、Messages、Safari の履歴、SSH キー、ほかのアプリのコンテナも含まれます。これは「メールを検索してもよい」という権限よりはるかに広く、まさにその範囲を制限するための APPLE_MAIL_ACCOUNTS の目的を損ないます。
.mcp.json はデータであってコードではないため、そこに権限を付与して回避することはできません。また、node は責任プロセスではなく、マシン上のすべての Node プログラムで共有されるため、node に権限を付与して回避することもできません。
代わりにすべきこと
このパッケージの開発に使われている、署名済みのメニューバーアプリ Cupertino.app をインストールしてください。このアプリがフルディスクアクセスの許可を受け、その配下でこのサーバーを起動するため、アクセス許可はエディターではなく、公証済みのバイナリに付与されます。
brew install --cask mgcrea/tap/cupertino
確認済み:署名済みの .app に付与されたフルディスクアクセスとオートメーションの許可は、そこから起動されたプロセスの2階層下まで引き継がれます。そのため、孫プロセスの node は Envelope Index を読み取ることができ、tccd は孫プロセスの osascript の起動元を、起動したプロセスではなくアプリとして解決します。
このパッケージの以前のバージョンでは、プライベート API を介して自らを自身の責任プロセスにする、小さなコンパイル済みランチャーについて説明していました。現在はビルドも配布もしていません。回避すべきものはもうありません。ただし、問題を最も明確に示している native/launcher.c はリポジトリに残しています。
アプリをインストールしたくない場合は、エディターにフルディスクアクセスを付与しても機能します。付与する前に、上の段落を読んで、その許可が実際に対象とする範囲を確認してください。
許可を付与したら、MCP ホストを再起動してください。apple_mail_diagnostics は、不足しているアクセス許可と、そのために利用できない機能を報告します。
知っておくべき注意点があります。TCC で保護されたファイルに対する stat() は成功します。フルディスクアクセスがなくてもインデックスのサイズと更新日時は確認できますが、読み取りだけが拒否されます。したがって、「ファイルが存在する」ことは、許可が付与されている証拠にはなりません。証拠になるのは access(R_OK) です。
このインストール方法は一時的な回避策です。docs/distribution.md では、今後の方針について説明しています。許可がアップデート後も維持され、コンパイラーを必要としない、署名・公証済みアプリを用意する計画と、App Store での配布ができない理由を記載しています。
クイックスタート
# A. from npm
npx -y @mgcrea/mcp-apple-mail
# B. from source
pnpm install && pnpm build && node dist/cli.js
Claude Code(.mcp.json)または Claude Desktop に設定します。
{
"mcpServers": {
"apple-mail": {
"command": "npx",
"args": ["-y", "@mgcrea/mcp-apple-mail"],
"env": {
// Off by default. With it off the write tools are not registered at all.
"APPLE_MAIL_ALLOW_WRITES": "0",
},
},
},
}
ツールを直接確認します。
npx @modelcontextprotocol/inspector node dist/cli.js
ツール
| ツール | 機能 |
|---|---|
apple_mail_diagnostics | サーバーで現在できることと、その理由を確認します。問題が起きたら、まずこれを呼び出してください。開くべき「システム設定」のパネルが正確に表示されます。 |
apple_mail_list_accounts | UUID、アドレス、メールボックス名を含むアカウント一覧です。まずはこちらを確認してください。 |
apple_mail_list_mailboxes | メールボックスの一覧と、オプションで件数を取得します。インデックスから一括で取得でき、インデックスなしでは1件あたり約0.3秒かかります。 |
apple_mail_list_messages | 1つのメールボックスから、新しい順に N 件を取得します。各メッセージには ref が付与されます。 |
apple_mail_search_messages | 任意のフィルターを組み合わせて検索します。body はメッセージ本文を検索します。本文検索を参照してください。 |
apple_mail_count_messages | 合計数と未読数を、取得元を明示して表示します。 |
apple_mail_list_open_drafts | 現在 Mail で開いている下書きを取得します。各下書きには composer ハンドル、宛先、引用部分とは別の本文が含まれます。アプリの apple_desktop_mail_composers 動詞で読み取ります。 |
すべてのメッセージには、不透明な ref(m1:<accountUuid>/<mailbox>#<id>)が付いており、読み取りツールと操作ツールで使用します。これはバージョン管理され、メールボックスの情報を含むため、行 ID が誤ったメールボックスに適用されることはありません。手動で作成しないでください。
添付ファイル
メールでは、添付ファイルの本文はほとんどの場合、メッセージファイルの外側にあるサイドカーツリーに保存されます。実際のメールストアでは、サンプリングした添付ファイルにインラインのものはありませんでした。そのため、添付ファイルのサイズや取得できるかどうかは、メッセージだけを解析しても判断できません。また、もっともらしい簡略化の方法が2つありますが、どちらも誤りです。
- MIME パートが空でないからといって、バイトデータが存在するとは限りません。ストリップすると区切り文字の空白が1バイト残り、添付ファイルが存在するかのように見えて、サイズも1バイトと判定されます。
X-Apple-Content-Lengthはバイト数ではありません。base64 エンコード後の長さを記録するため、164,156バイトのPDFでは224,634と表示されます。
そのため、list_attachments はメッセージから推測するのではなく、ディスク上のファイルに基づく sizeBytes と、save_attachment が実際に成功するかを示す retrievable フラグを返します。
セキュリティ
影響範囲。 このサーバーはメールアーカイブ全体を読み取ることができ、書き込みを有効にすると、あなたの名義でメールを送信できます。独立した制御が2つあります。
APPLE_MAIL_ALLOW_WRITES(デフォルトではオフ)により、すべての変更操作を制御します。オフの場合、書き込みツールは登録されません。単に拒否されるのではなく、モデルから見えなくなります。APPLE_MAIL_ACCOUNTSにより、表示対象のアカウントを制限します。これは読み取り側の制御であり、ALLOW_WRITESでは制御されません。1か所で適用されるため、どのクエリ経路からも回避できません。
実装後、送信時はデフォルトでレビュー用の下書きを開いた状態にします。実際に送信するには、ALLOW_WRITES と明示的な confirm: true の両方が必要です。
シークレットなし。 サーバーは一切の認証情報を保持しません。アクセスは、ホストアプリに許可した macOS の権限によって行われます。漏えいするものは何もなく、何も外部に送信されません。ネットワーク呼び出しは一切行われません。
添付ファイル。 save_attachment が書き込めるのは APPLE_MAIL_ATTACHMENT_DIR 内だけで、ファイル名は最初にベース名へ変換されます。たとえば、送信者が添付ファイルに ../../../.ssh/authorized_keys という名前を付けても、ダウンロードフォルダーに authorized_keys というファイルが作られるだけで、それ以外のことは起きません。既存のファイルは、明示的に指定しない限り上書きされません。
逆に、send_message、reply_to_message、forward_message では、Mail が読み取って添付する絶対パスを attachments に指定します。デフォルトの下書きでは、送信前にファイルを確認できます。1回の呼び出しで添付と送信を行う操作(sendNow)は、APPLE_MAIL_ALLOW_FILE_SEND が設定されていない限り拒否されます。確認する人がいないため、パスを指定すれば、このサーバーが読み取れるものを何でもメールで外部に送信できてしまうからです。
シェルなし。 このパッケージでプロセスを起動する箇所は1つだけで、exec ではなく execFile を使い、呼び出し元の入力がスクリプトテキストに埋め込まれることはありません。スクリプトは静的な定数で、stdin 経由で osascript に渡されます。すべての値は JSON 引数として渡されます。"; do shell script "touch /tmp/pwned"; // というメールボックス名は構文ではなくデータです。このケースを確認するテストと、${ を含むスクリプトの実行を拒否するトリップワイヤーがあります。
依存関係。 2つです。MCP SDK と zod です。SQLite は Node 24 に組み込まれている node:sqlite を使います。
設定
| 変数 | デフォルト | 説明 |
|---|---|---|
APPLE_MAIL_ALLOW_WRITES | 0 | 変更を伴うツールを登録します。 |
APPLE_MAIL_ACCOUNTS | all | アカウント名または UUID をカンマ区切りで指定します。 |
APPLE_MAIL_DEBUG | off | 詳細なログを標準エラーに出力します。 |
APPLE_MAIL_INDEX_MODE | auto | auto | ro | immutable | off。off にするとインデックス処理を完全に無効にします。 |
APPLE_MAIL_ROOT | auto | Mail のデータルートを上書きします。通常は Mail 自体から検出されます。 |
APPLE_MAIL_ENVELOPE_INDEX | auto | テストやフォレンジックコピー用にインデックスパスを明示的に指定します。 |
APPLE_MAIL_OSASCRIPT_TIMEOUT_MS | 30000 | 初回の権限確認プロンプトで処理がブロックされるため、それに合わせた値です。 |
APPLE_MAIL_DEGRADED_MAX_MESSAGES | 50 | Apple Events の一覧取得処理での上限です。 |
APPLE_MAIL_MAX_RESULTS | 200 | 検索結果の上限です。 |
APPLE_MAIL_BODY_MAX_BYTES | 262144 | コンテキストウィンドウを保護するための本文の切り詰めサイズです。 |
APPLE_MAIL_ATTACHMENT_DIR | ~/Downloads | 添付ファイルの保存先として許可される唯一のディレクトリです。 |
APPLE_MAIL_ALLOW_FILE_SEND | off | 作成ツールがローカルファイルを添付し、同じ呼び出しで送信することを許可します。 |
APPLE_MAIL_ALLOW_WATCHES | off | watch と unwatch を登録します。エージェントが読み取り、ホストが購読できるリソースです。 |
APPLE_MAIL_ALLOW_EVENTS | off | 実験的な mail.received イベント(2026-07-28 時点でドラフトの MCP Events 拡張機能)を提供します。 |
APPLE_MAIL_MAILBOX_CACHE_TTL_MS | 60000 | アカウント/メールボックスのマップをキャッシュする期間です。 |
注意事項
見落とすと問題になる点を、誰も再発見しなくて済むように記録しています。
- Mail の
unread countはまったく間違っていることがあります。 このマシンでは、あるメールボックスのunreadCount = 0と報告された一方で、messages whose read status is falseは 1618 件、Mail 独自のバッジは 37 件を示していました。同じメールボックスで 3 つの数字が食い違っています。これはキャッシュされた値です。そのため、件数は統合せず、取得元とともに報告します。 - Gmail アカウントでは、すべてのメールが
[Gmail]/All Mailに格納されています。 INBOX への所属は ラベル なので、単純なインデックスクエリ(WHERE mailbox = ?)では受信トレイが空になります。メールボックス名は、[Gmail]/プレフィックスを取り除く段階的な処理で解決します。 - ディスク上の構成も入れ子になっていますが、ref からは参照できません。
All Mailは<account>/All Mail.mboxではなく、<account>/[Gmail].mbox/All Mail.mboxにあります。また、Work/Projectsのようなラベルは 2 階層に入れ子になっています。インデックスは完全なパスを把握していますが、上記の段階的な処理では ref を作成する前に末端の名前まで切り詰めるため、ファイルレーンにはAll Mailしか渡されません。そのため、アカウントのルート配下にある*.mboxディレクトリを(Data/には入らず)たどって名前を解決し、末端の名前が曖昧な場合は、対象の rowid を実際に含んでいるコンテナを選びます。ここでフラットな結合を使うと、Gmail アカウントのすべてのメッセージファイル機能が気付かれないまま無効になっていました。 - TCC で保護されたパスに対しても
existsSyncは成功します。 これは stat に基づくため、「そこにあるか」には答えますが、「読み取れるか」には答えません。EPERMを返すのはreaddirSyncとreadFileSyncです。存在するかどうかと読み取り可能かどうかは別の問いです。そのため、apple_mail_diagnosticsは stat を実行するのではなく、実際のメッセージファイルから 1 バイトを読み取ります。また、検索時に「パスが違う」のか「権限がない」のかを区別でき、どちらの場合も Full Disk Access のせいにせずに済みます。 - インデックスを開く方法として
immutable=1は不適切です。 よくある推奨方法ですが、SQLite に-walファイルを無視するよう指示します。Mail は WAL モードで動作するため、読み取り時にまだチェックポイントされていない内容が見えないことがあります。これはエージェントがよく尋ねられる直近のメールそのものです。ただし、これは競合の可能性であり、必ず起きるわけではありません。-walが 1 MB ある状態で、稼働中の 437 MB のインデックスを両方のモードで調べたところ、最新のメッセージがすでにチェックポイントされていたため、MAX(ROWID)は同じ値になりました。staleness が確認されたからではなく、この問題をなくすためにmode=roをデフォルトにしています。 - メッセージを読み取っても Mail は起動しません。 Mail が実行されていない場合、読み取りツールは Mail を起動せずに正常に失敗します。Mail を起動するとフォーカスが奪われ、同期が始まるためです。
本文検索
search_messages は body の語句を受け取り、インデックスが用意されていない唯一のフィルターです。
インデックスの対象となるものがありません。Envelope Index には FTS テーブルがなく、Spotlight のボリュームインデックスは ~/Library 全体を対象外としています。クエリの書き方にかかわらず、mdfind はこのパスの下で何も返しません。これは Spotlight の対象範囲についての事実であり、メールについての事実ではありません。メール本文の検索は、CoreSpotlight への登録情報をもとに、登録元のアプリが CSSearchQuery を通じて実行するもので、ここからは利用できません。計測結果は
docs/mail-body.md にあります。
そのため、インデックスで絞り込み、スキャンでは残ったメールだけを読み込みます。コストは候補数に比例し、181,734 件のメッセージを含むストアでは、1 件あたり 0.48 ms でした。
| 他のフィルター適用後の候補数 | コスト |
|---|---|
| 100 — 絞り込みが厳しい場合 | 48 ms |
| 500 | 242 ms |
| 1,932 — 1 つのメールボックスの過去 90 日間 | 0.9 s |
| 6,566 — すべてのメールボックスの過去 90 日間 | 3.2 s |
| 182,329 — フィルターなし | 88 s、実用不可 |
このため、上限は隠さず明示しています。APPLE_MAIL_BODY_SCAN_MAX(2,000)を超える場合、検索は候補数と上限を示して degraded を返し、スキャンは行いません。最新の N 件で黙って上限を設けると、古いメールが「見つからない」という結果になったとき、それが実際に存在しないのか区別できなくなります。モデルにはその違いを見分ける方法がありません。読み取れる形で拒否すれば、それに応じた対応ができます。
知っておくべき点が 2 つあります。
bodyは絞り込みフィルターと組み合わせてください。mailbox、sender、dateFromなど、どれでもかまいません。候補が数百件なら、本文検索はほぼ瞬時に終わります。- スキャンでは各ファイルの先頭
APPLE_MAIL_BODY_SCAN_BYTES(64 KB)を読み込みます。 実際のストアではバイト数の 79% が、テキスト検索では一致しない base64 です。また、MIME ではテキストパートが添付ファイルより前に置かれるため、誰も必要としない末尾部分を対象外にすることで、候補ごとに発生するコストを抑えています。上限を超えた部分にある語句は見つかりません。レイテンシーよりも検索範囲を優先する場合は、上限を引き上げてください。
開発
pnpm install
pnpm dev # tsdown --watch
pnpm test # vitest, fully offline
pnpm typecheck
pnpm lint && pnpm format:check
pnpm probe # the phase 0 spike (needs Full Disk Access)
テストはオフラインで完結し、外部環境に依存しません。プロセス境界を注入するため、キュー、注入検知機構、エンベロープ処理はすべて、osascript を起動したり Mail にアクセスしたりせずに実行されます。
ライセンス
MIT
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。