只读MCP服务器:把邮件变成AI可查询的知识库
前几天我刷到一个很有意思的 Side Project作者说自己把所有的邮件账户都拉进了一个只读的 MCP 服务器里而且还能在手机上用。看起来一句话就能讲完但这里面至少藏着三个关键决定为什么是“所有账户”为什么强调“只读”以及为什么特意提到“手机”。这位作者不是做了一款邮件客户端而是重新思考了“邮件数据应该以什么方式被 AI 使用”。这个项目真正有价值的地方不是邮箱聚合而是把一堆散落在不同服务商里的私有邮件变成了一套 AI 可以通过标准协议查询的只读知识库。只读这两个字既是安全底线也是这个项目能够长期存在的前提。围绕着这个项目我想拆开讲几件事只读到底意味着什么MCP 服务器该怎么设计手机访问的坑在哪里以及如果你也想做一个类似的只读数据接入服务应该从哪一步开始。1. 为什么“只读”是这个项目的灵魂1.1 邮件数据适合被 AI 读取但不适合被 AI 操作邮件是我个人数据里最典型的一种存量资产。里面既有工作往来、订单凭证、账单通知也有大量历史归档。AI 非常擅长做摘要、搜索、时间线梳理、关键信息抽取这些事。比如“帮我找一下上个月那笔云服务的续费金额”“这封邮件里的附件是什么主题”这些都是只读操作。但写操作就是另一回事了。发送一封错误邮件通常比发错一条聊天消息严重得多删除一封重要归档可能需要联系服务商才能恢复把邮件移动到错误文件夹也可能让后续检索变得混乱。AI 再聪明也无法保证在写操作上永远不出错。把“读”和“写”分开是这类个人数据接入最理性的分界。这个项目的标题里特意写了 read-only说明作者很清楚地知道读邮件是刚需写邮件则是一个高风险的动作。先做只读不是不完整而是为了把一件事做稳。1.2 从“只读”的工程约束看为什么不能只靠自觉如果你写过业务系统可能见过类似 MyBatis 的报错write operations are not allowed in read-only mode (flushmode.manual)。这个报错的背后是一套强约束连接被明确标记为只读代码层不允许执行 insert、update、delete。这套机制比“代码规范里写着不要写”要可靠得多。邮件接入也应该这样。如果你只是在一个普通 IMAP 连接上约定“大家不要调用 STORE 命令”那么某个模块一旦不小心用错了接口邮箱状态可能就被改掉了。更稳妥的做法是在协议层面使用只读 IMAP 会话未读消息用BODY.PEEK获取不更新已读标志在 API 层面申请最小权限范围比如 Gmail 只申请gmail.readonlyMicrosoft Graph 只申请Mail.Read在账号层面使用一个专用账号或专用应用凭据连接邮箱而不是使用你日常登录的账号。只读不是一句声明而是要写进代码、写进权限、写进测试里的约束。1.3 只读边界保护的不只是用户还有 AI 和开发者很多人只想到只读保护用户数据其实它也保护了 AI 和开发者本身。对 AI 来说工具越少、权限越清晰模型做决策的难度就越低。如果 MCP 服务器里同时有“读取邮件”和“发送邮件”工具模型在不确定时可能会倾向于调用“发送”这是很危险的。只有只读工具时模型的所有行为都收敛到“查询和推理”这个安全范围内。对开发者来说只读服务器不用承担误操作的投诉责任。你的服务只是帮用户查找和总结不用处理“为什么我的邮件被删了”这种问题。维护成本会低很多。所以“read-only”不是这个项目的功能限制而是设计哲学。一个个人数据接入服务第一原则不是“能做什么”而是“允许做什么”。2. 从聚合邮箱到模型可查询的知识库MCP 服务器的关键设计2.1 工具层把“读邮件”拆成原子能力MCPModel Context Protocol的核心思路是给 AI 提供一组标准工具和资源。对邮件聚合场景我理解作者的 MCP 服务器至少需要暴露这些工具list_mailboxes(account_id)列出某个账户下的文件夹list_emails(account_id, folder, limit, offset, since, unread_only)获取邮件列表search_emails(account_id, query)按关键词搜索邮件get_email(account_id, folder, uid)读取一封邮件的完整内容。这些工具要尽量原子化不要把一个复杂功能揉在一起。参数设计需要明确account_id用来区分多个邮箱账户folder要兼容不同服务商的不同命名limit和offset用来控制返回数量。这里有一个容易被忽略的点只读工具不应该主动修改邮件的“已读”状态。标准 IMAP 读邮件时如果使用BODY[]有些服务端会把邮件标记为已读。正确做法是用BODY.PEEK[]它只获取内容不改变标志位。否则 AI 只是读了一封邮件用户却看到所有邮件都变成了已读状态。2.2 认证层多账户聚合的难点是授权而不是连接多邮箱聚合很多人以为难点在“连上多个服务商”其实邮件协议已经足够成熟真正麻烦的是授权体系。现在的邮件服务商基本都在从密码认证转向 OAuth 2.0。Gmail 支持只读范围的 OAuth 权限Microsoft Graph 也支持只读邮件权限。每个账户需要单独的 client_id、refresh_token有些还需要用户手动完成一次授权流程。对 MCP 服务器来说不能让模型直接接触 token而是要把 token 的生命周期管理放在服务器内部。刷新逻辑要提前写好因为很多服务商的 refresh token 有过期时间长期不活跃的账户也可能被吊销。多账户还有一个细节每个账户返回的数据格式可能不一样。Gmail 的标签体系、Outlook 的文件夹体系、IMAP 的文件夹列表命名习惯差异很大。建议在拿到原始数据后统一转换成你自己的邮件模型再暴露给 MCP 工具。这样 AI 不需要理解每个服务商的差异。2.3 资源层让模型理解一封邮件的上下文MCP 除了 tools还有 resources 的概念。你可以把一封邮件暴露成一个 URI比如email://gmail/12345模型可以在上下文里引用它。这样 AI 在回答“刚才那封邮件里提到的合同编号是多少”时可以先读取邮件资源再给出答案。不过邮件正文通常是 HTML里面还有内嵌图片、签名、回复链。直接把原始 HTML 丢给模型会浪费大量 token而且干扰信息很多。建议做一层文本抽取把 HTML 转成纯文本去掉多于的签名和回复片段只保留发件人、收件人、时间、主题、正文关键部分。还需要考虑 token 上限。如果一封邮件特别长最好先做截断或摘要而不是让模型全量读取。但截断要保留关键信息正文前半部分通常是重点附件列表要单独生成。3. 手机能访问的真正门槛不是 MCP是暴露方式与认证3.1 移动端 MCP 客户端的现状MCP 是一个协议不是说你在手机上装一个 App就能自动连接所有 MCP 服务器。目前支持 MCP 的桌面端应用比较多移动端应用相对保守。作者说“usable from my phone”可能意味着它做了一个简单的移动端 Web 页面也可能只是让支持 MCP 的手机客户端能通过远程 URL 连接。如果你想复刻这个模式第一步不是写工具而是确认你的目标客户端是否支持远程 MCP over HTTP/SSE。如果只支持本地进程那你就需要一个移动端网关或者换一个支持远程 MCP 的客户端。移动端还有一个特性网络不稳定。手机可能在 Wi-Fi 和 5G 之间切换也可能在电梯里断网。MCP 服务器端要设计好超时和重试策略客户端也要处理连接中断后自动恢复否则一次查询可能因为网络抖动就失败。3.2 如何把本地邮件服务安全暴露给手机这里的安全问题比功能问题更重要。因为你暴露的不只是一个“发消息”的服务而是你所有邮件的数据入口。如果接口被攻击者拿到邮件隐私就完全泄露了。稳妥的做法有几种部署到一台带公网 IP 的服务器通过 Nginx 反向代理加 HTTPS 和访问认证在局域网内部署通过路由器端口映射并限制来源 IP如果你不想买服务器也有一些托管远程访问服务可以帮你把本地端口安全暴露到公网。无论哪种方案都要注意不要裸 HTTP 暴露不要使用弱密码不要把所有账户的 token 都放在同一个容易被读取的配置文件里。建议使用足够长的随机 token 做请求头认证同时加 IP 白名单限制来源。如果要更严格可以启用双向 TLS。3.3 交互模式直接连服务器 vs 通过移动网关在移动端使用 MCP通常有两条路。第一条路是手机上的 AI 客户端直接连接远程 MCP 服务器。这种做法最简单只要服务端支持远程传输客户端能填 MCP server URL 就行。但问题在于很多移动 AI 应用并不支持任意 MCP 远程连接而且它们对工具返回数据的展示方式也可能不理想。第二条路是做一个极简的移动 Web 网关。你不需要在手机上运行 MCP 客户端而是用一个普通浏览器访问自己的服务。后端服务负责调用 MCP 服务器再把结果渲染成适合手机端阅读的界面。这样做的好处是稳定、可控不依赖某个特定 App 是否支持 MCP代价是多写一个 Web 层。从工程经验看如果你只是想“在手机上查邮件摘要”第二条路更快落地。如果你希望手机上的 AI 助手能够自由调用工具第一条路才是理想的终局。4. 从单机服务器到长期可用哪些坑会在上线后出现4.1 OAuth 令牌过期与刷新策略多邮件账户接入后最常见的问题就是某个账户的 token 过期了工具调用直接报token exchange failed或权限错误。很多服务商会定期吊销不活跃的 refresh token如果你的服务器只是偶尔被查询很容易踩到。建议把 refresh token 的刷新逻辑做成后台任务定期检查 token 是否接近过期。同时在 MCP 工具返回错误时要能区分“认证失败”和“邮件不存在”不然排查问题会很痛苦。如果使用纯 IMAP 密码认证虽然绕开了 OAuth但安全性弱而且服务商很容易限制第三方登录。4.2 IMAP 连接数、限流与缓存多个邮件账户意味着多个 IMAP 连接。有些服务商会对单账号的并发连接数做限制如果 MCP 服务器同时处理多个查询可能会被临时拒绝。这里的常见策略是为每个账户维护一个连接池限制并发数高频查询使用本地缓存避免每次都实时拉取邮件搜索历史邮件时使用 SQLite 或类似轻量数据库建立索引而不是每次去服务商那边做全量在线搜索。在线搜索看起来很直接但多个账户、多年邮件搜索一次可能耗时几十秒。如果服务器长期运行建议把邮件元数据同步到本地再在本地做检索。只读权限足够支撑这种同步而且不会影响线上邮箱状态。4.3 只读边界为何会被绕过以及如何兜底只读模式看起来简单但在真实的 IMAP 请求里一个不小心就可能破了边界。举个例子如果你使用 IMAP 的FETCH命令并请求BODY[]部分服务端会认为你读了邮件然后更新已读标志。正确的方式是总是请求BODY.PEEK[]。另外如果你申请了 Gmail API 的gmail.modify权限你的代码里虽然只写了 list 和 get但权限已经违反了最小化原则。兜底方案有几个给每个邮件服务商使用独立的连接配置避免共享默认账号在测试环境里用测试邮箱执行一遍工具调用然后检查邮件状态标志是否发生变化写自动化测试断言 MCP 服务器返回的每封邮件 UID 对应的已读标志与调用前一致检查服务器日志确认没有任何发往邮件服务商写接口的请求。只读不是配置上的一个选项而是需要持续验证的工程属性。5. 如果你也想做一个类似的只读 MCP 服务器建议的落地路径5.1 先跑通一个账户再抽象多账户不要一开始就设计多账户。找一个 Gmail 测试账号申请只读权限写一个最简单的 MCP tool返回最近十条邮件。先用桌面端客户端调用确认工具能返回结构化数据再考虑增加其他账户。多账户的抽象放在第二步。统一接口模型把account_id作为所有工具的必要参数。每一步都先跑通一条链路再并行扩展。5.2 把“只读”写进实现与文档在代码层面使用只读 IMAP 会话或只读 API 客户端。在 MCP 工具描述里明确写清楚每个工具的行为尤其是“不会修改邮件状态”这句话。这不仅是给用户看的也是给模型看的。模型能够理解工具的描述从而放心调用。同时把权限范围写进 README例如“这个服务器只读取不发送不删除不修改”。如果你开放给别人使用一定要让使用者知道它的边界在哪。5.3 手机接入从最小可用开始手机端不要追求复杂。先用一个支持远程 MCP 的桌面客户端验证然后再考虑手机。手机端测试时先确认网络连通性再看认证是否通过最后看工具返回是否能正常展示。建议准备一个健康检查接口返回“服务器在线、账户数量、token 是否有效”这些信息方便排查。5.4 一个可复用的排查链路如果你遇到“手机连不上 MCP 服务器”的问题按这个顺序排查连通性手机到服务器的网络是否通畅防火墙是否放行端口认证token 是否有效时间是否同步证书链是否完整客户端兼容性手机上的 MCP 客户端是否支持远程 HTTP/SSE 传输工具返回调用一个简单工具确认返回的 JSON 能被客户端解析日志查看 MCP 服务器日志看请求是否到达、处理是否报错。如果你遇到“某个账户查询失败”的问题顺序应该是单独用邮件服务商的 API 测试工具拉取一次该账户的邮件确认服务商侧正常检查该账户的 token 是否过期检查是否有 IMAP 连接数限制检查账户文件夹名是否正确比如INBOX、Archive、Sent最后再回头看 MCP 工具的参数映射是否写错。6. 这个项目带来的启发MCP 如何改变个人数据的使用方式这个项目看起来只是一个小工具但它示范了一种很重要的思考方式个人数据接入 AI 时不是先做更多功能而是先把边界定住。越来越多的人开始把日历、邮件、笔记、代码仓库通过 MCP 暴露给 AI。如果每次连接都是松散的、没有标准的AI 永远学不会怎么安全地使用这些数据。而 MCP 提供了一个统一协议让 AI 可以标准化地发现工具、调用工具、读取资源。再配合“只读”这种权限边界个人数据才能真正放心地交到 AI 手里。邮件只是起点。日历、通讯录、网盘、支付账单都可以采用同样的思路先只读再考虑是否需要写先单账户再抽象多账户先局域网再考虑安全暴露到手机端。如果你也想做一个类似的项目我建议你从最小可用流程开始一个测试邮箱一个只读工具一次桌面端调用。跑通这条链路之后再去加账户、加搜索、加手机端。至于“只读”这两个字应该写进第一行代码而不是最后一篇文档里。下次你刷到类似的项目不妨先问一句它的只读模式是真的吗我能不能信任它答案如果都是肯定的那它就值得继续看下去。