只读MCP Server:AI安全边界的工程实践与设计解析
最近在 Hacker News 上有一个项目很值得开发者注意:Show HN: All my mail accounts in one read-only MCP server, usable from my phone。作者把自己的所有邮箱账号汇聚到一个只读的 MCP Server 中,然后用手机上的 AI 客户端随时跨账号查邮件。这个项目表面看是一次个人工具分享,但仔细拆开,它触及了 MCP 实践里最容易被忽略的问题:AI 工具的安全边界应该怎么划。
如果你写过 Java 后端,大概率见过一个经典报错:write operations are not allowed in read-only mode。这个错误的意思很直白:底层连接已经被声明为只读,事务管理器会在更深层拦截写操作。今天聊的这个 MCP 项目,与它背后是同一个思想,只不过把“只读”从数据库连接层,搬到了 AI 工具的 API 层。
我对这个项目的判断是:它有明确的实用价值,也有很好的示范意义。真正值得学习的不是“怎么连邮箱”,而是它如何通过只读接口设计,让 AI 能读邮件、会搜邮件、能总结邮件,却没有任何能力去发送、删除或修改邮件。这个很克制的设计,同时解决了检索效率和安全隐患两个问题。
这篇文章会从四个层面展开:先讲 MCP 在当前 AI 应用中的定位;再拆解只读 MCP Server 的设计边界;然后给出一个可复用的 Python 实现;最后讨论手机端访问时的工程落地和常见坑。全文以可操作为目标,代码可以直接改成自己的邮箱配置跑起来。
1. 为什么要关注这个项目:邮箱接入 AI 的两种姿势
先问一个问题:你的邮箱里有多少账号?
很多人不止一个。工作邮箱、私人邮箱、项目通知邮箱、各种订阅列表。搜索一封几个月前的邮件时,你需要分别登录不同客户端,重复输入关键词,来回切换。这是邮件管理最原始、最烦躁的痛点。
MCP 出现后,这个问题有了新解法。MCP,也就是 Model Context Protocol(模型上下文协议),是 Anthropic 在 2024 年底开源的一套协议。它定义了 AI 助手(Host)如何通过 MCP Client 去调用 MCP Server 暴露的能力。放在邮箱场景里,AI 助手可以调用“搜索邮件”“读取邮件”这两个工具,然后告诉你“上周三的报销单在 work 账号的收件箱里”,甚至帮你把三封相关邮件的关键信息汇总成一段摘要。
这个场景听起来很诱人,但有一个现实隐患:如果 AI 同时具备“发送邮件”的能力,一次幻觉、一次误触,就可能把草稿发给错误的人,或者把内部邮件转发出去。这是邮箱接入 AI 时最恐怖的失败模式。
于是就有了两种设计姿势。第一种是让 AI 拥有完整读写权限,优点是能力完整,缺点是出事的代价可能非常严重。第二种就是本文主角的做法:只暴露读取和搜索类工具,发送、删除、移动、改状态这些操作一律不提供,让 AI 在安全边界内把“读”这件事做到极致。
从工程角度看,第二种姿势显然更适合邮件这类高隐私、高影响数据。它看起来像是对 AI 能力的一种限制,实际上是对确定性的一种保护。这个项目的标题特意强调了 read-only,说明作者把这个边界当成核心特性,而不是简单省略。
2. MCP 与只读模式:先搞清楚协议本身
要理解这个项目,得先理解 MCP 的基本结构。MCP 的整体架构是三层:Host(宿主应用,也就是 AI 聊天客户端)、Client(宿主应用内部与 Server 通信的组件)、Server(提供工具和资源的服务)。
以邮箱场景为例:你在手机或电脑上打开一个支持 MCP 的 AI 客户端,这个客户端就是 Host。客户端内置了 MCP Client 模块,负责与 MCP Server 建立连接。MCP Server 是你的邮箱聚合服务,负责与 IMAP 邮件服务器通信,把搜索结果、邮件正文等数据返回给客户端。最终,AI 模型只看到工具名称、入参出参和执行结果,它不关心底层 IMAP 协议怎么工作。
MCP Server 对外暴露三种能力:Tools、Resources、Prompts。Tools 是可被 AI 调用的函数,类似函数调用的概念;Resources 是可以暴露给模型的文件和结构化数据;Prompts 是预设的提示词模板。在本文项目里,重点是 Tools。
Tools 的定义需要声明名称、描述和参数结构。当 AI 判断用户需要搜索某封邮件时,它会根据工具描述来构造调用参数。这里的关键是:AI 只能调用 Server 注册过的工具,如果 Server 根本不注册“发送邮件”这个工具,AI 再聪明也没有通道去做这件事。
下面用一个表对比只读和全权限设计的差异:
| 维度 | 只读 MCP Server | 全权限 MCP Server |
|---|---|---|
| 暴露工具 | 搜邮件、读邮件、列账号 | 搜邮件、读邮件、发邮件、删邮件 |
| 数据风险 | AI 只能读取,无法修改 | 幻觉可能导致误发、误删 |
| 用户信任 | 容易接受 | 需要较强信任背书 |
| 实现复杂度 | 低,IMAP 只读会话即可 | 高,需要事务、确认、审计 |
| 适合阶段 | 个人使用、初期产品 | 企业流程完善后 |
对个人开发者来说,先做只读版本是最理性的选择:代码量小、风险低、能快速验证 AI 邮箱助手的产品价值。等真的需要“让 AI 帮你写邮件”时,再单独设计发送工具、加入确认机制,也不会太迟。
3. 只读边界:不止是一个标签,而是三层约束
这个项目最值得研究的是“只读”到底怎么落地。只看工具命名不叫只读,真正可靠的是在多个层面同时实施限制。
第一层是工具注册层。MCP Server 里根本不注册发送、删除、移动类的工具,AI 就不会产生调用它们的意图。这是最基本的约束,相当于让这些可选项从 API 上消失。
第二层是 IMAP 会话层。IMAP 协议提供了两种打开文件夹的方式:SELECT 以读写模式打开,EXAMINE 以只读模式打开。本项目应该使用 EXAMINE,这样即使 MCP 工具代码里误调用了设置标志位的方法,服务器端也会拒绝或忽略。
第三层是数据读取方式。即使只读取邮件正文,也有读写差异:使用 BODY[] 抓取会隐式设置 \Seen 标志,也就是把邮件标记为已读;使用 BODY.PEEK[] 抓取则不会改变任何状态。在只读设计里,所有读取都应该使用 BODY.PEEK。
这三层约束的关系很像数据库事务的只读模式。你在 Spring 里配置了 readOnly=true 的事务,MyBatis 执行写操作时会抛出 write operations are not allowed in read-only mode 之类的错误。工具注册层相当于 SQL 层面的权限控制,IMAP 会话层相当于事务管理器,而 BODY.PEEK 则相当于查询语句本身避免了副作用。每多一层约束,意外写操作的概率就低一截。
有一个容易被忽视的细节:标记已读算不算写操作?从 IMAP 协议角度看,它当然算,因为修改 \Seen 标志位就是状态变更。因此,严格只读的邮件 MCP Server 不应该暴露“标记已读”工具。如果一个 AI 客户端尝试执行类似操作,接口应该直接返回“不支持”,而不是悄悄降级后执行。
4. 环境准备与前置条件
在写代码之前,先确认环境。以下以 Python 为例,这套逻辑同样可以用 TypeScript、Go 或 Java 实现,语言不是关键。
- Python 3.10 或更高版本,具体版本以实际环境为准。
- MCP Python SDK,使用 pip 安装。
- 一个支持 IMAP 的邮箱账号,并确认服务商已开启 IMAP 服务。
- 邮箱服务商提供的应用专用密码,不要使用主登录密码。
- 一台可以运行 Python 进程的设备,本地电脑或小服务器均可。
- 一个支持连接 MCP Server 的 AI 客户端,手机或桌面端均可。
安装 MCP SDK 的命令:
pip install mcp如果使用 uv 等包管理器,也可以按对应方式安装。安装后可以通过以下命令确认 SDK 可用,但不同版本 API 存在差异,后续代码以通用模式为准:
python -c "import mcp; print(mcp.__version__)"这里要特别提醒:邮箱密码不要直接写进代码。大部分邮箱服务商都支持开启 IMAP 后生成应用专用密码,这类密码一般只对特定协议有效,即使泄露也相对容易撤销。后面的示例代码会使用环境变量来读取密码。
5. 核心流程拆解
把整个项目拆开,核心流程有五步。
第一步是配置邮箱服务。你需要确定每个邮箱的 IMAP 服务器地址和端口,一般是 imap.服务商域名 和 993 端口,使用 SSL 加密。同时创建一个配置文件,记录邮箱名称、IMAP 主机、用户名、对应环境变量名,但不记录真实密码。
第二步是初始化 MCP Server。用 FastMCP 创建一个单例实例,给服务命名,比如 mail-reader。这个实例负责工具注册和通信协议处理。
第三步是实现只读工具。这一步是核心。至少需要三个基础工具:list_accounts 列出账号、search_mails 按条件搜索邮件、read_mail 读取某封邮件内容。每个工具只做读取操作,内部全部使用 EXAMINE 和 BODY.PEEK。
第四步是启动服务。本地开发可以用 stdio 模式,MCP Client 通过标准输入输出与 Server 通信,适合调试。如果要从手机访问,则需要使用 streamable-http 模式监听网络端口,让手机上的客户端通过网络连接。
第五步是客户端注册。在支持 MCP 的 AI 客户端里配置 Server 地址。本地 stdio 模式配置为启动命令,远程 HTTP 模式配置为 URL。配置完成后,AI 客户端就能发现并调用邮箱工具了。
这五步里最容易踩坑的是第二步和第四步的传输模式。FastMCP 默认的传输方式是 stdio,适合本地脚本。手机访问场景必须显式切换到 HTTP 传输,
