让AI Agent只读更安全:ms-365-mcp-server最小权限与安全配置清单

原创2026-10-16 10:57:55321 阅读

让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 通过私有渠道负责任地报告。

登录后查看全文
ms-365-mcp-server