mcp-mail-manager
by hamazlabo
README.md
# mcp-mail-manager
AI エージェント(Claude Desktop / claude.ai / Claude Code などの MCP クライアント)から、自分の IMAP / SMTP メールボックスを
検索・閲覧・送信・返信・転送・整理・予約送信できるようにする、AWS 上のリモート MCP サーバです。
メールサーバのパスワードは AWS Secrets Manager にだけ置き、エージェントには渡しません。
開発プロセス(Spec 駆動開発)と CI/CD の管理は [.spec/README.md](.spec/README.md) を参照してください。
## できること
| 分類 | ツール | 内容 |
|------|--------|------|
| 閲覧 | `list_folders` `search_messages` `get_message` | フォルダ一覧、条件検索(フォルダ・差出人・宛先・件名・期間・未読・フラグ)、本文の取得 |
| 送信 | `send_message` `reply_message` `forward_message` | プレーンテキストの新規送信、スレッドを維持した返信、転送。送ったメールは Sent にも残る |
| 整理 | `set_read` `set_flagged` `move_message` `trash_message` | 既読/未読、フラグ、フォルダ移動、ゴミ箱へ移動(完全削除はしない) |
| 予約 | `schedule_message` `list_scheduled_messages` `cancel_scheduled_message` | 指定日時(最大 1 年先)に 1 通を 1 回だけ送る。一覧と取消 |
## 仕組み

図の元データは `docs/architecture.drawio`(SVG にも埋め込んであるので、SVG を draw.io で直接開いて編集できます)。CI/CD は [.spec/README.md](.spec/README.md) を参照。
| 役割 | 実体 |
|------|------|
| MCP サーバ | Amazon Bedrock AgentCore Runtime 上のコンテナ(`src/mcp`)。OAuth 2.1 のトークンを AgentCore が検証する |
| 入口 | CloudFront(`https://<配布ドメイン>/mcp`)。OAuth のディスカバリ(well-known)と認証前チェックを CloudFront Function が担う |
| 認証 | Amazon Cognito User Pool(Managed Login)。利用者は自分(と正常性テスト用の `smoke` ユーザ)だけ、セルフサインアップ無効 |
| 同期 | Lambda が 15 分ごとに IMAP から新着を取り込み、生メッセージを S3、メタ情報を DynamoDB に保存(検索は DynamoDB) |
| 予約送信 | EventBridge Scheduler の一回限りスケジュールで Lambda を起動し、DynamoDB の条件付き更新で二重送信を防ぐ |
| 設定 | Secrets Manager `mail-mcp/<stage>/mail`(IMAP / SMTP の認証情報) |
保持期間: 同期したメールは受信日から 1 年、予約レコードは完了から 90 日で自動削除。常時起動のリソースは持たず、利用がない月のコストは 1 USD 未満です。
## 前提
- Node.js 22、AWS CLI v2(対象アカウントのプロファイルを設定済み)
- AWS アカウント(`ap-northeast-1`)。dev / prod の 2 段構成を想定するが、スタック名は `MailMcp-<stage>` で分かれるので 1 アカウントでも動く(CI/CD の OIDC ロールを同一アカウントに 2 つ作る場合は `.spec/README.md` の注意を参照)
- メールサーバが IMAP(993 / TLS)と SMTP(587 STARTTLS または 465)でパスワード認証できること。さくらのメールボックスで動作確認済み
- Docker はローカルでコンテナを試すときだけ必要(ARM64 イメージはデプロイ時に AWS 側の CodeBuild がビルドする)
## デプロイ
```sh
npm ci
npm run build # dist/mcp/main.js(コンテナが使う)
npx cdk bootstrap aws://<ACCOUNT_ID>/ap-northeast-1 --profile <profile> --context stage=<dev|prod> # アカウントごとに 1 回
npx cdk deploy --all --context stage=<dev|prod> --profile <profile> --outputs-file cdk-outputs.json
```
初回は CodeBuild でのイメージビルドと CloudFront の作成で 10〜15 分かかります。完了すると `cdk-outputs.json` に次の出力が入ります。
| 出力 | 用途 |
|------|------|
| `McpUrl` | Claude に登録する URL(`https://<配布ドメイン>/mcp`) |
| `UserPoolClientId` | Claude に登録する OAuth クライアント ID |
| `UserPoolId` / `CognitoDomain` | 自分のユーザ作成、ログイン画面のドメイン |
| `MailSecretArn` / `TableName` / `BucketName` | 設定と保存先 |
アラーム通知の宛先は `cdk.json` の context `alarmEmail`(`--context alarmEmail=you@example.net` で上書き可)。
## デプロイ後の設定(1 回だけ)
### 1. メールサーバの認証情報を入れる
Secrets Manager `mail-mcp/<stage>/mail` の値を JSON で上書きします。必須は 3 項目です。
```sh
aws secretsmanager put-secret-value --profile <profile> --secret-id mail-mcp/<stage>/mail \
--secret-string '{"domain":"example.net","user":"someone","password":"..."}'
```
| キー | 必須 | 既定値 / 導出 |
|------|------|---------------|
| `domain` | 必須 | メールアドレスのドメイン。ログイン名 `<user>@<domain>` と From アドレスに使う |
| `user` | 必須 | メールアドレスのローカル部 |
| `password` | 必須 | IMAP / SMTP 共通 |
| `imapHost` / `smtpHost` | 任意 | 省略時は `domain` の MX レコードのうち優先度最小のホスト(さくら: `<初期ドメイン>.sakura.ne.jp`)。MX と IMAP ホストが違うプロバイダでは指定する |
| `imapPort` / `smtpPort` / `smtpSecure` | 任意 | `993` / `587` / `false`(STARTTLS)。465 を使うなら `smtpPort: 465, smtpSecure: true` |
| `fromName` | 任意 | From の表示名 |
| `sentFolder` / `trashFolder` | 任意 | `INBOX.Sent` / `INBOX.Trash`。サーバが SPECIAL-USE 属性を持つ場合はそちらを優先 |
15 分以内に同期が始まり、過去 90 日分のメールが検索できるようになります(件数が多いと複数回に分けて取り込みます)。
### 2. 自分の Cognito ユーザを作る
```sh
aws cognito-idp admin-create-user --profile <profile> --user-pool-id <UserPoolId> \
--username developer \
--user-attributes Name=email,Value=<あなたのメールアドレス> Name=email_verified,Value=true \
--desired-delivery-mediums EMAIL
```
- `--username` にメールアドレス形式は使えません(email はエイリアスとして登録され、ログイン時はどちらでも可)。
- Cognito から「Your temporary password」という招待メールが届きます。初回ログインで本パスワードを設定します。仮パスワードは 7 日で失効し、`--message-action RESEND` で再送できます。
- この招待メールは HTML パートしか無いため、HTML を表示しないウェブメールでは本文が空に見えます。「HTML で表示」か「ソースを表示」で読んでください。
### 3. アラーム通知の購読を確認する
初回デプロイ後に "AWS Notification - Subscription Confirmation" というメールが届くので、確認リンクを開きます。予約送信が失敗したときに通知が届くようになります。
## Claude に登録する
Claude Desktop / claude.ai(設定 → コネクタ → カスタムコネクタを追加):
| 項目 | 値 |
|------|----|
| リモート MCP サーバー URL | `McpUrl` |
| OAuth クライアント ID | `UserPoolClientId` |
| OAuth クライアントシークレット | 空欄 |
Claude Code:
```sh
claude mcp add --transport http --client-id <UserPoolClientId> --callback-port 8765 mail <McpUrl>
```
いずれもブラウザで Cognito のログイン画面が開き、完了すると `list_folders` などが使えます。コールバック URL(`https://claude.ai/api/mcp/auth_callback` と `http://localhost:8765/callback`)は登録済みです。
## 使い方
自然言語で頼めば Claude がツールを選びます。例:
- 「受信トレイの最新 5 件を表にして、1 件目を要約して」
- 「A さんからの先週のメールを探して、要点をまとめて返信して」
- 「このメールを contact@example.net に『ご確認ください』と添えて転送して」
- 「件名『請求書』のメールに フラグを付けて、INBOX.Archive に移動して」
- 「明日 9 時に B さんへ『本日の打合せについて』を送るよう予約して」「予約一覧を出して」「取り消して」
知っておくこと:
- 検索・閲覧は 15 分ごとの同期データに対して動きます。届いたばかりのメールや、いま整理したメールの状態は次の同期まで反映されません。`list_folders` の件数は同期時点(`lastSyncAt`)の値です。
- 送信メールはプレーンテキストのみ。受信した HTML メールはテキスト化して返します。添付ファイルはファイル名・種別・サイズだけ分かり、本体の取得・送信は未対応です。
- 削除は「ゴミ箱へ移動」だけです。サーバによっては移動元に削除マーク付きのコピーが残り、メールソフトの整理(expunge)で消えます。
- `move_message` / `trash_message` の後はメッセージの id が変わります。Claude は応答の新しい id を使います。
- 自分宛に送ったメールは INBOX と Sent の両方に別のメッセージとして現れます。
- 予約送信は指定時刻から数分以内に 1 回だけ送られ、`list_scheduled_messages` の状態が `sent` になります。失敗すると合計 3 回まで試行し(初回 + 再試行 2 回)、それでも失敗すると `failed` になって理由が一覧に残り、アラームメールが届きます。送信結果が確認できない場合は再送せず `failed`(`unknown-delivery`)にします。
## 動作確認とテスト
```sh
npm test # ユニットテスト(AWS 不要)
STAGE=dev CDK_OUTPUTS_FILE=cdk-outputs.json AWS_PROFILE=<profile> npm run test:smoke # デプロイ済み環境の正常性テスト(実メールは送らない)
# コンテナをローカルで動かす(ホストのアーキテクチャでビルド)
npm run build && docker build -f docker/Dockerfile -t mail-mcp .
docker run --rm -p 8000:8000 -e STAGE=local -e TABLE_NAME=<TableName> -e BUCKET_NAME=<BucketName> \
-e MAIL_SECRET_ARN=<MailSecretArn> -e SCHEDULE_GROUP=mail-mcp-dev -e SCHEDULER_ROLE_ARN=arn:aws:iam::0:role/x \
-e SCHEDULED_SEND_FUNCTION_ARN=arn:aws:lambda:ap-northeast-1:0:function:x -e AWS_REGION=ap-northeast-1 \
-v ~/.aws:/home/node/.aws:ro -e AWS_PROFILE=<profile> mail-mcp # ツール実行には AWS 資格情報が必要(/ping と tools/list だけなら不要)
npx @modelcontextprotocol/inspector # Streamable HTTP で http://localhost:8000/mcp に接続
```
## コストの目安
想定利用(受信 100 通/日、ツール呼出 200 回/日、予約 10 通/日、同期 15 分間隔)で月額約 2 USD、利用がない月は約 0.9 USD(Secrets Manager、ECR、ログ保持)。内訳は `.spec/design.md` 8 章。
## トラブルシューティング
| 症状 | 確認すること |
|------|--------------|
| Claude でログインは通るのに「認証に失敗しました」 | Cognito の User Pool に `McpUrl` を識別子とするリソースサーバがあるか(CDK が作る。無いとトークン交換が `invalid_grant` になる)。CloudTrail の `Token_POST` に 400 が出る |
| 招待メールの本文が空 | HTML のみのメール。ウェブメールで HTML 表示に切り替える |
| 送ったメールが検索に出ない | 同期は 15 分ごと。`list_folders` の `lastSyncAt` を確認する |
| 予約が `failed` | `list_scheduled_messages` の `error` を見る。`unknown-delivery` は送信結果が確認できなかった状態で、Sent フォルダを確認してから必要なら送り直す |
| 移動先フォルダのエラー | フォルダ名は IMAP の完全名(さくらは `INBOX.spam` のように `INBOX.` 付き) |
ログ(CloudWatch Logs)には本文・認証情報・トークンを出しません。同期 Lambda のログ `sync.reconcile` の `known` と `total` が食い違う場合は、同期データを作り直すと揃います。
## 削除
```sh
npx cdk destroy --context stage=<dev|prod> --profile <profile>
```
prod ではデータ(DynamoDB テーブル、S3 バケット、Cognito User Pool)を保持する設定なので、不要なら手動で削除してください。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues