mcpField
M
ms-365-mcp-server

ms-365-mcp-server

本地服务
github.com/softeria生产力1.1k今天更新

关于 ms-365-mcp-server

ms-365-mcp-server 通过 Microsoft Graph API 为 MCP 客户端提供 Microsoft 365 和 Office 服务访问,涵盖个人及组织场景。它支持只读模式、工具筛选和权限范围限制,以便按部署需求调整可用工具。

主要功能

  1. 01提供 300 多个 Graph API 工具
  2. 02支持邮件、日历和 OneDrive
  3. 03组织模式涵盖 Teams 与 SharePoint
  4. 04支持只读模式和工具筛选
  5. 05按启用工具计算权限范围
  6. 06支持 JSON 与实验性 TOON 输出

适用场景

  1. 01从 MCP 客户端访问 Outlook 邮件和日历
  2. 02管理 OneDrive 文件与 Planner 任务
  3. 03在组织模式下使用 Teams 和 SharePoint
  4. 04部署前检查工具所需的 Graph 权限

MCP 相关

这是一个 MCP 服务,通过工具向 MCP 客户端开放 Microsoft Graph API 所覆盖的 Microsoft 365 与 Office 服务。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"ms-365": {
"command": "npx",
"args": [
"-y",
"@softeria/ms-365-mcp-server"
]
}
}
}
启动命令npx -y @softeria/ms-365-mcp-server

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@softeria/ms-365-mcp-server0.176.0

工具

188

收录的工具列表 · 更新于 3 周前

参数类型必填说明
forceboolean否即使已经登录,也强制重新登录

详细介绍

ms-365-mcp-server

npm version build status license

Microsoft 365 MCP 服务

一个通过 Graph API 与 Microsoft 365 和 Microsoft Office 服务交互的模型上下文协议(MCP)服务。

支持的云环境

此服务支持多种 Microsoft 云环境:

云环境描述身份验证端点Graph API 端点
Global(默认)国际版 Microsoft 365login.microsoftonline.comgraph.microsoft.com
China(21Vianet)由 21Vianet 运营的 Microsoft 365login.chinacloudapi.cnmicrosoftgraph.chinacloudapi.cn
GCC High美国政府版 Microsoft 365 GCC Highlogin.microsoftonline.usgraph.microsoft.us

GCC High 没有预注册的客户端:请在你的政府租户中注册应用,并设置 MS365_MCP_CLIENT_ID(和 MS365_MCP_TENANT_ID),然后使用 --cloud gcc-high。

若要通过你自己的代理路由 Graph 流量(该代理会终止客户端的纯 HTTP 连接,并向 Microsoft 发起 TLS 连接),请将 MS365_MCP_GRAPH_BASE_URL 设置为其基础 URL;请参阅 CLI 选项下的环境变量列表,其中也说明了哪些流量仍会绕过该代理。

先决条件

  • Node.js >= 20(推荐)
  • Node.js 14+ 可能可以运行,但会出现依赖项警告

功能

  • 通过 Microsoft Authentication Library(MSAL)进行身份验证
  • 全面的 Microsoft 365 服务集成
  • 支持只读模式,确保操作安全
  • 工具筛选,实现精细的访问控制
  • 工具预设和动态发现,缩小工具范围并减少令牌用量

输出格式:JSON 与 TOON

此服务支持两种可全局配置的输出格式:

JSON 格式(默认)

带美化排版的标准 JSON 输出:

{
  "value": [
    {
      "id": "1",
      "displayName": "Alice Johnson",
      "mail": "[email protected]",
      "jobTitle": "Software Engineer"
    }
  ]
}
(实验性)TOON 格式

Token-Oriented Object Notation,可高效使用 LLM 令牌:

value[1]{id,displayName,mail,jobTitle}:
  "1",Alice Johnson,[email protected],Software Engineer

优点:

  • 与 JSON 相比,令牌用量减少 30-60%
  • 最适合结构统一的数组数据(邮件列表、日历事件、文件等)
  • 非常适用于大规模、成本敏感型应用

用法: (实验性)全局启用 TOON 格式:

通过 CLI 标志:

npx @softeria/ms-365-mcp-server --toon

通过 Claude Desktop 配置:

{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--toon"]
    }
  }
}

通过环境变量:

MS365_MCP_OUTPUT_FORMAT=toon npx @softeria/ms-365-mcp-server

支持的服务和工具

此服务提供 300+ 个工具,覆盖 Microsoft Graph API 的大部分功能。每个工具都与一个 Graph API 端点一一对应,并以声明方式定义在 src/endpoints.json 中。

个人帐户工具(默认可用)

电子邮件(Outlook)、日历、OneDrive 文件、Excel、OneNote、To Do 任务、Planner、联系人、用户资料、搜索

组织账户工具(需要 --org-mode 标志)

Teams 和聊天、在线会议、文字记录和录制内容、出席报告、SharePoint 站点和列表、共享邮箱和日历、用户管理、状态、虚拟活动

在组织模式下,可通过 list-custom-emojis 和 create-custom-emoji(teams 和 work 预设)使用自定义 Teams 表情符号。这些工具使用 Microsoft Graph beta API,并分别请求委派权限 TeamworkCustomEmoji.Read 和 TeamworkCustomEmoji.Create。只读模式仅开放列表工具。现有部署可能需要同意这些新权限范围并重新进行身份验证;添加工具支持不会升级已签发的令牌。

默认情况下,列表结果可能包含 contentBytes: null。若要请求 base64 PNG/GIF 图像,请传入 select: "displayName,contentBytes" 或 select: ["displayName", "contentBytes"]。请将 top 设为较小的值,以控制图像响应的大小。 若要创建表情符号,请传入 body: { displayName, contentBytes },其中包含经批准的准确名称和 base64 PNG/GIF 文件字节。请参阅 Microsoft 的list和create契约。这些工具不会发布消息或反应。

必需的 Graph API 权限

系统会根据启用的工具动态请求权限。使用 --list-permissions 查看当前配置所需的确切权限:

# Personal mode (default)
npx @softeria/ms-365-mcp-server --list-permissions

# Organization mode (includes Teams, SharePoint, etc.)
npx @softeria/ms-365-mcp-server --org-mode --list-permissions

# Filtered by preset
npx @softeria/ms-365-mcp-server --preset mail --list-permissions

对于必须在部署新版本之前预先批准 Graph API 权限并获得管理员同意的企业环境,这一功能很有用。

--list-permissions JSON 包含:

  • toolPermissions:在应用 --allowed-scopes 过滤之前,根据工具接口确定的权限
  • effectivePermissions:根据应用 --allowed-scopes 后仍处于启用状态的工具确定的权限
  • permissions:effectivePermissions 的旧版别名,用于兼容现有脚本
  • allowedScopes:已配置的权限范围允许列表(如有提供)
  • disabledTools:因其所需的 Graph 权限范围不在 allowedScopes 中而被隐藏的工具
  • missingAllowedScopesForTools:所有已禁用工具缺失的权限范围(不重复)
  • extraAllowedScopesNotUsedByTools:当前工具接口未使用、但已获允许的权限范围
允许的权限范围

默认情况下,MSAL 会请求启用工具所需的权限范围,而工具界面由 --enabled-tools、--enabled-tools-file、--preset、--org-mode 和 --read-only 控制。

企业部署和无头部署可通过 --allowed-scopes 或 MS365_MCP_ALLOWED_SCOPES 设置权限范围边界。设置后,服务会先计算常规工具界面,然后隐藏所需权限范围未包含在允许列表中的 Graph 工具。OAuth 元数据和登录流程只会请求剩余已启用工具所需的有效权限。

npx @softeria/ms-365-mcp-server \
  --org-mode \
  --enabled-tools '^(list-mail-messages|get-mail-message|list-drives|get-drive-item|download-bytes)$' \
  --allowed-scopes 'User.Read Mail.Read Files.Read'

对于较长的筛选条件,可将每个正则表达式单独放在文件的一行中,并传入 --enabled-tools-file / ENABLED_TOOLS_FILE。空行和整行 # 注释会被丢弃;其余内容会以 | 连接,因此该文件等同于 --enabled-tools=line1|line2|…:

# tools.txt
^list-mail-messages$
^get-mail-message$
drive|download-bytes
npx @softeria/ms-365-mcp-server --org-mode --enabled-tools-file ./tools.txt

命令行值优先于 MS365_MCP_ALLOWED_SCOPES;如果两者都未设置,则默认的、由工具推导权限范围的行为保持不变。提供空值会导致启动失败,避免部署意外回退到范围更广的工具界面。

权限范围覆盖关系支持层级匹配:例如,Mail.ReadWrite 覆盖需要 Mail.Read 的工具,Files.ReadWrite.All 覆盖需要 Files.Read 的工具。

SharePoint 支持两种企业权限模型:

  • 范围较广的租户权限范围,例如 Sites.Read.All、Sites.ReadWrite.All 和 Sites.Manage.All。
  • Microsoft Graph Sites.Selected,此模式会向应用授予对特定站点集的 SharePoint 站点访问权限,而 Graph 会在请求时检查已登录用户自身的权限。

默认的组织模式行为会继续请求现有部署所用的范围较广的 SharePoint 权限范围。希望使用选定站点 SharePoint 访问权限的企业,可设置包含 Sites.Selected 而非范围较广的 Sites.*.All 权限范围的允许列表。针对明确 SharePoint 站点的直接站点/列表/项目工具,以及针对已获授权站点中驱动器的 /drives/{drive-id}/... 项目工具(列表、获取、上传、文件夹、移动/重命名、复制、版本),均可使用 Sites.Selected 运行;租户范围的 SharePoint 发现和搜索工具仍需要范围较广的 SharePoint 权限范围。

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'

在 HTTP 模式下,OAuth 发现会公布经过筛选后的有效权限,以便客户端请求相同的授权范围。在 On-Behalf-Of 模式(--obo)下,受保护资源元数据仍会公布 <clientId>/access_as_user,并在 /authorize 上请求该权限;--allowed-scopes 不会覆盖 OBO,只会缩小工具界面。

限制用户个人资料字段

部署可通过 --user-fields 或 MS365_MCP_USER_FIELDS 为选定的用户目录路径设置以逗号分隔的字段筛选器。它会缩小 Microsoft Graph 对 /users 读取请求发送的 $select 范围,并投影下方列出的受覆盖路径上的响应。这是针对特定路径的数据筛选器,并非完整的 Graph 授权边界:其他 Graph 路由和资源形态可能会暴露重叠的个人资料数据。

npx @softeria/ms-365-mcp-server \
  --org-mode \
  --user-fields 'id,displayName,mail,userPrincipalName'

筛选器根据已知的 Graph 路径而非工具名称生效。它适用于直接工具、发现模式的 execute-tool,以及匹配的 graph-batch 子请求。对于受覆盖的 /users 读取请求,批处理子请求 URL 会被重写;对于所有受覆盖的个人资料路径,响应都会经过投影。当筛选器启用时,字节直通工具(download-bytes、download-bytes-to-file、get-download-url)会原样返回响应,并拒绝目标路径与受覆盖用户个人资料路径匹配的请求。

根据 Graph 返回的内容,会采用以下两种级别的强制措施:

路径强制措施
/users、/users/{id}请求中的 $select 范围会缩小,响应也会经过投影,因此被排除的字段不会离开租户
/me/manager、/me/directReports、/users/{id}/manager、/users/{id}/directReports、/groups/{id}/members、/groups/{id}/owners响应会经过投影。这些路径的类型是 directoryObject;Graph 要求先使用 OData 强制转换,才能对 jobTitle 等仅适用于用户的属性使用 $select。因此,缩小请求范围可能会导致目前可用的调用失败

筛选器启用时,会从 /me、/groups 和 /groups/{id} 请求中移除 $expand,因为这些资源可能会扩展为相关的用户个人资料。此规则同样适用于匹配的批处理子请求。除此之外,基础 /me 和群组响应不会经过投影。

这并未覆盖所有可能返回用户形态对象的路由。特别是,通用 Graph 访问仍可访问其他路由或表示形式,例如 /directoryObjects/{id}、/groups/{id}/transitiveMembers 或 /users('{id}')。这些路径目前不会规范化为受覆盖的路径。如果此设置是安全要求,还应限制通用 Graph 访问(例如 graph-batch),并验证部署实际提供的工具范围;不要仅依赖 --user-fields 作为租户级数据访问控制。

用户下的资源,例如 /users/{id}/messages 或 /users/{id}/photo/$value,属于邮件、日历和二进制资源,而非个人资料属性,因此不受影响。已登录用户自己的 /me 个人资料也不受影响,Teams 和聊天成员列表同样如此,因为它们返回的是 conversationMember,而非用户个人资料。

已知缺口:list-relevant-people

list-relevant-people(/me/people)未涵盖。它返回 person 资源,这是一种不同的类型,其属性仅部分与 user 重叠——它包含 jobTitle、department 和 officeLocation,但地址以 scoredEmailAddresses 而非 mail 的形式返回。对其应用用户字段允许列表会拒绝有效的字段名称,并将结果缩小到该工具无法使用的范围。

需要同时关闭 people 接口的部署,应从接口中移除该工具,例如使用 --enabled-tools,或选择不包含该工具的预设:

npx @softeria/ms-365-mcp-server \
  --org-mode \
  --user-fields 'id,displayName,mail' \
  --enabled-tools '^(?!list-relevant-people$).*'

CLI 值优先于环境变量。若指定的值不包含任何字段,启动时会失败。如果两者均未配置,则行为保持不变。请在 LibreChat MCP 服务环境或命令参数中配置此项;项目本地的 .env 文件有意限制为应用凭据,不用于此设置。

此允许列表涵盖所有用户属性:与普通的 $select 不同,只有在列表中包含 id 时才会返回它,因此如果下游工具需要使用它来定位用户,请将其加入列表。会保留 @odata.nextLink 等集合注释,以确保分页正常工作。如果 Graph 未返回任何允许列表中的属性,则会将响应投影为空,而不是原样返回未裁剪的响应。

请求额外的作用域

--allowed-scopes 只会缩小令牌请求的范围。若要请求任何捆绑工具都不需要的 Graph 作用域——例如,通过 graph-batch 调用某个端点——请使用 --extra-scopes(或 MS365_MCP_EXTRA_SCOPES)。这些作用域会逐字附加到令牌请求中,并叠加在根据工具推导出的作用域之上。

npx @softeria/ms-365-mcp-server \
  --org-mode \
  --extra-scopes 'CopilotPackages.ReadWrite.All'

此功能适用于使用你自己的 Azure 应用注册(MS365_MCP_CLIENT_ID / MS365_MCP_CLIENT_SECRET):默认的 Softeria 应用仅声明一组精简且固定的权限,因此请使用你控制的应用请求额外作用域(由你的租户管理员在该应用中授予同意)。CLI 值优先于环境变量;空值会导致启动失败。

组织/工作模式

若要访问工作或学校功能(Teams、SharePoint 等),请使用以下任一标志启用组织模式:

{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode"]
    }
  }
}

必须从一开始就启用组织模式,才能访问工作账户功能。未启用此标志时,只能使用个人账户功能(电子邮件、日历、OneDrive 等)。

共享邮箱访问

若要访问共享邮箱,你需要:

  1. 组织模式:共享邮箱工具需要 --org-mode 标志(仅限工作/学校账户)
  2. 委派权限:读取需要 Mail.Read.Shared;创建、更新或移动邮件需要 Mail.ReadWrite.Shared;发送、回复或转发需要 Mail.Send.Shared;共享日历工具需要 Calendars.Read.Shared
  3. Exchange 权限:必须已向登录用户授予共享邮箱的访问权限
  4. 用法:在共享邮箱工具的 user-id 参数中使用共享邮箱的电子邮件地址

查找共享邮箱:使用 list-users 工具,在组织中查找可用用户和共享邮箱。

示例:调用 list-shared-mailbox-messages,并将 user-id 设为 [email protected]

快速入门示例

在 Claude Desktop 中测试登录:

登录示例

示例

图片

集成

Claude Desktop

若要将此 MCP 服务添加到 Claude Desktop,请在 Settings > Developer 下编辑配置文件。

个人账户(MSA)
{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server"]
    }
  }
}
工作/学校账户(全球)
{
  "mcpServers": {
    "ms365": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode"]
    }
  }
}
工作/学校账户(中国 21Vianet)
{
  "mcpServers": {
    "ms365-china": {
      "command": "npx",
      "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode", "--cloud", "china"]
    }
  }
}
Claude Code CLI
个人账户(MSA)
claude mcp add ms365 -- npx -y @softeria/ms-365-mcp-server
工作/学校账户(全球)
# macOS/Linux
claude mcp add ms365 -- npx -y @softeria/ms-365-mcp-server --org-mode

# Windows (use cmd /c wrapper)
claude mcp add ms365 -s user -- cmd /c "npx -y @softeria/ms-365-mcp-server --org-mode"
工作/学校帐户(中国 21Vianet)
# macOS/Linux
claude mcp add ms365-china -- npx -y @softeria/ms-365-mcp-server --org-mode --cloud china

# Windows (use cmd /c wrapper)
claude mcp add ms365-china -s user -- cmd /c "npx -y @softeria/ms-365-mcp-server --org-mode --cloud china"

对于其他支持 MCP 的接口,请参阅相应文档,了解正确的集成方法。

Open WebUI

Open WebUI 通过 HTTP 传输和 OAuth 2.1 支持 MCP 服务。

  1. 以 HTTP 模式启动服务:

    npx @softeria/ms-365-mcp-server --http
    
  2. 在 Open WebUI 中,前往 Admin Settings → Tools(/admin/settings/tools)→ Add Connection:

    • Type:MCP Streamable HTTP
    • URL:MCP 服务 URL,路径为 /mcp
    • Auth:OAuth 2.1
  3. 点击 Register Client。

注意:HTTP 模式下默认启用动态客户端注册。使用 --no-dynamic-registration(或设置 MS365_MCP_DISABLE_DCR=true)可将其禁用。如果使用自定义 Azure Entra 应用,重定向 URI 的平台类型取决于应用是否具有客户端密钥:有密钥时选择“Web”,没有密钥时选择“Mobile and desktop applications”(切勿选择“Single-page application”)。

快速测试设置:使用默认 Azure 应用(ID ms-365 和 localhost:8080 已预先配置):

docker run -d -p 8080:8080 \
  -e WEBUI_AUTH=false \
  -e OPENAI_API_KEY \
  ghcr.io/open-webui/open-webui:main

npx @softeria/ms-365-mcp-server --http

然后,添加连接,URL 填写为 http://localhost:3000/mcp,ID 填写为 ms-365。

Open WebUI MCP 连接

在反向代理后方的 Docker 中运行? 设置 --public-url https://your-domain.com,这样提供给用户浏览器的 OAuth 授权 URL 就能从容器网络外部访问。完整指南请参阅 docs/deployment.md。

本地开发

用于本地开发或测试:

# From the project directory
claude mcp add ms -- npx tsx src/index.ts --org-mode

或者手动配置 Claude Desktop:

{
  "mcpServers": {
    "ms365": {
      "command": "node",
      "args": ["/absolute/path/to/ms-365-mcp-server/dist/index.js", "--org-mode"]
    }
  }
}

注意:代码更改后运行 npm run build,以更新 dist/ 文件夹。

身份验证

⚠️ 使用工具前必须先进行身份验证。

该服务支持三种身份验证方式:

1. 设备代码流(默认)

通过设备代码进行交互式身份验证:

  • MCP 客户端登录:
    • 调用 login 工具(自动检查现有令牌)
    • 如有需要,获取 URL 和代码,并在浏览器中访问
    • 使用 verify-login 工具进行确认
  • 命令行登录:
    npx @softeria/ms-365-mcp-server --login
    
    按照终端中显示的 URL 和代码提示操作。

令牌会安全地缓存在操作系统的凭据存储中(无法使用时则回退到文件存储)。

2. OAuth 授权码流(仅限 HTTP 模式)

使用 --http 运行时,服务要求进行 OAuth 身份验证:

npx @softeria/ms-365-mcp-server --http 3000

此模式:

  • 向 MCP 客户端声明 OAuth 功能
  • 在 /auth/* 提供 OAuth 端点(authorize、token、metadata)
  • 所有 MCP 请求都必须提供 Authorization: Bearer <token>
  • 使用 Microsoft Graph API 验证令牌
  • 默认禁用登录/登出工具(使用 --enable-auth-tools 可启用)

MCP 客户端检测到所声明的功能后,会自动处理 OAuth 流程。

为 OAuth 测试设置 Azure AD

要使用自定义 Azure 凭据(建议用于生产环境)启用 OAuth 模式,你需要设置 Azure AD 应用注册:

  1. 创建 Azure AD 应用注册:
  • 前往 Azure Portal
  • 导航至 Azure Active Directory → App registrations → New registration
  • 设置名称:“MS365 MCP Server”
  1. 配置重定向 URI:
  • 配置 OAuth 回调 URI:前往应用注册页面,然后在左侧选择 Authentication。
  • 在 Platform configurations 下:
    • 点击 Add a platform(如果尚未看到“Mobile and desktop applications”/“Public client”平台)。
    • 选择 Mobile and desktop applications 或 Public client/native (mobile & desktop)(具体标签取决于门户版本)。
  1. 使用 MCP Inspector 测试(npm run inspector):
  • 前往应用注册页面,然后在左侧选择 Authentication。
  • 在 Platform configurations 下:
    • 点击 Add a platform(如果尚未看到“Web”平台)。
    • 选择 Web。
    • 配置以下重定向 URI
      • http://localhost:6274/oauth/callback
      • http://localhost:6274/oauth/callback/debug
      • http://localhost:3000/callback(可选,用于服务回调)
  1. 获取凭据:
  • 从 Overview 页面复制 Application (client) ID
  • 前往 Certificates & secrets → New client secret → 复制密钥值(公共应用可选)
  1. 配置环境变量: 在项目根目录中创建 .env 文件:
    MS365_MCP_CLIENT_ID=your-azure-ad-app-client-id-here
    MS365_MCP_CLIENT_SECRET=your-secret-here  # Optional for public apps
    MS365_MCP_TENANT_ID=common
    

完成这些配置后,服务将使用你的自定义 Azure 应用,而不是内置应用。

注意:.env 会从服务启动所在的目录读取,具体目录由 MCP 客户端决定。只有 MS365_MCP_CLIENT_ID、MS365_MCP_CLIENT_SECRET、MS365_MCP_TENANT_ID 和 MS365_MCP_CLOUD_TYPE 会从中读取。上文列出的其他所有变量都必须在你的 shell 或 MCP 客户端配置中设置;.env 中发现的其他变量都会被忽略,并在 stderr 中显示警告。

3. 自带令牌(BYOT)

如果你将 ms-365-mcp-server 作为更大型系统的一部分运行,且该系统在外部管理 Microsoft OAuth 令牌,则可以直接向此 MCP 服务提供访问令牌:

MS365_MCP_OAUTH_TOKEN=your_oauth_token npx @softeria/ms-365-mcp-server

此方法:

  • 跳过交互式身份验证流程
  • 使用你现有的 OAuth 令牌发送 Microsoft Graph API 请求
  • 不处理令牌刷新(令牌生命周期管理由你负责)

注意:HTTP 模式需要身份验证。若要进行无身份验证测试,请使用带设备代码流的 stdio 模式。

身份验证工具:在 HTTP 模式下,默认禁用登录/注销工具,因为 OAuth 会处理身份验证。 如果需要启用这些工具,请使用 --enable-auth-tools。

多账户支持

使用单个服务实例为多个 Microsoft 账户提供服务。登录多个账户后,每个工具都会自动注入 account 参数,让你能够在每次工具调用中指定要使用的账户。

登录多个账户(每个账户只需一次):

# Login first account (device code flow)
npx @softeria/ms-365-mcp-server --login
# Follow the device code prompt, sign in as [email protected]

# Login second account
npx @softeria/ms-365-mcp-server --login
# Follow the device code prompt, sign in as [email protected]

列出已配置的账户:

npx @softeria/ms-365-mcp-server --list-accounts

**在工具调用中使用:**在任意工具请求中传入 "account": "[email protected]":

{ "tool": "list-mail-messages", "arguments": { "account": "[email protected]" } }

行为:

  • 配置了单个账户时,会自动选择该账户(无需 account 参数)。
  • 配置了多个账户但未提供 account 参数时,服务会使用选定的默认账户,或返回列出可用账户的说明性错误。
  • 完全向后兼容:现有的单账户配置无需更改即可正常工作。
  • account 参数接受电子邮件地址(例如 [email protected])或 MSAL homeAccountId。
严格固定账户

无头 stdio 部署可将本地 MSAL 缓存固定到一个预期的 Microsoft 账户:

# Username matching is case-insensitive
[email protected] npx @softeria/ms-365-mcp-server --login

# Or pin the exact MSAL homeAccountId shown by --list-accounts
npx @softeria/ms-365-mcp-server --expected-home-account-id <homeAccountId> --login

使用 --list-accounts 查找 homeAccountId 值。MCP list-accounts 工具会有意隐藏账户 ID,因此要精确固定 ID,请使用 CLI。

固定账户为可选设置,且仅适用于本地 MSAL:

  • CLI 值(--expected-username、--expected-home-account-id)优先于 MS365_MCP_EXPECTED_USERNAME 和 MS365_MCP_EXPECTED_HOME_ACCOUNT_ID。
  • 提供空的固定值会导致启动失败,而不会被忽略。
  • 用户名固定值比较时不区分大小写;homeAccountId 固定值必须完全匹配。
  • 如果同时设置了两个固定值,它们必须对应同一个缓存账户。
  • 如果令牌缓存中没有预期账户,本地 stdio 启动会立即失败。可通过设置固定值、运行 --login,然后启动无头服务来完成初始化。
  • 设备代码和浏览器登录会在持久化所选账户或令牌缓存前,拒绝缺失或不匹配的账户。
  • 固定账户会将有效的 MCP 模式收窄为单账户模式:服务不会公布 account 参数,MCP 使用说明也不会建议切换账户。
  • --http、--obo 和 MS365_MCP_OAUTH_TOKEN 使用请求提供的令牌调用 Graph,因此在这些模式下,账户固定仅会发出警告。如果启用了 HTTP 身份验证工具,固定账户仍适用于这些本地 MSAL 辅助流程。
  • --logout 会清除所有缓存账户,包括固定账户。如需精准清理,建议使用 --remove-account <id>。

对于 MCP 多路复用器(Legate、Governor): 多账户模式取代了 N 个进程的模式。无需为每个账户启动一个服务实例,单个实例即可通过 account 参数处理所有账户,将工具重复数从 N×110 降至 110。

工具预设

为减少初始连接开销和令牌用量,请使用预设工具类别,而不是加载完整工具集:

npx @softeria/ms-365-mcp-server --preset mail
npx @softeria/ms-365-mcp-server --list-presets  # See all available presets

可用预设:mail、calendar、files、personal、work、excel、contacts、tasks、onenote、search、users、outlook、onedrive、teams、teams-write、all

完整的预设工具列表请参见 PRESETS.md(运行 npm run generate 时会重新生成)。

endpoints.json 中的每个端点都通过 presets 数组声明所属预设,因此每个预设都是精确的工具名称允许列表,不会跨应用意外匹配(例如,mail 不包含共享邮箱工具,这些工具属于 work)。通用二进制读取工具 download-bytes 包含在除 teams-write 之外的所有预设中,因此无论应用返回什么内容(文件、附件、照片或录音),都始终可以获取;get-download-url(用于网盘/SharePoint 文件的预认证 URL)随网盘相关预设提供,并且在 HTTP 模式使用 --enable-attachment-urls 运行时,也会加入除 teams-write 之外的其他所有预设。因此,能够查找文件的预设始终也能读取其字节内容。

outlook、onedrive 和 teams 预设按应用限定:每个预设只开放一个 Microsoft 应用。需要“只开放一个应用”的部署请使用这些预设:

# Outlook only (mail + calendar + contacts; no shared mailboxes, no files)
npx @softeria/ms-365-mcp-server --preset outlook

# Teams only (requires --org-mode)
npx @softeria/ms-365-mcp-server --org-mode --preset teams

teams-write 是 --read-only 的仅发送对应预设:可在聊天中发送消息、在频道中发送或回复消息、按名称列出聊天/团队/频道,以及接收活动通知——不提供消息读取功能,也不提供字节下载工具。所需令牌在设计上已精简到最低限度(Chat.ReadBasic、*.Send 范围,以及基本的团队/频道列表权限——不包含任何可读取消息内容的权限):

npx @softeria/ms-365-mcp-server --org-mode --preset teams-write

动态工具发现

无需预先加载所有工具,可使用动态发现,让大模型仅在需要时查找并加载工具:

npx @softeria/ms-365-mcp-server --discovery

这能缩小初始上下文并减少令牌用量,特别适用于长会话或注重成本的配置(例如,Open WebUI 使用付费 API 的场景)。

通过直接工具实现混合发现

在直接暴露常用操作的同时,让完整的已启用目录均可发现:

npx @softeria/ms-365-mcp-server --org-mode --discovery \
  --eager-tools list-mail-messages,get-calendar-view,download-bytes

--eager-tools 接受以逗号分隔的精确 Graph 或实用工具名称, 而不是正则表达式或预设。环境变量 MS365_MCP_EAGER_TOOLS 是后备设置;CLI 优先。空值或仅包含空白字符的环境变量值视为未设置,即使未启用发现功能也是如此;显式传入空的 CLI 选项仍然无效。该选项需要 --discovery。空条目和未知名称会导致启动失败,包括 HTTP 模式。重复名称只会注册一次。

直接工具与常规模式使用相同的架构、注释和执行处理程序。它们仍可通过发现功能搜索和执行。现有的 --preset、--enabled-tools、--allowed-scopes、--read-only、组织模式 和传输限制会优先应用:预先选择工具无法启用已被过滤掉的工具,也无法添加权限。已识别但不可用的工具会被省略,并且每个服务器实例只记录一次诊断信息。HTTP 仍不包含服务器本地文件工具。

请选择少量工具:暴露大量生成的 Graph 架构可能会占用大量模型上下文。此选项不会缩减单个架构。未指定 --eager-tools 时,发现和常规注册行为保持不变。

CLI 选项

直接从命令行运行 ms-365-mcp-server 时,可以使用以下选项:

--login           Login using device code flow
--logout          Log out and clear saved credentials
--verify-login    Verify login without starting the server
--list-permissions List required Graph API permissions and exit (respects --org-mode, --preset, --enabled-tools, --enabled-tools-file, --allowed-scopes)
--org-mode        Enable organization/work mode from start (includes Teams, SharePoint, etc.)
--work-mode       Alias for --org-mode
--force-work-scopes Backwards compatibility alias for --org-mode (deprecated)
--cloud <type>    Microsoft cloud environment: global (default), china (21Vianet) or gcc-high
--allowed-scopes <scopes> Limit exposed tools to Graph scopes covered by this allowlist
--extra-scopes <scopes> Append additional Graph scopes to the token request (for use with your own app registration + graph-batch)
--expected-username <username> Require local MSAL auth to use this account username
--expected-home-account-id <id> Require local MSAL auth to use this exact homeAccountId
服务器选项

作为 MCP 服务运行时,可以使用以下选项:

-v                Enable verbose logging
--read-only       Start server in read-only mode, disabling write operations
--http [port]     Use Streamable HTTP transport instead of stdio (optionally specify port, default: 3000)
                  Starts Express.js server with MCP endpoint at /mcp. Bound to a loopback host
                  (e.g. --http 127.0.0.1:3000 or --http [::1]:3000) with no --public-url, it
                  rejects requests whose Host or Origin is not localhost (not applied to the
                  --attachment-port listener)
--http-local-file-tools Register download-bytes-to-file over HTTP. Anyone who can reach the port
                  can write files as the server's user (without a valid token, only an empty
                  file that is removed again), so enable it only on a single-user machine.
                  With MS365_MCP_UPLOAD_DIRS set it also registers upload-attachment-from-file,
                  which reads files from those directories.
                  Refused unless --http binds a loopback host with no --public-url and no
                  --trust-proxy-auth
--enable-auth-tools Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)
--enable-attachment-urls Let get-download-url mint a server-served URL for byte resources Graph
                  exposes no pre-authenticated URL for (see "Server-Minted Attachment URLs").
                  Also adds get-download-url to any --preset that lacks it (except teams-write)
--attachment-port <port> Serve /attachment on its own listener on this port instead of on the
                  MCP app, so a fetcher that can read attachments cannot also reach /mcp
                  (requires --enable-attachment-urls; see "Splitting the attachment listener")
--attachment-host <host> Interface the --attachment-port listener binds. Defaults to whatever
                  --http bound, which with a wildcard --http leaves BOTH ports on every
                  interface and so isolates nothing — set this to make the split real
                  (requires --attachment-port; see "Splitting the attachment listener")
--no-dynamic-registration Disable OAuth Dynamic Client Registration (enabled by default in HTTP mode)
--enabled-tools <pattern> Filter tools using regex pattern (e.g., "excel|contact" to enable Excel and Contact tools)
--enabled-tools-file <path> Filter tools using a file with one regex per line (same as --enabled-tools;
                  blank lines and whole-line # comments ignored). Overrides --enabled-tools;
                  overridden by --preset. Equivalent env var: ENABLED_TOOLS_FILE
--preset <names>  Use preset tool categories (comma-separated). See "Tool Presets" section above
--list-presets    List all available presets and exit
--toon            (experimental) Enable TOON output format for 30-60% token reduction
--blob-output     File download tools answer an MCP EmbeddedResource with the file instead of
                  base64 JSON in a text block (see MS365_MCP_BLOB_OUTPUT below)
--discovery       Dynamic tool discovery: loads tools on demand to reduce initial token usage (see "Dynamic Tool Discovery" above)
--public-url <url> Public base URL for OAuth when behind a reverse proxy (see Open WebUI section and docs/deployment.md)

环境变量:

  • READ_ONLY=true|1:可替代 --read-only 标志
  • ENABLED_TOOLS:使用正则表达式筛选工具(可替代 --enabled-tools 标志)
  • ENABLED_TOOLS_FILE:包含每行一个正则表达式的文件路径(可替代 --enabled-tools-file)。空行和整行 # 注释会被删除,然后将各行用 | 连接(等同于 ENABLED_TOOLS=line1|line2|…)。配置叠加顺序(后者优先):--enabled-tools → --enabled-tools-file → --preset → ENABLED_TOOLS → ENABLED_TOOLS_FILE
  • MS365_MCP_ORG_MODE=true|1:启用组织/工作模式(可替代 --org-mode 标志)
  • MS365_MCP_FORCE_WORK_SCOPES=true|1:为 MS365_MCP_ORG_MODE 提供向后兼容性
  • MS365_MCP_OUTPUT_FORMAT=toon:启用 TOON 输出格式(可替代 --toon 标志)
  • MS365_MCP_MAX_TOP=<n>:限制列表请求中 Graph $top / top 的最大值(正整数)。当模型传入更大的值时,服务会将其限制为 n,以缩小响应内容。例如:MS365_MCP_MAX_TOP=15
  • MS365_MCP_MAX_PAGES=<n>:调用工具并设置 fetchAllPages: true 时,最多跟进的页数(正整数,默认值为 100)。限制大型结果集所需的内存和延迟。
  • MS365_MCP_MAX_ITEMS=<n>:设置 fetchAllPages: true 时累计的项目数上限(正整数,默认值为 10000)。收集到此数量的项目后,分页会停止,响应也会被截断。
  • MS365_MCP_ALLOW_PAGINATION=0|false|no:完全禁用多页跟进。设置后,工具不会公布 fetchAllPages 参数;仍传入该参数的请求只会返回第一页(默认:启用分页)。
  • MS365_MCP_BLOB_OUTPUT=true|1:文件下载工具(download-bytes、get-mail-message-mime、get-shared-mailbox-message-mime、get-mail-attachment-content、get-shared-mailbox-attachment-content)会返回简短的 JSON 文本块(name、contentType、size)和包含 BlobResourceContents(uri、mimeType、blob)的 MCP EmbeddedResource,而非文本中的 base64 JSON。uri 的最后一段是可选的 fileName 参数。默认:关闭。可替代 --blob-output 标志。
  • MS365_MCP_BLOB_MAX_BYTES=<n>:启用 blob 输出时,下载工具读取的最大文件大小(正整数,默认值为 5242880)。服务超过此大小后会停止读取,并返回注明限制值的错误。
  • MS365_MCP_BODY_FORMAT=html:以 HTML 而非纯文本形式返回邮件正文(默认:文本)
  • MS365_MCP_OBO_ALLOWED_TENANTS=<ids> / MS365_MCP_OBO_ALLOWED_USERS=<ids>:使用 --obo 时,只有令牌中的 tid(租户 ID)/oid(用户对象 ID)列在其中的调用方才能使用服务;其他调用方会在任何 On-Behalf-Of 令牌交换发生前收到 403。用逗号或空格分隔;未设置 = 所有人。读取声明时不会验证签名;Entra 会在交换期间验证,因此即使令牌谎报这些声明,也无法获取 Graph 令牌。若 Entra 在交换时拒绝令牌(签名无效、已过期、受众错误、需要同意),则会返回带有 WWW-Authenticate 挑战的 401,以便客户端重新登录
  • MS365_MCP_READ_MAIL_TEXT_MAX_CHARS=<n>:read-mail-text 返回结果的最大字符数(默认值为 80000)。若结果超出限制,则会整体拒绝,并列出最大邮件,而非截断结果(参见下文“以纯文本形式阅读邮件”)
  • MS365_MCP_MESSAGE_SIGNOFF_PREFIX=<text>:添加到外发消息前的署名,以便收件人识别消息由 Agent 发送,例如 🤖。默认:无。CLI 等效项:--message-signoff-prefix <text>(参见下文“消息署名”)
  • MS365_MCP_MESSAGE_SIGNOFF_SUFFIX=<text>:添加到外发消息末尾的署名。默认:无。CLI 等效项:--message-signoff-suffix <text>。--no-message-signoff 可同时禁用前后署名(参见下文“消息署名”)
  • MS365_MCP_RATE_LIMIT_DISABLED=true|1:在 HTTP 模式下禁用按 IP 限流(默认:启用——/authorize、/token、/register 为 30 次请求/分钟;/mcp 为 120 次请求/分钟)
  • MS365_MCP_TRUST_PROXY_HOPS=<n>:HTTP 模式下受信任的反向代理跳数(默认值为 1)。按 IP 准确限流取决于此值是否与部署环境相符——设置为服务前方的代理数量;设为 0 可使用原始套接字对端 IP;也可设置为以逗号分隔的子网列表
  • MS365_MCP_ATTACHMENT_PORT=<port>:在此端口使用独立的监听器提供附件路由(可替代 --attachment-port;需要 --enable-attachment-urls)
  • MS365_MCP_ATTACHMENT_HOST=<host>:MS365_MCP_ATTACHMENT_PORT 监听器绑定的接口(可替代 --attachment-host;需要 --attachment-port)。默认使用 --http 绑定的主机——若 --http 使用通配符,两种端口都会对所有地址开放,因此端口分离无法隔离流量。参见“拆分附件监听器”
  • MS365_MCP_HTTP_LOCAL_FILE_TOOLS=true|1:通过 HTTP 注册 download-bytes-to-file;当设置了 MS365_MCP_UPLOAD_DIRS 时,也会注册 upload-attachment-from-file(可替代 --http-local-file-tools;限制相同)
  • MS365_MCP_UPLOAD_DIRS=<dirs>:upload-attachment-from-file 可读取的目录,使用类似 PATH 的格式以绝对路径分隔。未设置(默认)时,不会注册此工具。参见“在 stdio 模式下附加本地文件”
  • MS365_MCP_CLOUD_TYPE=global|china|gcc-high:Microsoft 云环境(可替代 --cloud 标志)
  • MS365_MCP_GRAPH_BASE_URL=<url>:将 Graph API 请求发送到此基准 URL,而非云环境的 graph.microsoft.com(例如,为自行发起 TLS 的出口代理设置为 http://127.0.0.1:10255/tenant-a/graph)。必须是没有查询或片段的绝对 http(s) URL;路径前缀会保留,因此请求会发送到 <url>/v1.0/...。登录颁发机构和 On-Behalf-Of 令牌资源不会改变。以下两种情况会绕过此设置:get-download-url 返回 Graph 预先验证的 SharePoint URL;/content 返回的 302 会直接指向 SharePoint,因此跟进这两种链接的一方都会直接连接到 SharePoint。如果你控制出口,请允许这些 SharePoint 主机,或禁用这些工具。不会从 .env 读取
  • LOG_LEVEL:设置日志级别(默认:'info')
  • SILENT=true|1:禁用控制台输出
  • MS365_MCP_REDACT_PII=false|0:禁用对日志消息中的 JWT、Bearer 标头、OAuth 令牌字段和电子邮件地址进行清理(默认:启用)。服务会处理实时的 Graph Bearer 令牌,因此除非你选择退出以进行完全详细的本地调试,否则会启用脱敏。
  • MS365_MCP_CLIENT_ID:自定义 Azure 应用客户端 ID(默认使用内置应用)
  • MS365_MCP_TENANT_ID:自定义租户 ID(多租户时默认值为 'common')。个人 Microsoft 帐户应将此项设为 consumers——截至 2026 年 6 月,通过默认 'common' 颁发机构签发的刷新令牌会在首次刷新时被拒绝,因此会话会在登录约一小时后失效
  • MS365_MCP_OAUTH_TOKEN:Microsoft Graph API 的现有 OAuth 令牌(BYOT 方式)
  • MS365_MCP_KEYVAULT_URL:用于机密管理的 Azure Key Vault URL(参见 Azure Key Vault 部分)
  • MS365_MCP_TOKEN_CACHE_PATH:MSAL 令牌缓存的自定义文件路径(参见下文“令牌存储”)
  • MS365_MCP_SELECTED_ACCOUNT_PATH:所选帐户元数据的自定义文件路径(参见下文“令牌存储”)
  • MS365_MCP_AUTH_CACHE_COMMAND:用于提供商无关的身份验证缓存存储的外部可执行文件包装器(参见下文“令牌存储”)
  • MS365_MCP_AUTH_CACHE_COMMAND_TIMEOUT_MS:每次调用 MS365_MCP_AUTH_CACHE_COMMAND 的超时时间(默认值为 10000)
  • MS365_MCP_EXPECTED_USERNAME:要求本地 MSAL 身份验证使用此 Microsoft 帐户用户名(不区分大小写;CLI 标志优先)
  • MS365_MCP_EXPECTED_HOME_ACCOUNT_ID:要求本地 MSAL 身份验证使用此确切的 MSAL homeAccountId(CLI 标志优先)

将邮件作为纯文本读取

read-mail-text 会以文本形式返回邮件,并移除大量杂项内容。Graph 的 纯文本正文仍然大多不是邮件本身:链接被 Safe Links 或跟踪重定向包裹,长度可达数百个字符;营销邮件会用不可见字符填充预标题;图片会变成 [https://…png] 行;回复会带上整段引用邮件。在真实邮箱中,20 封收件箱邮件通过 list-mail-messages 获取时约有 280,000 个字符,通过 read-mail-text 获取时约有 70,000 个字符。

规则是:移除明显的杂项内容,绝不截断或改写邮件;任何有歧义的内容都会保留。它会移除成串的不可见填充字符、图片占位符(方括号括起的图片文件地址),以及从邮件头开始的引用回复历史,包括带日期的 On … wrote:、以 > 引用的内容、Outlook 的 From:/Sent: 区块和 -----Original Message-----。转发内容会保留:主题以 FW: / Fwd: 或本地化前缀(VS:、WG:、TR:、RV:、I:、ENC: 等,也包括 [EXTERNAL] 等标签之后)开头,或者历史记录紧跟在 Forwarded message / Begin forwarded message: 标记下方。链接会保留: Safe Links、Proofpoint URL Defense 和 Google 重定向会还原为真实地址,跟踪参数(utm_* 等类似参数)会被移除,只有仍超过 300 个字符的地址(跟踪重定向)才会缩短为 ⟦link: domain⟧。换行符会变成 \n,标记括号为专用字符:正文自身的 ⟦ ⟧ 会显示为 [ ],因此文本无法伪装成标记。其他所有内容——签名、页脚、空白字符、重复链接——都会保持邮件原样。get-mail-message 会返回原始正文。

  • messageIds(最多 20 个,通过一个 $batch),或使用 folderId,并可选提供 top (最多 20)、filter 或 search。
  • 每封邮件最多截取 maxChars 个字符,默认值为 3,000(并标记为 truncated):这样一页 20 封邮件仍能放入结果限制,典型邮件也能完整保留。会在每次更新中重复历史记录的工单系统会将最新条目放在前面,因此截取后仍会保留最新条目。传入更大的 maxChars(最多 300,000;超过此长度的原始正文只会清理到该长度),即可获取长邮件全文;get-mail-message 会返回原始正文。
  • 返回 [{ id, from, to, cc, subject, received, hasAttachments, text, chars, truncated }]; 未知 ID 会返回 { id, error }。
  • 超过 MS365_MCP_READ_MAIL_TEXT_MAX_CHARS(默认值为 80,000)的结果会整体拒绝,并列出最大的邮件,以便 Agent 缩小查询范围,而不是悄悄只获取部分内容。

服务端生成的附件 URL

get-download-url 会返回 Microsoft 自有的预认证 @microsoft.graph.downloadUrl,用于 OneDrive 和 SharePoint 项目。对于邮件和日历附件、会议录制内容,以及任何其他 /$value 字节端点,Graph 不会发布此类 URL——读取这些内容的唯一方式一直是使用 download-bytes,将 base64 数据返回到 Agent 的上下文中。一个 73 KB、3 页的 PDF 这样就要消耗约 24,500 个 token,而且模型无论如何也无法解析它。

--enable-attachment-urls(HTTP 模式,默认关闭)弥补了这一缺口。当 Graph 没有自有 URL 时,get-download-url 会生成一个由此服务提供的 URL。启用后,get-download-url 会在每个 --preset 下注册(仅发送邮件的 teams-write 除外),因此,使用 --preset mail 的 Agent 列出附件后,就可以为其生成 URL。只有当预设的作用域涵盖相应资源时,生成的 URL 才有效:contacts、tasks 或 excel 预设没有 Mail 作用域,因此为邮件附件生成的 URL 在获取时会失败。

生成的 URL 如下所示:

GET /attachment?t=<ticket>&dgk=<key-id>&dgx=<expiry>&dgs=<signature>

票据由 CSPRNG 生成的 32 字节数据构成,只能使用一次,仅存储在内存中,并会在 MS365_MCP_ATTACHMENT_URL_TTL_S 秒后过期。兑换票据时,会以生成票据的身份从 Graph 流式传输字节数据;获取方无需发送 Authorization 标头,也无需持有 Microsoft 凭据。

这不会授予调用 Agent 原本不具备的任何权限。 所有可以生成 URL 的目标,都是同一账户上同一调用方通过 download-bytes 可以获取的目标。票据只是将这些字节移出上下文窗口,改为直接传输。

以相同方式上传附件

同一个票据存储也支持反向操作。get-upload-url 会生成一个 URL,用于向草稿消息或事件附加文件。这样,字节数据会从调用方直接传到 Graph,而不会以 base64 contentBytes 的形式经过 Agent 上下文:

PUT /attachment?t=<ticket>&dgk=<key-id>&dgx=<expiry>&dgs=<signature>
Content-Length: <file size>

<raw file bytes>

target 是项目的附件集合(草稿对应 /me/messages/{id}/attachments,事件对应 /me/events/{id}/attachments);附件会记录 name 和 contentType。小于 3 MB 的文件会直接附加;更大的文件最大可达 Graph 的 150 MB,会通过上传会话按 3.75 MiB 的分块流式传输,因此服务器最多只会持有一个分块。必须提供 Content-Length。票据只能使用一次,无论结果如何,首次提交后都会作废;上传时使用生成票据的身份,与下载时获取内容的身份完全相同。通过 GET 提交上传票据(或通过 PUT 提交下载票据)时,会像其他无效票据一样返回 404。

在 stdio 模式下附加本地文件

在 stdio 模式下,服务器和文件位于同一台机器,因此无需 URL。upload-attachment-from-file 接受相同的 target 和绝对路径 filePath,从磁盘读取文件并将其附加,限制同样为 3 MB 和 150 MB。它是 download-bytes-to-file 对应的上传工具。

在列出允许读取的目录之前,该工具处于关闭状态:

MS365_MCP_UPLOAD_DIRS=/Users/me/Documents/invitations:/Users/me/Scans

条目必须是绝对路径,并采用与 PATH 相同的分隔符(macOS 和 Linux 上为 :,Windows 上为 ;)。不在这些目录中的文件会被拒绝;符号链接会先解析,因此允许目录中的链接无法将读取位置指向其他地方。此变量不会从 .env 中读取。

在 HTTP 模式下,只有使用 --http-local-file-tools 才会注册该工具,与 download-bytes-to-file 一样。

配置
MS365_MCP_ATTACHMENT_URL_BASE=http://m365-mcp:3000   # required
MS365_MCP_ATTACHMENT_URL_KEY=...                     # optional (or _KEY_FILE=/path), see below
MS365_MCP_ATTACHMENT_URL_KEY_ID=1                    # optional, default 1; needs a key
MS365_MCP_ATTACHMENT_URL_TTL_S=120                   # optional, default 120, max 300

MS365_MCP_ATTACHMENT_URL_BASE 故意不设为 MS365_MCP_PUBLIC_URL:后者面向浏览器,用于 OAuth 重定向;而前者由服务端之间获取,通常是容器地址。设置缺失或格式错误时,服务会在启动时失败,而不是等到每次请求时才失败。

签名密钥为可选项。如果不设置,生成的 URL 只是 /attachment?t=<ticket>,任何 HTTP 客户端都能获取;仅凭 ticket 即可授权兑换。只有在获取方会验证 dgk/dgx/dgs 签名时(例如 docglean-mcp sidecar;它会拒绝无签名的 URL),才需要设置密钥。如果未设置密钥却设置了 _KEY_ID,或者 _KEY_FILE 为空或不可读,服务会在启动时失败。

分离附件监听器

默认情况下,/attachment 由与 /mcp 相同的 Express 应用在同一端口上提供服务。如果调用方通过 bearer token 进行身份验证,这样没问题;如果没有,这就会带来问题。在 --trust-proxy-auth 模式下,MCP 端点完全不读取 Authorization 标头——能访问即代表已通过身份验证——因此,共用端口意味着你允许通过来获取 PDF 的 sidecar 也能调用服务器上的所有工具。

--attachment-port <port>(或 MS365_MCP_ATTACHMENT_PORT)会将该路由移至独立监听器,而 --attachment-host <host>(或 MS365_MCP_ATTACHMENT_HOST)用于指定该监听器绑定的网络接口:

ms-365-mcp-server --http 10.89.0.2:3000 --trust-proxy-auth \
                  --enable-attachment-urls \
                  --attachment-port 3001 --attachment-host 10.89.1.2
MS365_MCP_ATTACHMENT_URL_BASE=http://m365-mcp:3001   # note: the attachment port
  • GET /attachment 在 3001 上可用;在 3000 上则返回 404——MCP 应用不会挂载该路由。
  • /mcp 在 3001 上返回 404,其他所有路径也一样:第二个应用只有附件路由,不包含其他内容。没有 OAuth 路由、请求体解析器、CORS 或健康检查。
  • 限制该路由的每分钟 60 次请求的限流器也会随路由移至新的监听器。
  • 附件监听器上的 trust proxy 处于关闭状态(且不会读取 MS365_MCP_TRUST_PROXY_HOPS),这与信任单跳代理的 MCP 监听器不同。此端口用于在容器网络上直接访问;如果服务器上唯一无需凭据的入口也接受 X-Forwarded-For,调用方就能自行选择限流计数桶。

此标志需要同时启用 --enable-attachment-urls,否则会拒绝启动——单独使用它会开放一个空端口,而操作人员却以为服务入口已分离。在 stdio 模式下,它会像其依赖的标志一样发出警告并被忽略。--attachment-host 同样需要 --attachment-port:单独使用它只会为一个不存在的监听器指定网络接口。

两个端口不等于两个界面,除非它们绑定到两个接口

这是决定前面的一切是否有意义的关键。 部署拆分方案前,请先阅读。

单独使用 --attachment-port 只会在_进程内_分离这两个界面,不会在_网络上_分离它们。不加 --attachment-host 时,附件监听器会继承 --http 绑定的主机;而常见的 --http 3000 并未指定主机,因此 Node 会绑定通配地址,两个端口都会在每个接口上响应:

ms-365-mcp-server --http 3000 --trust-proxy-auth \
                  --enable-attachment-urls --attachment-port 3001   # NOT isolated

容器网络会向对等方开放容器上的每个端口,而不是只开放一个端口。把文档转换 sidecar 放在共享网桥上,让它能在 3001 端口获取 /attachment,同一个 sidecar 也能连接 :3000/mcp——在 --trust-proxy-auth 下,这个端点完全不读取 Authorization 标头,并会返回完整的工具目录。不会发生任何故障,也不会记录任何错误,配置看起来与隔离配置一模一样。

要真正实现隔离,必须为两个监听器指定不同的地址,并且只将附件地址放到抓取器所在的网络上:

# docker compose — the MCP port on the agent's own bridge, the attachment port on the
# bridge shared with the converter. The converter can reach 3001 and cannot route to 3000.
services:
  m365-mcp:
    networks: { agent-net: { ipv4_address: 10.89.0.2 }, convert-net: { ipv4_address: 10.89.1.2 } }
    command: >
      --http 10.89.0.2:3000 --trust-proxy-auth
      --enable-attachment-urls
      --attachment-port 3001 --attachment-host 10.89.1.2
  docglean:
    networks: [convert-net]

这样,MCP 端口通过绑定而无法从 convert-net 访问——该接口上根本没有套接字在监听,而不是依赖必须持续保持匹配的防火墙规则。

如果使用 --trust-proxy-auth 和 --attachment-port,且两个监听器仍可通过一个共同接口访问(地址相同,或其中一个绑定到通配地址),服务会在启动时发出警告。日志会显示两个绑定地址,这些地址直接从套接字读取,而不是从标志读取,因此可以直接比较 Server listening on … 和 Attachment listener on …。

--attachment-host 接受裸 IPv4 地址、IPv6 地址(带方括号的 [::1] 或不带方括号的 ::1)或主机名。无效值会被拒绝,而不是强行转换——--attachment-host 10.0.0.5:3001 会报错并指出 --attachment-port,而不会绑定到其他地址。请注意,设置签名密钥后,MS365_MCP_ATTACHMENT_URL_BASE 不能是 IPv6 字面量(URL 签名涵盖主机,而这两种实现对 IPv6 的规范化方式不同);如果将监听器绑定到 IPv6 地址,请在基础 URL 中使用主机名。未设置密钥时则没有此限制。

将 MS365_MCP_ATTACHMENT_URL_BASE 指向附件端口。服务无法替你检查这一点:基础 URL 通常是当前进程无法解析的网络上的容器名称,因此端口填错会导致 sidecar 抓取失败,而不是在此处报错。基础 URL 和绑定端口会在启动时分别记录在相邻的两行中,便于直接比较。

签名,以及各方的检查职责

此服务绝不会在兑换时检查 dgk/dgx/dgs,这是有意为之。它们是为抓取器准备的:文档转换 sidecar 会拒绝连接私有地址,除非 URL 携带来自其已配置为信任的来源的有效 HMAC。此处授权兑换的是票据。验证返回请求中的签名,只能证明 URL 是由我们生成的——票据本身已经能证明这一点——同时还会让兑换过程依赖 sidecar 的时钟,以及密钥能否在重启后保留。

线上格式采用 docglean-mcp 的 signing.py(canonical_string),src/lib/url-signing.ts 是它的移植版本。规范字符串以 \n 连接,包含 v1、转为小写的 scheme、转为小写的主机、始终显式写出的端口、路径、移除 dgk/dgx/dgs 后对其余部分排序并重新编码的查询字符串,以及过期时间。test/attachment-url-signing.test.ts 中的测试向量已与 Python 实现逐字节核对——JavaScript 与 Python 在三个看似显而易见的地方存在差异(转义 !*'()、将 + 解码为空格,以及按码点而非 UTF-16 排序),因此需要进行这项核对,而不能想当然。

票据通过查询字符串而非路径传递,因为负责验证的 sidecar 会在错误消息中保留所抓取 URL 的路径,并剥离查询字符串。

读取字节所使用的身份

在 --trust-proxy-auth 模式下,服务会使用自身缓存的账户读取,兑换时也会再次查找该账户。

在普通的 --http 和 --obo 模式下,身份会随每个请求通过调用方的 Authorization 标头传入,而票据稍后由不会发送任何标头的获取器兑换。因此, 票据会保留签发请求所使用的 Graph 令牌(在 --obo 模式下为交换得到的令牌), 兑换时只使用该令牌读取数据,不使用其他任何信息。令牌会保留在服务内存中, 绝不会出现在 URL 中。

如果设置了 MS365_MCP_OAUTH_TOKEN,票据会改为保留该令牌,无论是否启用 --trust-proxy-auth。

以这种方式保留的令牌无法刷新。如果令牌在 URL 被获取前过期,兑换请求会返回 502。 客户端获得新令牌后,Agent 可以重新签发票据;过期的 MS365_MCP_OAUTH_TOKEN 则必须由操作员替换。

票据保存在签发它们的进程内存中,因此必须在同一实例上兑换 URL。在负载均衡器后面, 这意味着必须使用单个副本或启用粘性路由。

令牌存储

身份验证令牌存储在加密文件中(AES-256-GCM)。只有 32 字节的加密密钥会通过 keytar 存入操作系统凭据存储。

缓存本身太大,某些凭据存储无法容纳——Windows Credential Manager 的数据块上限为 2560 字节,而实际令牌缓存要大好几倍,因此在 Windows 上写入永远无法成功。无论登录了 多少个账户,密钥始终为 32 字节,因此所有平台上的工作方式都相同。

默认路径位于每位用户的配置目录中:

平台位置
Windows%APPDATA%\ms-365-mcp-server\
macOS~/Library/Application Support/ms-365-mcp-server/
Linux$XDG_CONFIG_HOME/ms-365-mcp-server/(或 ~/.config/ms-365-mcp-server/)

早期版本默认将缓存放在已安装的软件包内,而在 npx 下,这会解析到内容哈希缓存目录; 运行 npm cache clean 或升级版本都会将其删除。首次运行时,仍留在软件包目录中的缓存 会被移至新位置。

这适用于全局和本地安装,以及哈希未更改时的 npx。它无法访问留在_之前的_ npx 哈希目录中的缓存,因此升级 npx 安装时,需要最后再登录一次。从其他目录接管缓存 意味着信任一个本软件包无法证明由自身写入的目录,不值得为了少登录一次而这样做。

如有需要,可覆盖这些路径:

export MS365_MCP_TOKEN_CACHE_PATH="$HOME/.config/ms365-mcp/.token-cache.json"
export MS365_MCP_SELECTED_ACCOUNT_PATH="$HOME/.config/ms365-mcp/.selected-account.json"

父目录会自动创建。文件的权限设置为 0600。

没有凭据存储时(无头 Linux、大多数容器),密钥会以 0600 权限写入缓存文件旁边的 .cache-key。这样可以防止令牌出现在随手运行的 cat 输出、备份或意外提交中。但这无法 防范已经能读取该目录的人——密钥就在那里。如果需要将缓存存入真正的机密存储,请使用 下方的 MS365_MCP_AUTH_CACHE_COMMAND。

主动跳过凭据存储:

export MS365_MCP_USE_KEYTAR=0   # also accepts false, no or off

此时,密钥会在所有平台上都写入 .cache-key,与不存在凭据存储时完全相同,服务中的 任何代码都不会调用 keytar。如果凭据存储每次启动都会弹出提示,这种方式会很有用—— macOS 会在调用二进制文件发生变化时重新询问,而使用 npx 时,每次版本升级都会发生 变化——或者在你的平台上原生模块运行异常,而不只是无法加载。其他任何值都会继续使用 凭据存储;无法识别的值会发出警告,而不会被悄悄忽略。 关闭该功能会使某个缓存无法访问:该缓存使用的密钥已存储在凭据存储区中,但现在无法再访问这个密钥。服务器会提示这一点,并在下次登录时替换该缓存;这会注销其中所有账户,而不只是你重新登录的那个账户。如果想保留该缓存,请先取消设置此变量。

只有机器上没有任何东西能打开的缓存才会被替换。如果缓存解密失败,但可用密钥就在眼前——例如文件被截断、降级到旧版本,或缓存来自其他地方——这属于损坏,而不是无法访问的缓存;默认情况下,服务器会原样保留它。

服务器有两件事是刻意不会做的。无论是注销还是其他情况,它都不会删除已存入凭据存储区的内容,因为你刚要求它停止做的正是访问该存储区——如果想删除这些内容,请手动清除 ms-365-mcp-server 条目。另一个是,如果 .cache-key 存在但无法读取(例如绑定挂载的配置目录所有者不正确),服务器会将其视为可恢复,而不是缺失:它既不会覆盖缓存,也不会生成替代密钥,并会说明原因,而不是删除一个在修复权限后就能再次使用的密钥。请修复权限,或自行删除 .cache-key 以重新开始——这意味着需要再次登录。

如果缓存无法解密——密钥丢失、密钥链已锁定或文件被修改——系统会要求你重新登录,而不是让服务器无法启动。缓存文件会原样保留:不会被删除,也不会被这次新登录覆盖。仅仅处于锁定状态的密钥链通常会在下次启动时正常读取,届时缓存仍然存在。

代价是,在此期间不会保存新会话,因此每次启动都需要重新登录。如果密钥确实丢失,缓存也永远无法打开,请删除 .token-cache.json 以重新开始——日志会说明这一点,并给出路径。

托管/沙盒环境(例如 Anthropic Cowork):将 MS365_MCP_TOKEN_CACHE_PATH 和 MS365_MCP_SELECTED_ACCOUNT_PATH 设置为持久化挂载,以便令牌在不同会话之间保留。

外部身份验证缓存命令

无头本地 MSAL 部署可以使用与提供商无关的外部命令,替代内置的 keytar/文件存储:

export MS365_MCP_AUTH_CACHE_COMMAND="/path/to/ms365-auth-cache-store"
export MS365_MCP_AUTH_CACHE_COMMAND_TIMEOUT_MS=10000

为本地身份验证流程设置 MS365_MCP_AUTH_CACHE_COMMAND 后,服务器只会使用该命令存储 MSAL 令牌缓存和所选帐户元数据,不会回退到 keytar 或本地文件。如果命令路径不存在、在 POSIX 上不可执行、退出状态非零、超时或返回格式错误的数据,身份验证缓存操作将以安全失败的方式终止,并显示经过净化的错误消息。

该值必须是实际的可执行文件包装器路径。它不是 shell 命令字符串,也没有配套的 args 环境变量。请将任何解释器、区域、配置文件或特定于提供商的设置放入包装器中。Windows 用户应将该变量指向可由 Node 直接启动、无需 shell 解析的包装器可执行文件或脚本。

服务器会使用以下参数调用包装器:

$MS365_MCP_AUTH_CACHE_COMMAND load token-cache
$MS365_MCP_AUTH_CACHE_COMMAND save token-cache
$MS365_MCP_AUTH_CACHE_COMMAND delete token-cache
$MS365_MCP_AUTH_CACHE_COMMAND load selected-account
$MS365_MCP_AUTH_CACHE_COMMAND save selected-account
$MS365_MCP_AUTH_CACHE_COMMAND delete selected-account

协议 v1:

  • load <key> 不读取 stdin。若存在,返回退出状态 0,并输出 {"found":true,"value":"<stored envelope string>"}。若未找到,返回退出状态 0,并输出 {"found":false} 或空 stdout。
  • save <key> 从 stdin 接收 {"value":"<stamped envelope string>"},且必须在值已持久提交后才返回退出状态 0。v1 不支持即发即忘或合并保存。
  • delete <key> 不读取 stdin,无论键是否存在,均返回退出状态 0。
  • <key> 为 token-cache 或 selected-account。
  • 任何非零退出状态都表示存储错误。缓存未命中时不要使用退出代码 2。
  • Stderr 会被捕获,并在经过净化的错误消息中截断。服务器绝不会记录 stdin 和 stdout 的载荷。
  • 令牌缓存载荷可能很大;包装器应至少能处理 256 KB 的值。

常规的无状态 HTTP Graph 请求不会使用本地身份验证缓存存储。在 HTTP 模式下,除非显式启用本地身份验证工具,或使用 --login、--verify-login、--list-accounts、--select-account 或 --logout 等本地帐户命令,否则启动时和每个请求中都会跳过命令存储。

Azure Key Vault 集成

在生产部署中,你可以将机密存储在 Azure Key Vault 中,而不是环境变量中。对于使用托管标识的 Azure Container Apps,这尤其有用。

设置
  1. 创建 Key Vault(如果尚未创建):

    az keyvault create --name your-keyvault-name --resource-group your-rg --location eastus
    
  2. 将机密添加到 Key Vault:

    az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-client-id --value "your-client-id"
    az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-tenant-id --value "your-tenant-id"
    # Optional: if using confidential client flow
    az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-client-secret --value "your-secret"
    
  3. 授予对 Key Vault 的访问权限:

    对于使用托管标识的 Azure Container Apps:

    # Get the managed identity principal ID
    PRINCIPAL_ID=$(az containerapp show --name your-app --resource-group your-rg --query identity.principalId -o tsv)
    
    # Grant access to Key Vault secrets
    az keyvault set-policy --name your-keyvault-name --object-id $PRINCIPAL_ID --secret-permissions get list
    

    对于使用 Azure CLI 的本地开发:

    # Your Azure CLI identity already has access if you have appropriate RBAC roles
    az login
    
  4. 配置服务器:

    MS365_MCP_KEYVAULT_URL=https://your-keyvault-name.vault.azure.net npx @softeria/ms-365-mcp-server
    
机密名称映射
Key Vault 机密名称环境变量必需
ms365-mcp-client-idMS365_MCP_CLIENT_ID是
ms365-mcp-tenant-idMS365_MCP_TENANT_ID否(默认为 'common')
ms365-mcp-client-secretMS365_MCP_CLIENT_SECRET否
身份验证

Key Vault 集成使用 Azure Identity SDK 中的 DefaultAzureCredential,它会按顺序自动尝试多种身份验证方式:

  1. 环境变量(AZURE_CLIENT_ID、AZURE_CLIENT_SECRET、AZURE_TENANT_ID)
  2. 托管标识(推荐用于 Azure Container Apps)
  3. Azure CLI 凭据(用于本地开发)
  4. Visual Studio Code 凭据
  5. Azure PowerShell 凭据
可选依赖项

Azure Key Vault 包(@azure/identity 和 @azure/keyvault-secrets)是可选依赖项。只有在配置 MS365_MCP_KEYVAULT_URL 时才会加载这些包。如果不使用 Key Vault,则无需安装这些包。

消息署名

可为发出的消息添加可配置的署名(例如 🤖 前缀),让接收者区分由 Agent 发送的消息和你亲自输入的消息。默认关闭,可通过 --message-signoff-prefix / --message-signoff-suffix 启用(环境变量:MS365_MCP_MESSAGE_SIGNOFF_PREFIX / MS365_MCP_MESSAGE_SIGNOFF_SUFFIX);--no-message-signoff 或空环境变量值可将其关闭。

配置后,该署名会应用于所有 Teams 消息(发送、回复和编辑,包括通过 graph-batch 执行的操作)、直接邮件发送(send-mail、回复/转发、对应的共享邮箱变体,以及群组会话回复),并会在撰写邮件草稿内容时添加。send-draft-message 会按原样发送草稿,因此你亲自撰写的草稿发出时不会被改动。已带有标记的消息不会重复添加署名;如果消息正文无法添加署名,则会拒绝发送,而不是不带署名发送。

标记可以包含标记语言(例如带颜色的 <span>),只要渲染后能显示文本即可。请注意,署名是防止 Agent 滥用所提供工具的一项防护措施,并非严格的安全边界——拥有同一台机器上的 shell 访问权限的 Agent 可以在不启用署名的情况下直接重启服务。

生产环境部署

请参阅 docs/deployment.md,了解如何托管服务以供整个组织访问的完整指南,内容包括 Docker、Azure Container Apps、Azure App Service、Azure AD 应用注册、反向代理配置、客户端配置和公开的端点。

贡献

欢迎贡献!提交拉取请求前,请确保你的更改符合我们的质量标准。

运行验证脚本,检查所有代码质量要求:

npm run verify
面向开发者

克隆代码库后,你可能需要根据 Microsoft Graph OpenAPI 规范生成客户端代码:

npm run generate

相关项目

  • ms-365-admin-mcp-server,由 @okapi-ca 开发:面向管理员/守护进程场景的配套服务,使用应用程序权限(客户端凭据流),涵盖安全警报、审计日志、服务运行状况和使用情况报告。

支持

如果你遇到问题或需要帮助:

许可证

MIT © 2026 Softeria

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。