email-mcp

email-mcp

A local MCP server for Gmail with strict alias whitelisting, allowing AI clients to search, read, reply to, and send emails only from explicitly whitelisted alias addresses while keeping all other mailbox content unreachable.

Category
访问服务器

README

email-mcp

A local MCP server for Gmail with strict alias whitelisting: the connected AI client can only see, search, reply to, send from, or draft as an explicitly whitelisted set of alias addresses. Every other message in the mailbox is unreachable — including by direct message/thread/attachment ID — and indistinguishable from a message that doesn't exist.

Built for mailboxes that receive mail for many aliases (Google Workspace "Send mail as" addresses) where an AI assistant should be able to handle some of that mail, but must never read the rest.

Security model

The server process holds Gmail OAuth tokens and is trusted; the MCP client (the AI) is untrusted. All enforcement lives in one module, src/gmail/gateway.ts (SecureGmailGateway) — the only code that touches the Gmail API. Three layers:

  1. Server-built queries (Layer 1). Every Gmail search has a whitelist clause (from:/to:/cc:/deliveredto: per alias) ANDed in server-side. There is no raw query passthrough; free text is tokenized and each token quoted, so Gmail search operators (OR, -, {}, deliveredto: …) cannot be injected to escape the constraint.
  2. Post-fetch header verification (Layer 2 — the real guarantee). Before any content is returned, the message's From/To/Cc/Bcc/Delivered-To/X-Original-To headers are parsed with an RFC 5322 parser (email-addresses) and at least one exact addr-spec must match the whitelist. Display-name spoofing ("sales@example.com" <evil@x.com>), lookalike domains, and substring tricks don't match; malformed headers never grant access. Denials return a uniform "not found" — denied and nonexistent are indistinguishable. Threads are filtered per message; hidden counts are never revealed.
  3. Send/draft gating (Layer 3). from_alias must be whitelisted and a registered Gmail send-as alias. User-supplied header values are CRLF-stripped (header-injection defense). Replies verify the parent message via Layer 2 first.

OAuth scopes are minimal: gmail.readonly, gmail.send, gmail.compose. No gmail.modify — the server cannot delete mail or change labels even if asked.

Scope caveat: gmail.readonly technically lets the server process read the whole mailbox — Gmail offers no narrower read scope. The whitelist constrains what crosses the boundary to the AI client, which is the boundary this project enforces.

Tools

Tool Inputs Returns
list_allowed_aliases — Whitelisted aliases; per alias whether it is a registered send-as (can be used as from_alias) and its display name
search_emails free_text, subject, from_address, to_address, alias, after/before (YYYY/MM/DD), has_attachment, unread_only, max_results, page_token Message summaries (headers, snippet, matched_alias) + pagination token
get_message message_id Headers, decoded plain-text body, attachment metadata
get_thread thread_id The thread's whitelist-visible messages (headers + snippet)
get_attachment message_id, attachment_id Base64 content, size-capped (limits.maxAttachmentBytes)
send_email from_alias, to, cc, bcc, subject, body_text, reply_to_message_id Sent message/thread IDs. Replying sets In-Reply-To/References and threads automatically. Disabled when requireDraftOnly is true
create_draft same as send_email Draft ID — for human review/send in Gmail

Deliberately absent: delete, trash, label read/write, raw Gmail query passthrough, history/watch.

Configuration

The config file path comes from the EMAIL_MCP_CONFIG environment variable (default: ./email-mcp.config.json). See config.example.json.

Key Default Meaning
whitelistedAliases (required) Addresses the AI may see and act as. Server refuses to start if empty or malformed
oauth.clientId / oauth.clientSecret (required) Google OAuth Desktop app client credentials
oauth.tokenPath (required) Where OAuth tokens are stored (written with mode 600)
limits.maxSearchResults 25 Hard cap on search page size
limits.maxAttachmentBytes 3000000 Attachment download cap
requireDraftOnly false If true, send_email is not registered at all — the AI can only create drafts you review in Gmail

The whitelist lives only in this file. Nothing in the tool surface can read or change it.

Setup

1. Google Cloud project (one-time)

  1. Create or choose a project at https://console.cloud.google.com and enable the Gmail API.
  2. OAuth consent screen: for a Google Workspace account choose Internal (no verification, stable refresh tokens). Otherwise External and add yourself as a test user (test-mode refresh tokens expire after 7 days until the app is published).
  3. Credentials → Create credentials → OAuth client ID → Desktop app. Copy the client ID and secret. (Desktop clients need no authorized origins or redirect URIs — the loopback redirect is allowed automatically.)

2. Install and configure

git clone <this repo> && cd email-mcp
npm install
npm run build
cp config.example.json email-mcp.config.json
# edit: whitelistedAliases, oauth.clientId, oauth.clientSecret, oauth.tokenPath

3. Authorize (one-time)

node dist/index.js auth

Opens a consent URL; approve as the mailbox owner. Tokens are saved to oauth.tokenPath with mode 600.

Running on a remote/headless machine? The redirect targets 127.0.0.1:<port> on that machine. Either forward the printed port from the machine your browser runs on (ssh -L <port>:127.0.0.1:<port> <host>), or paste the full redirect URL your browser lands on into a local curl "<url>" on the server.

4. Register with an MCP client

.mcp.json (Claude Code) or the mcpServers block of claude_desktop_config.json (Claude Desktop):

{
  "mcpServers": {
    "email": {
      "command": "node",
      "args": ["/absolute/path/to/email-mcp/dist/index.js"],
      "env": { "EMAIL_MCP_CONFIG": "/absolute/path/to/email-mcp/email-mcp.config.json" }
    }
  }
}

Testing

npm test        # unit + adversarial suites (see below)
npx @modelcontextprotocol/inspector node dist/index.js   # interactive smoke test

The adversarial suites cover: Gmail query injection (plus a fuzz pass), header spoofing (display names, lookalike domains, group syntax, malformed headers), direct ID-fetch denial with proof that no content fetch occurred, mixed-thread filtering, send/draft gating, and CRLF header injection.

Recommended manual red-team pass after connecting a real account: take the ID of a message you know is outside the whitelist and ask the AI to fetch it (expect "Message not found"); try query injection through free_text; try send_email with a non-whitelisted from_alias.

Project layout

src/
├── index.ts                  # entry: `auth` subcommand vs stdio server
├── server.ts                 # MCP tool registration (the exposed surface)
├── config.ts                 # zod-validated config loading
├── auth/                     # OAuth installed-app flow + 600-perm token store
├── gmail/
│   ├── gateway.ts            # SecureGmailGateway — sole security chokepoint
│   ├── render.ts             # Gmail payload → text body / attachments
│   └── mime.ts               # RFC 822 builder (CRLF-safe)
└── security/
    ├── whitelist.ts          # normalized exact-match set
    ├── queryBuilder.ts       # Layer 1
    └── headerVerifier.ts     # Layer 2

Known limitations

  • With many aliases (> ~15) the search clause gets long; if Gmail rejects it, chunk searches per alias group (Layer 2 already guarantees safety).
  • Mail delivered via groups/forwarding may not be findable through deliveredto: search, though it still passes Layer 2 on direct/thread fetch via X-Original-To.
  • Whitelisted receive-only addresses (not registered send-as) can't send; list_allowed_aliases shows which can.
  • The AI can compose arbitrary outbound text from whitelisted aliases — your MCP client's per-tool-call approval prompt is the human gate, or set requireDraftOnly: true.
  • The token file is protected by file permissions only; any process running as the same OS user could read it.
  • Whitelist matching is exact ASCII; internationalized addresses are not punycode-folded.

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选