{"id":49385564,"url":"https://github.com/mitigate-dev/mail_mcp","last_synced_at":"2026-04-28T09:04:48.426Z","repository":{"id":354207613,"uuid":"1222320630","full_name":"mitigate-dev/mail_mcp","owner":"mitigate-dev","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-27T15:03:27.000Z","size":84,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T17:06:14.142Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mitigate-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-27T08:49:48.000Z","updated_at":"2026-04-27T15:03:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mitigate-dev/mail_mcp","commit_stats":null,"previous_names":["mitigate-dev/mail_mcp"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mitigate-dev/mail_mcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitigate-dev%2Fmail_mcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitigate-dev%2Fmail_mcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitigate-dev%2Fmail_mcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitigate-dev%2Fmail_mcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mitigate-dev","download_url":"https://codeload.github.com/mitigate-dev/mail_mcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mitigate-dev%2Fmail_mcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32373521,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-27T20:07:02.737Z","status":"online","status_checked_at":"2026-04-28T02:00:07.250Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-28T09:04:44.588Z","updated_at":"2026-04-28T09:04:48.410Z","avatar_url":"https://github.com/mitigate-dev.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mail MCP\n\nA hosted [Model Context Protocol](https://modelcontextprotocol.io/) server for IMAP and SMTP email, built in Ruby. It acts as both an OAuth 2.1 Authorization Server and an MCP Resource Server.\n\n## Architecture\n\n```\nClient (Claude Desktop / MCP Inspector)\n  │\n  ├─ OAuth 2.1 flow\n  │   GET  /.well-known/oauth-protected-resource    RFC 9728 metadata\n  │   GET  /.well-known/oauth-authorization-server  RFC 8414 metadata\n  │   GET  /oauth/authorize                         Login UI\n  │   POST /oauth/authorize                         Validate IMAP/SMTP → issue code\n  │   POST /oauth/token                             Code + PKCE + client_secret → tokens\n  │                                                 (also: refresh_token grant)\n  │\n  └─ MCP calls (all HTTP methods)\n      /mcp   Bearer access token → decrypt creds → IMAP/SMTP operations\n```\n\n### How authentication works\n\nClients are provisioned once via the `bin/mail_mcp generate` CLI, which produces:\n\n- **`client_id`** — a JWE token (encrypted, opaque) encoding the IMAP/SMTP server configuration and the `client_secret`. Only the server can decrypt it.\n- **`client_secret`** — a random secret used to authenticate the client at the token endpoint.\n\nThe OAuth flow:\n\n1. Client redirects the user to `/oauth/authorize?client_id=\u003cjwe\u003e\u0026...`\n2. Server decrypts the `client_id` JWE to learn which IMAP/SMTP servers to connect to\n3. User enters their IMAP/SMTP username and password in the login form\n4. Server validates both connections live; shows an error banner on failure\n5. On success: credentials are encrypted directly in a JWE access token and a JWE refresh token\n6. Client exchanges the authorization code (`POST /oauth/token`) with `client_id` + `client_secret`; receives `access_token` + `refresh_token`\n7. Every MCP request carries `Authorization: Bearer \u003caccess_token\u003e`; the server decrypts credentials per-request\n\n![Login form](assets/form.png)\n\n### Token formats\n\nAll tokens are **5-part JWE** (`dir` / `A256GCM`), encrypted with `ENCRYPTION_KEY`. There is no separate signing key.\n\n| Token         | `typ` claim | Expiry  | Contents                             |\n|---------------|-------------|---------|--------------------------------------|\n| `client_id`   | `client_id` | none    | imap/smtp host+port, `client_secret` |\n| Access token  | `access`    | 8 hours | IMAP + SMTP credentials              |\n| Refresh token | `refresh`   | 30 days | IMAP + SMTP credentials              |\n\n## Directory Structure\n\n```\nmail_mcp/\n├── Gemfile\n├── .env.sample            # Environment variable template\n├── bin/\n│   └── mail_mcp           # CLI: `generate` (client_id) and `server` (puma)\n├── config.ru              # Rack entry point — run MailMCP::App.new\n├── config/\n│   └── puma.rb            # Puma config (single worker, 5 threads)\n├── lib/\n│   ├── mail_mcp.rb        # Module root + requires\n│   └── mail_mcp/\n│       ├── jwt_service.rb         # All JWE tokens (access, refresh, client_id)\n│       ├── pkce.rb                # PKCE S256 challenge/verify\n│       ├── credential_context.rb  # Struct passed as MCP server_context per request\n│       ├── imap_client.rb         # net-imap wrapper\n│       ├── smtp_client.rb         # net-smtp wrapper\n│       ├── attachment_store.rb    # S3 upload + presigned URLs (7 days)\n│       ├── tool.rb                # MailMCP::Tool base class\n│       ├── app.rb                 # Sinatra: OAuth + MCP /mcp route (all methods)\n│       └── tools/*.rb             # MCP tool classes\n├── views/\n│   └── login.erb          # Login form (username + password only)\n├── spec/                  # RSpec test suite\n└── Dockerfile\n```\n\n## Configuration\n\nCopy `.env.sample` to `.env` and fill in the values:\n\n| Variable                | Description                                                         |\n|-------------------------|---------------------------------------------------------------------|\n| `BASE_URL`              | Public URL of this server, e.g. `https://mail.mcp.example.com`      |\n| `ENCRYPTION_KEY`        | AES-256 key (base64-encoded 32 bytes) — used for **all** JWE tokens |\n| `AWS_ACCESS_KEY_ID`     | AWS credentials for S3 attachment storage                           |\n| `AWS_SECRET_ACCESS_KEY` | AWS credentials for S3 attachment storage                           |\n| `AWS_REGION`            | S3 bucket region, e.g. `us-east-1`                                  |\n| `AWS_S3_BUCKET`         | S3 bucket name for attachments                                      |\n| `PORT`                  | HTTP port (default `3000`)                                          |\n| `RACK_ENV`              | `development` or `production`                                       |\n| `MAIL_MCP_LOG_LEVEL`    | Log level (`DEBUG`, `INFO`, `WARN`, `ERROR`, default `INFO`)        |\n\nGenerate `ENCRYPTION_KEY`:\n```bash\nruby -e \"require 'base64','securerandom'; puts Base64.strict_encode64(SecureRandom.bytes(32))\"\n```\n\nIMAP/SMTP host and port are embedded in the `client_id` JWE and are never passed as headers or query parameters.\n\n## Setup\n\n```bash\n# Install dependencies\nbundle install\n\n# Copy and edit environment variables\ncp .env.sample .env\n$EDITOR .env\n\n# Run tests\nbundle exec rspec\n\n# Start the server\nbundle exec puma -C config/puma.rb\n```\n\n## Provisioning a Client\n\nRun `bin/mail_mcp generate` once per mail server configuration. The resulting `client_id` and `client_secret` are configured in the MCP client (e.g. Claude Desktop).\n\n```bash\nbundle exec bin/mail_mcp generate \\\n  --imap-host=imap.gmail.com \\\n  --imap-port=993 \\\n  --smtp-host=smtp.gmail.com \\\n  --smtp-port=465\n\n#   Client ID:     eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0...\u003cencrypted\u003e\n#   Client Secret: 713576e2f94802b9d9abfd755e38e29b63e491df...\n#\n#   IMAP: imap.gmail.com:993 (ssl=true)\n#   SMTP: smtp.gmail.com:465 (ssl=true)\n```\n\n| Flag               | Default              | Description             |\n|--------------------|----------------------|-------------------------|\n| `--imap-host=HOST` | required             | IMAP server hostname    |\n| `--imap-port=PORT` | `993`                | IMAP port               |\n| `--[no-]imap-ssl`  | `true` when port 993 | Enable SSL/TLS for IMAP |\n| `--smtp-host=HOST` | required             | SMTP server hostname    |\n| `--smtp-port=PORT` | `465`                | SMTP port               |\n| `--[no-]smtp-ssl`  | `true` when port 465 | Enable SSL/TLS for SMTP |\n\n## OAuth 2.1 Flow\n\n1. **Discovery** — client fetches `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`\n2. **Authorization** — client redirects user to `/oauth/authorize?client_id=\u003cjwe\u003e\u0026code_challenge=\u003cs256\u003e\u0026...`\n3. **Login** — server decrypts `client_id` JWE to get IMAP/SMTP hosts; user enters credentials; server validates both connections live\n4. **Code exchange** — `POST /oauth/token` with `grant_type=authorization_code`, `code`, `code_verifier`, `client_id`, `client_secret`; server issues `access_token` + `refresh_token`\n5. **Token refresh** — `POST /oauth/token` with `grant_type=refresh_token`, `refresh_token`, `client_id`, `client_secret`; server issues a new `access_token` + `refresh_token`\n6. **MCP calls** — client sends `Authorization: Bearer \u003caccess_token\u003e` on every request; server decrypts credentials per-request via a stateless per-request MCP server\n\n## MCP Tools\n\n| Tool                        | Parameters                                                                          | Description                                                                                      |\n|-----------------------------|-------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|\n| `list_mailboxes`            | —                                                                                   | List all IMAP folders                                                                            |\n| `list_mail_messages`        | `folder`, `page`, `per_page`                                                        | List messages with pagination                                                                    |\n| `get_mail_message`          | `folder`, `uid`                                                                     | Fetch full message; attachments uploaded to S3 and returned as presigned URLs                    |\n| `search_mail_messages`      | `folder`, `query`                                                                   | Raw IMAP SEARCH criteria, e.g. `UNSEEN` or `FROM alice@example.com SINCE 01-Jan-2025`            |\n| `send_mail_message`         | `to`, `subject`, `text_body`, `cc`, `bcc`, `html_body`, `attachment_urls`, `folder` | Send via SMTP and append to the Sent folder via IMAP; attachments fetched from S3 presigned URLs |\n| `create_draft_mail_message` | `to`, `subject`, `text_body`, `cc`, `bcc`, `html_body`, `attachment_urls`, `folder` | Append to Drafts via IMAP APPEND; attachments fetched from S3 presigned URLs                     |\n| `delete_mail_message`       | `folder`, `uid`                                                                     | Mark `\\Deleted` + EXPUNGE                                                                        |\n| `move_mail_message`         | `folder`, `uid`, `destination`                                                      | IMAP MOVE (or COPY+DELETE fallback)                                                              |\n| `update_mail_message_flags` | `folder`, `uid`, `add`, `remove`                                                    | Add/remove IMAP flags, e.g. `\\Seen`, `\\Flagged`                                                  |\n\nAttachments are never returned as binary data — they are uploaded to S3 on first access and returned as presigned URLs valid for 7 days.\n\n## Docker\n\n```bash\ndocker build -t mail_mcp .\ndocker run -p 3000:3000 --env-file .env mail_mcp\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitigate-dev%2Fmail_mcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmitigate-dev%2Fmail_mcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmitigate-dev%2Fmail_mcp/lists"}