让AI Agent只读更安全:ms-365-mcp-server最小权限与安全配置清单
让AI Agent只读更安全:ms-365-mcp-server最小权限与安全配置清单
ms-365-mcp-server 是一个面向 Microsoft 365 与 Microsoft Office 服务的 MCP server,它通过 Microsoft Graph API 让 AI Agent 直接操作邮件、日历、OneDrive 文件、Teams 等 300+ 个工具。工具越多,权限面越大——一旦交给 LLM,"能读就能改"的默认配置风险不小。本文给出一份可直接照做的最小权限与安全配置清单,从开启只读模式、收窄工具面,到令牌存储与审计日志,帮助你把 AI Agent 的权限压到最小。
为什么 AI Agent 需要"最小权限"
该项目把每个 Graph API 端点都声明式地映射为一个工具(定义在 src/endpoints.json),默认暴露了邮件、日历、文件、笔记、待办等个人工具,加上 --org-mode 后还会暴露 Teams、SharePoint 等组织工具。
对 AI Agent 来说,这带来两个典型风险:
- 📧 误操作:模型可能误发一封邮件、误删一个日历事件;
- 🔓 越权访问:工具面越宽,Agent 能碰到的敏感数据(他人邮箱、内部文件)越多。
好消息是,ms-365-mcp-server 内置了完整的权限收敛机制,全部通过 CLI 参数或环境变量开启。下面的清单按"由粗到细"排序。
1️⃣ 开启只读模式:一行参数禁用所有写操作
最直接的一道闸门是 --read-only(或环境变量 READ_ONLY=true)。开启后,服务端只注册 GET 类端点,所有 POST / PATCH / PUT / DELETE 写操作都会被剔除:
npx @softeria/ms-365-mcp-server --read-only
这里有一个容易忽略的细节:少数以 POST 提交的"查询"端点(如 get-schedule、find-meeting-times)在 src/endpoints.json 中标记了 readOnly: true,只读模式下会被放行,因为它们本质上是查询而非修改。判定逻辑见 src/lib/destructive-ops.ts,行为测试见 test/read-only.test.ts。
💡 经验法则:只读是第一优先级。绝大多数"让 AI 帮我查资料"的场景,只读模式就足够了。
2️⃣ 用 Preset 预设收窄工具面
即使只读,加载全部 300+ 工具也会让初始上下文很大。--preset 可以按应用选择工具集合:
# 只看 Outlook(邮件 + 日历 + 联系人,不含共享邮箱、文件)
npx @softeria/ms-365-mcp-server --preset outlook
常用预设:mail、calendar、files、personal、work、excel、contacts、tasks、onenote、search、users、outlook、onedrive、teams、teams-write、all,可用 --list-presets 查看全部。
预设是精确的工具名白名单(每个端点在 endpoints.json 中通过 presets 数组声明归属),不会跨应用过度匹配——比如 mail 预设不包含共享邮箱工具(那些属于 work)。其中 teams-write 是只读模式的"发送端对偶":只允许发消息,按构造请求最小令牌(Chat.ReadBasic + *.Send 范围,读不了任何消息内容)。
3️⃣ 用正则过滤器精确控制工具
预设粒度不够时,用 --enabled-tools(或 ENABLED_TOOLS 环境变量)做白名单正则:
npx @softeria/ms-365-mcp-server \
--enabled-tools '^(list-mail-messages|get-mail-message)$'
上例只允许"列出邮件"和"读取单封邮件"两个工具,其余 300 多个全部不注册。被隐藏的工具在 src/graph-tools.ts 中会记录完整的拒绝策略(含缺失的权限范围),便于排查。
4️⃣ 用 OAuth 范围白名单设置"硬边界"
工具过滤控制的是"有哪些工具",而 --allowed-scopes(或 MS365_MCP_ALLOWED_SCOPES)控制的是令牌本身能拿到什么权限——这是企业部署最关键的边界:
npx @softeria/ms-365-mcp-server \
--org-mode \
--read-only \
--enabled-tools 'sharepoint|site|drive|planner' \
--allowed-scopes 'User.Read Files.Read Notes.Read Tasks.Read Sites.Selected'
工作方式是:先按常规逻辑计算工具面,然后隐藏所需范围不在白名单内的所有工具,OAuth 元数据与登录流程也只请求剩余工具的有效权限。范围覆盖是层级感知的——例如 Mail.ReadWrite 会覆盖需要 Mail.Read 的工具。空值会在启动时直接失败,防止意外回退到更宽的工具面。
企业 SharePoint 场景建议优先使用 Sites.Selected(站点级授权)而非 Sites.Read.All 这类租户级大范围权限,实现"应用只能访问被授权的站点集合"。
5️⃣ 部署前用 --list-permissions 预览权限
企业环境通常要求 Graph API 权限先审批、再部署。该参数会打印当前配置下实际需要的权限:
npx @softeria/ms-365-mcp-server --preset mail --list-permissions
输出包含 toolPermissions(过滤前)、effectivePermissions(过滤后)、disabledTools(因范围不足被隐藏的工具)、missingAllowedScopesForTools 等字段,让你在授权同意之前就知道这份配置到底要什么权限——这是最小权限落地的"验收工具"。
6️⃣ 给写操作加一道"确认门"
如果确实需要保留写操作(比如发草稿、建事件),可以开启破坏性操作的确认闸门:
MS365_MCP_REQUIRE_CONFIRM=true npx @softeria/ms-365-mcp-server
开启后,所有 POST / PATCH / PUT / DELETE 类工具(endpoints.json 中标记只读的 POST 查询除外)都要求调用方显式传入 confirm: true 才会真正请求 Graph。这是针对"LLM 走错请求或跟随注入指令"的纵深防御,默认关闭、按需开启,实现见 src/graph-tools.ts。
7️⃣ 把服务器锁定到指定 Microsoft 账户
无头(headless)部署时,防止"登错账户"导致的意外跨账户访问:
MS365_MCP_EXPECTED_USERNAME=work@company.com npx @softeria/ms-365-mcp-server
或更精确地用 --expected-home-account-id 锁定 MSAL 的 homeAccountId(用 --list-accounts 查询)。账户不匹配会在启动时快速失败;锁定后服务端也不再暴露多账户切换的 account 参数,MCP 指令中也不会建议切换账户。相关逻辑见 src/startup-pinning.ts。
8️⃣ 限制用户档案返回字段
组织模式下读取 /users 时可能暴露员工档案。--user-fields 可对指定用户目录路径做字段级投影:
npx @softeria/ms-365-mcp-server --org-mode \
--user-fields 'id,displayName,mail,userPrincipalName'
注意两点:字段白名单是完备式的(id 不在列表中就不返回,需要时务必显式加入);它是路径级数据过滤,不是完整的授权边界(官方明确提示 list-relevant-people 是已知缺口,需要的话配合 --enabled-tools 一并剔除)。实现细节见 src/lib/user-field-policy.ts。
9️⃣ 管好令牌与密钥:加密缓存 + Key Vault
- 本地令牌缓存:认证令牌以 AES-256-GCM 加密存储,只有 32 字节密钥进入系统凭据库(keytar),缓存文件以
0600权限写入。无凭据库环境(容器、无头 Linux)会退化为缓存目录下的.cache-key文件。详见 src/token-cache-storage.ts 与 src/lib/cache-encryption.ts。 - 生产密钥托管:把 Client ID / Secret 放进 Azure Key Vault,只需设置
MS365_MCP_KEYVAULT_URL,服务端会用DefaultAzureCredential(托管身份优先)自动解析:
MS365_MCP_KEYVAULT_URL=https://your-vault.vault.azure.net npx @softeria/ms-365-mcp-server
密钥名映射:ms365-mcp-client-id / ms365-mcp-tenant-id / ms365-mcp-client-secret,加载逻辑在 src/secrets.ts。
🔟 HTTP 模式:OAuth 强制 + 回环绑定 + 限速
用 --http 部署为服务端时,默认行为已经相当保守:
| 机制 | 说明 |
|---|---|
| OAuth 2.1 强制认证 | 所有 MCP 请求必须携带 Authorization: Bearer <token>,令牌经 Microsoft Graph 校验 |
| 回环绑定 | 默认只绑定 127.0.0.1 / ::1,未配 --public-url 时拒绝非 localhost 的 Host/Origin |
| 按 IP 限速 | /authorize、/token、/register 30 次/分钟,/mcp 120 次/分钟(MS365_MCP_RATE_LIMIT_DISABLED 才关闭) |
| 动态注册可关 | --no-dynamic-registration 关闭 OAuth 动态客户端注册 |
反向代理与完整生产部署指南见 docs/deployment.md。
1️⃣1️⃣ 审计日志与日志脱敏:默认全开
- 结构化审计日志:每次工具调用都会记录一条机器可读的 JSON 事件(谁、何时、访问了哪个资源 ID、是否出错),写入
~/.ms-365-mcp-server/logs/audit.log(可用MS365_MCP_LOG_DIR重定向),文件权限0600。它刻意不记录工具参数和响应体,满足 GDPR/SOC 2 等审计需求。实现见 src/audit-log.ts;如已用外部审计通道,可设MS365_MCP_AUDIT_LOG=false关闭。 - PII 脱敏:所有日志默认经过脱敏过滤器,自动替换 JWT、
Bearer头、OAuth token 字段和邮箱地址;仅在需要完整调试日志时用MS365_MCP_REDACT_PII=false关闭。规则见 src/lib/log-redactor.ts。
附:顺手限制响应体积
大列表会撑爆上下文、放大泄露面,两个变量值得一配:
MS365_MCP_MAX_TOP=15:硬限制列表请求的$top;MS365_MCP_MAX_PAGES/MS365_MCP_MAX_ITEMS:限制翻页跟随的上限(默认 100 页 / 10000 条)。
📋 组合示例:一个可直接抄的最小权限部署
npx @softeria/ms-365-mcp-server \
--read-only \
--preset outlook \
--enabled-tools '^(?!.*send-).*' \
--expected-username work@company.com
配合环境变量:
MS365_MCP_REQUIRE_CONFIRM=true # 若保留写工具则必开
MS365_MCP_MAX_TOP=15
最后用 --list-permissions 验证一次实际权限面,确认没有多余范围再上线。
结语
ms-365-mcp-server 的最小权限体系可以概括为一句话:用 --read-only 关闸、用 preset/正则收窄工具面、用 --allowed-scopes 钉死令牌边界、用审计日志兜底。按这份清单逐项打勾,你的 AI Agent 就只拥有"完成任务所必需"的那一点权限——更安全,也更容易向管理员和合规团队交代。发现安全漏洞?请按 SECURITY.md 通过私有渠道负责任地报告。