MCP Servers

模型上下文协议服务器、框架、SDK 和模板的综合目录。

Self-hosted remote MCP server that lets an AI (Claude, etc.) read, search and send email through multiple IMAP/SMTP accounts

创建于 4/17/2026
更新于 about 4 hours ago
Repository documentation and setup instructions

imap-mcp

Self-hosted remote MCP server that lets an AI (Claude, etc.) read, search and send email through multiple IMAP/SMTP accounts, authenticated via Clerk.

License: MIT Built with Next.js TypeScript MCP

screenshot.png


Why

MCP clients (Claude Desktop, Claude.ai, …) can talk to remote servers, but none of them ship with a way to plug your own IMAP/SMTP accounts securely. Shoving raw credentials into a client config or shipping them to a third-party SaaS is a non-starter for anything serious.

imap-mcp is a tiny, self-hosted Next.js app that:

  • authenticates humans with Clerk (you get a real sign-in UI, MFA, SSO, whatever Clerk supports),
  • authenticates MCP clients with OAuth 2.1 (PKCE + Dynamic Client Registration),
  • stores an arbitrary number of IMAP/SMTP accounts per user, encrypted at rest (AES-256-GCM),
  • exposes those accounts to any MCP client through a clean set of tools.

One container, one domain, your server, your keys.

Features

  • 🔐 Clerk for user auth — you manage users, not us
  • 🔑 OAuth 2.1 (Authorization Code + PKCE) with Dynamic Client Registration (RFC 7591)
  • 📬 Unlimited IMAP/SMTP accounts per user, each with its own HTML signature (Tiptap editor, DOMPurify-sanitized)
  • 🔒 Credentials encrypted with AES-256-GCM; OAuth tokens stored as SHA-256 hashes only
  • 🧰 19 MCP tools across four groups: reading (list_accounts, list_folders, list_messages, get_message, get_thread, search_messages, get_attachment), sending (send_message, reply_message), flags & triage (mark_read, mark_unread, flag_messages, unflag_messages, set_flags), and mailbox ops (move_messages, copy_messages, delete_messages, create_folder, rename_folder, delete_folder)
  • 🧪 Test connection from the list (IMAP NOOP + SMTP VERIFY) with per-account status badges and actionable error hints
  • Provider presets on account creation: Gmail, Outlook / Microsoft 365, iCloud, Yahoo, Fastmail, OVH — pre-fills hosts, ports and SSL flags
  • ⚠️ Live port/SSL consistency warnings — catches the wrong version number trap before it happens
  • 🎯 /connect guide with tabbed, copy-to-clipboard setup instructions for Claude.ai (web), Claude Desktop and Claude Code
  • 🎨 Polished UI: light/dark, hero homepage, status badges, shadows, focus rings
  • 🐳 Ships as a 2-service docker-compose (Postgres + app)

Architecture

┌─────────────────┐   OAuth 2.1 (PKCE + DCR)   ┌──────────────────────────┐
│   MCP client    │ ◀────────────────────────▶ │  /api/oauth/*            │
│ (Claude, …)     │   Bearer-auth'd JSON-RPC   │  /api/mcp   ← tools      │
└─────────────────┘                            │                          │
                                               │   Next.js 15 (App Router)│
┌─────────────────┐   Clerk session            │   /accounts  ← web UI    │
│     Browser     │ ─────────────────────────▶ │                          │
└─────────────────┘                            └──────────────┬───────────┘
                                                              │ Drizzle
                                                        ┌─────▼─────┐
                                                        │ Postgres  │
                                                        └───────────┘

The Next.js app is simultaneously:

  • the OAuth Authorization Server (issues codes and tokens),
  • the OAuth Resource Server (validates Bearer tokens at /api/mcp),
  • the web UI for users to manage their accounts.

Human auth at the /authorize endpoint is delegated to the active Clerk session.

Stack

| Concern | Choice | | ------------ | ------------------------------------------------------- | | Framework | Next.js 15 (App Router), React 19 | | Language | TypeScript (strict) | | Human auth | @clerk/nextjs | | Database | PostgreSQL 16 | | ORM | drizzle-orm | | IMAP client | imapflow | | SMTP client | nodemailer | | MCP SDK | @modelcontextprotocol/sdk | | HTML editor | Tiptap | | Sanitizer | isomorphic-dompurify | | Transport | Streamable HTTP (MCP spec 2025-06-18) |

Getting started

1. Clone & configure

git clone <your-fork> imap-mcp
cd imap-mcp
cp .env.example .env

Fill in .env:

# Generate a fresh 32-byte master key
openssl rand -base64 32

Paste it as MCP_MASTER_KEY. Add your Clerk keys (pk_test_… / sk_test_…) and set NEXT_PUBLIC_APP_URL to the public URL of your deployment (e.g. https://mcp.example.com or http://localhost:3000 for local).

⚠️ Losing MCP_MASTER_KEY means losing every stored IMAP/SMTP password. Back it up.

2. Run with Docker

docker compose up --build
# In another terminal, apply the schema on first install:
docker compose exec app npx drizzle-kit push

App is now available at http://localhost:3000.

3. Add an email account

  1. Open the app, sign up with Clerk.
  2. Go to /accounts/new.
  3. Pick a provider preset (Gmail, Outlook, iCloud, Yahoo, Fastmail, OVH) to auto-fill hosts, ports and SSL flags — or fill them by hand.
  4. Gmail / Google Workspace: use an app password (https://myaccount.google.com/apppasswords).
  5. Optionally paste/edit an HTML signature.
  6. Save, then click Test on the account card. Both IMAP and SMTP must come back green.

4. Connect your MCP client

Every signed-in user has a built-in guide at /connect with tabbed setup instructions and copy-to-clipboard snippets for the three major Claude surfaces:

Claude.ai (web)

  1. Open Settings → Connectors → Add custom connector.
  2. Paste https://<your-domain>/api/mcp.
  3. Sign in with Clerk in the popup, approve.

Claude Desktop

Merge into ~/Library/Application Support/Claude/claude_desktop_config.json (or the equivalent on Windows/Linux):

{
  "mcpServers": {
    "email-mcp": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://<your-domain>/api/mcp"]
    }
  }
}

Restart the app; the OAuth browser flow launches on first use. Requires Node ≥ 18.

Claude Code

claude mcp add --transport http email-mcp https://<your-domain>/api/mcp

Then type /mcp in a session — the first tool call triggers OAuth.

Under the hood

The client will:

  1. GET /.well-known/oauth-protected-resource — discover the auth server,
  2. POST /api/oauth/register — auto-register itself,
  3. open /api/oauth/authorize in a browser — you sign in with Clerk and approve,
  4. POST /api/oauth/token — exchange the code for an access token,
  5. call /api/mcp with Authorization: Bearer ….

All of this is handled transparently by conformant MCP clients.

Troubleshooting

SMTP test fails with tls_validate_record_header:wrong version number

Port/SSL mismatch. The form now warns about this live, and the accounts list surfaces a hint when the test fails. Use one of:

| Port | SSL/TLS checkbox | Meaning | | ---- | ---------------- | -------------------- | | 465 | ✅ on | Implicit TLS | | 587 | ❌ off | STARTTLS upgrade | | 25 | ❌ off | Plain (discouraged) |

First Docker build fails on DATABASE_URL is not set

The builder needs a placeholder at build-time; this repo's Dockerfile already sets a dummy DATABASE_URL and MCP_MASTER_KEY for the build stage, and receives NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY / NEXT_PUBLIC_APP_URL as build args from docker-compose.yml. Make sure your .env defines them before docker compose up --build.

Applying the schema inside Docker

docker compose exec app node node_modules/drizzle-kit/bin.cjs push

(The drizzle-kit push command needs esbuild at runtime to load the TS config. If missing, install it in the container: docker compose exec app npm i esbuild --no-save.)

MCP tools

Reading & searching

| Tool | Purpose | | ----------------- | ----------------------------------------------------------------------- | | list_accounts | List the current user's configured accounts | | list_folders | IMAP LIST — all mailboxes for a given account | | list_messages | Headers of the N most recent messages in a folder (with filters) | | get_message | Full message: headers, text, HTML, attachments metadata (with indexes) | | get_thread | Full conversation around a message; Gmail X-GM-THRID fast-path + RFC 5322 References fallback. cross_folder=true scans every mailbox. | | search_messages | IMAP SEARCH by from, to, subject, body, date ranges, unread | | get_attachment | Return a signed 15-minute HTTPS download URL for an attachment (file never persisted on the MCP server). Images are also previewed inline. inline_blob=true opts into returning the raw base64 as an embedded MCP resource. |

Sending

| Tool | Purpose | | ----------------- | ----------------------------------------------------------------------- | | send_message | Send via the account's SMTP, appending the HTML signature and base64 attachments; the sent copy is IMAP-appended to the Sent folder (skipped on Gmail, which saves it automatically). | | reply_message | Reply preserving In-Reply-To / References; same Sent-folder behavior as send_message. |

Flags & triage

| Tool | Purpose | | ----------------- | ----------------------------------------------------------------------- | | mark_read | Add \Seen to one or more UIDs | | mark_unread | Remove \Seen | | flag_messages | Add \Flagged (the star/favorite) | | unflag_messages | Remove \Flagged | | set_flags | Add and/or remove arbitrary IMAP flags (\Answered, $Important, labels…) |

Mailbox operations

| Tool | Purpose | | ----------------- | ----------------------------------------------------------------------- | | move_messages | Move UIDs from one folder to another | | copy_messages | Copy UIDs to another folder without removing the original | | delete_messages | Move to Trash by default; permanent: true expunges | | create_folder | Create a new IMAP mailbox (supports hierarchical paths) | | rename_folder | Rename or reparent a mailbox | | delete_folder | Delete a mailbox (INBOX is rejected) |

The authenticated user's ID is always injected from the OAuth token — tools never accept it as an argument, so a client cannot impersonate another user.

Local development

npm install
# Start postgres however you want (docker, local, …) and export DATABASE_URL
npm run db:push          # apply schema
npm run dev              # Next.js dev server on :3000
npm run typecheck        # strict TypeScript
npm run build            # production build

Data model

users(id, clerk_user_id UNIQUE)
mail_accounts(id, user_id, label, email,
              imap_{host,port,secure,user,password_enc},
              smtp_{host,port,secure,user,password_enc},
              signature_html, is_default)
oauth_clients(id, client_secret_hash, redirect_uris[], token_endpoint_auth_method)
oauth_auth_codes(code, client_id, user_id, redirect_uri,
                 code_challenge, code_challenge_method, expires_at, consumed_at)
oauth_tokens(id, access_token_hash UNIQUE, refresh_token_hash,
             client_id, user_id, access_expires_at, refresh_expires_at, revoked_at)

Security notes

  • Master key: AES-256-GCM, IV per ciphertext, authenticated. Ciphertext = base64(iv(12) ‖ ct ‖ tag(16)).
  • Passwords are never returned from the REST API — only their encrypted blob is stored.
  • Access tokens are opaque random strings; DB stores only their SHA-256.
  • Refresh tokens rotate on every use (old one is revoked).
  • Signatures pass through DOMPurify server-side before storage and before being injected into outgoing mail.
  • Attachment download URLs are HMAC-SHA256-signed (separate key derived from MCP_MASTER_KEY) and expire in 15 minutes. They encode {userId, accountId, folder, uid, index, exp} — tampering is rejected in constant time, expired tokens are refused. Files are never written to disk on the MCP server; each request streams directly from IMAP and is garbage-collected after the response.
  • The /api/mcp endpoint always returns WWW-Authenticate: Bearer resource_metadata="…" on 401, per RFC 9728.

Out of scope (v1)

  • XOAUTH2 for Gmail / Outlook (password/app-password only for now)
  • IMAP IDLE / push notifications
  • Master-key rotation flow (schema supports it, tool not written yet)
  • Per-user rate limiting on MCP tools

PRs welcome for any of the above.

Contributing

Issues and PRs are welcome. Before opening a PR, please:

  1. npm run typecheck must pass.
  2. npm run build must pass.
  3. Keep the monolith mindset: one Next.js app, one container, boring dependencies.

License

MIT — do whatever you want, no warranty.

快速设置
此服务器的安装指南

安装包 (如果需要)

npx @modelcontextprotocol/server-imap-mcp

Cursor 配置 (mcp.json)

{ "mcpServers": { "cldt-fr-imap-mcp": { "command": "npx", "args": [ "cldt-fr-imap-mcp" ] } } }