Skip to main content
Glama
H1er0

Azure Files MCP

by H1er0

Azure Files MCP(只读)

一个远程 MCP 服务器,让 Claude 对 Azure Files SMB 共享上的一个文件夹具有只读访问权限,每个连接的用户只能看到其自身 NTFS 权限所允许的内容。

只有两个工具,没有其他:

  • list_directory(path) - 列出配置的根文件夹下的文件/文件夹。

  • read_file(path) - 读取配置的根文件夹下的文件内容。纯文本文件按原样返回;PDF、Word(.docx)和 Excel(.xlsx)会自动转换为文本(请参阅下面的“读取 PDF/Word/Excel 文件”)。

此代码库中没有任何写入、删除或重命名工具——不是存根,不是通过配置禁用,而是根本不存在。

有关 Azure/Entra 设置的分步说明,请参阅 SETUP.md。本文件涵盖架构和设计决策;SETUP.md 涵盖点击式门户演练。

为什么这并不完全符合最初阅读工单时的预期

最初的设计假设:分配非特权 Storage File Data SMB Share Reader RBAC 角色,然后使用每个用户自己的 OAuth 令牌调用 Azure Files 的 FileREST API,Azure 会自动按用户强制执行 NTFS ACL。

这行不通。已直接对照 Microsoft 的 REST API 文档验证(使用 Microsoft Entra ID 授权(REST API)):每个 FileREST 读取操作(列出目录和文件、获取文件、获取文件属性等)都需要 .../files/read 和 .../readFileBackupSemantics/action 两者。readFileBackupSemantics/action——Microsoft 自己的术语,表示一种明确跳过 NTFS ACL 评估的模式——仅由 Storage File Data Privileged Reader/Contributor 授予。Storage File Data SMB Share Reader 根本不出现在 REST 权限表中——它仅适用于真正的 SMB 协议连接(端口 445、Kerberos),而调用 FileREST 的 Node HTTPS 后端无法使用。

因此,通过 REST,角色要么是特权(绕过 ACL),要么无关紧要。无法让 Azure 本身在每次请求的 REST/OAuth 基础上强制执行 NTFS ACL。

此服务器改为: 使用 Storage File Data Privileged Reader(仍然是只读,仍然按用户认证——见下文),并在代码中自行强制执行 NTFS 权限,使用 Azure Files 为每个文件/文件夹公开的真实安全描述符:

  1. 将文件/文件夹的 NTFS 权限作为 SDDL 字符串获取(getPermission REST 调用)。

  2. 将 DACL 解析为单独的 ACE(src/acl/sddl.ts——手工编写;不存在维护良好的 Node/TS 库)。

  3. 通过 Microsoft Graph(src/graph/sidResolver.ts)解析调用用户自己的本地 AD SID 以及他们传递所属的每个组 SID。

  4. 使用真实的 Windows AccessCheck 语义针对该 SID 集评估 DACL——显式拒绝优先于显式允许,未提及的位默认拒绝(src/acl/evaluate.ts)。

这确实是按用户强制执行——只是在这里实现,而不是委托给 Azure 的 RBAC 层,因为 Azure 没有可 REST 调用的机制来为你完成。真正的安全边界是此代码库,而不是 Azure RBAC——在推理下面的任何内容时请记住这一点。

Related MCP server: af-filesystem-mcp

架构

此服务器是它自己的 OAuth 2.1 授权服务器——Claude 从不直接与 Entra 通信。这是一个深思熟虑的设计选择,而不是显而易见的选择,所以值得解释原因:MCP 规范要求客户端发送一个等于 MCP 服务器自身 URL 的 RFC 8707 resource 参数,而 Entra 只接受与应用程序注册上的 已验证 标识符 URI 匹配的 resource 值。Entra 明确拒绝将任何 *.azurewebsites.net URL 注册为已验证域——已通过 AADSTS9010010 实时确认,并且无法通过任何门户设置修复。因此,Claude 改为针对 此服务器 进行授权(其自身 URL 轻松满足资源检查),而服务器在后台将真实登录中继到 Entra(src/auth/mcpOAuthProvider.ts),并将 Entra 真实、未修改的访问令牌交还给 Claude。在该交接之后,一切都使用正常的 Entra 颁发的持有者令牌,就像原本一样。

每个读取请求:

  1. Claude 通过此服务器自己的 /authorize 和 /token 端点(src/auth/mcpOAuthProvider.ts)进行授权,这些端点通过 /oauth/callback 路由将实际登录中继到 Entra,并交还 Entra 的真实访问令牌(受众 = 此应用的 Entra 应用注册)。此服务器仅在传入请求上 验证 令牌(src/auth/tokenVerifier.ts——通过 Entra 的 JWKS 验证签名、颁发者、受众、过期时间)——它自己从不颁发或签署令牌。

  2. path 在 任何 Azure 调用之前 被规范化并对照配置的根文件夹进行检查(src/files/pathScope.ts)——解析到根目录之外的路径会被拒绝,无论令牌原本允许什么。

  3. 传入的用户令牌通过 OAuth2 代表流(src/auth/obo.ts,@azure/identity 的 OnBehalfOfCredential)交换为范围限定为 https://storage.azure.com/.default 的新令牌。每次 Azure Files 调用都使用此按用户令牌(src/files/shareClient.ts)——绝不使用共享服务主体或静态密钥。

  4. 获取文件/目录的 NTFS 权限,并针对用户持有的 SID 进行评估(src/graph/sidResolver.ts + src/acl/)。如果未授予访问权限,工具返回权限拒绝错误,不返回其他内容。

  5. 仅当授予访问权限时,工具才返回步骤 3 中已获取的目录列表或文件内容——对于 PDF/Word/Excel,先转换为纯文本(见下文)。

  6. 每次调用——授予、拒绝或出错——都会发出一个结构化审计日志行(src/audit/log.ts),记录用户、请求的路径和结果。请参阅下面的“审计日志记录”。

解析用户自己的 SID/组 SID 使用 仅应用 Graph 客户端凭据调用(src/graph/sidResolver.ts),而不是 OBO。这是故意的:它是身份元数据(此用户属于哪些组),而不是文件数据,因此那里的共享应用身份不会违反“绝不使用共享凭据读取文件数据”——实际的 Azure Files 读取在整个过程中严格按用户进行。

因为 resolveHeldSids 在每次 list_directory/read_file 调用时都会运行,其结果(用户自己的 SID 加上每个传递组 SID)按用户缓存在内存中,持续 SID_CACHE_TTL_MS(默认 5 分钟,请参阅 .env.example)——缓存命中完全跳过 Microsoft Graph。组成员身份变化很少,这大大减少了每次请求的延迟和 Graph 负载,而不会显著扩大权限更改的过时窗口。设置 SID_CACHE_TTL_MS=0 以禁用缓存(例如,在调试未显示的权限更改时)。

步骤 1 中的 OAuth 中继在两个短生命周期、单次使用的内存映射(mcpOAuthProvider.ts 中的 pendingAuthorizations、issuedCodes)中跟踪进行中的登录。这对于单个 App Service 实例来说没问题,但意味着 此服务器在将状态移动到共享存储(例如 Redis)之前,不得扩展到超过一个实例——第二个实例会随机失败在另一个实例上开始但在该实例上完成的登录。

读取 PDF/Word/Excel 文件

read_file 在服务器端将几种常见的二进制文档格式转换为纯文本(src/files/textExtract.ts),因为渲染此连接器结果的 MCP 客户端本身无法解析二进制“资源”blob 供 Claude 推理——只有文本内容在聊天中实际可读。处理:.pdf、.docx、.xlsx。不处理:旧版二进制 .doc/.xls(2007 年之前的 Office 格式),这些格式回退为返回不可读的 blob,以及扫描/仅图像 PDF,这些返回清晰的“无可提取文本”消息而不是垃圾(无 OCR)。提取输出独立于原始文件大小上限(MAX_READ_FILE_BYTES)进行限制,因为密集电子表格的文本形式可能超过其二进制大小。

审计日志记录

读取访问完全在此服务器自己的代码中强制执行(见上文),而不是由 Azure RBAC 强制执行,因此其他地方没有审计跟踪——此服务器的审计日志(src/audit/log.ts)就是它。每次 list_directory/read_file 调用,无论结果如何,都会向 stdout 发出恰好一行 JSON:时间戳、工具名称、请求的路径、调用用户的 oid 和 UPN、决策(granted / denied / error)、除 granted 之外的任何原因,以及调用耗时。它作为普通的 console.log JSON 行编写,而不是通过日志库,因此它流入部署目标已经收集 stdout 的任何日志管道(例如 Azure App Service 的日志流 / Log Analytics),无需额外接线。

所需的 Azure/Entra 配置(此仓库未自动化)

完整的点击式步骤在 SETUP.md 中。实际需要的摘要:

  • RBAC:将 Storage File Data Privileged Reader(只读;不要 使用 Contributor)分配给其成员应能使用此连接器的 Entra 组,范围限定到存储帐户本身。这取代了原始工单中的 SMB Share Reader 角色——请参阅上面的理由。这是一个粗略的“此人能否提问”的门槛,而不是真正的权限检查——真正的 NTFS 权限(在代码中强制执行,见上文)仍然控制每个用户实际看到的内容。

  • 应用注册:一个应用注册承担三项工作——Claude 的 OAuth 客户端、到 Azure 存储的 On-Behalf-Of 交换的身份,以及(通常)Graph 的仅应用身份。它需要:

    • 公开 API:应用程序 ID URI api://<client-id>(默认),并有一个名为 access_as_user 的范围。

    • 身份验证:恰好一个 Web 重定向 URI,<PUBLIC_BASE_URL>/oauth/callback——此服务器自己的回调,而不是 Claude 的。Claude 的平台范围回调(https://claude.ai/api/mcp/auth_callback)根本不会在 Entra 中注册;原因请参阅上面的“架构”。

    • 一个 客户端密钥。

    • 此服务器不支持动态客户端注册——它只识别一个客户端(此应用注册自己的客户端 ID/密钥),这也是你在 Claude 中将其添加为自定义连接器时配置的 OAuth 客户端 ID/密钥。

  • Graph API 权限(应用程序,管理员同意)在 GRAPH_CLIENT_ID 指向的任何应用注册上:User.Read.All 和 GroupMember.Read.All(或更广泛的 Directory.Read.All)——需要读取用户的 onPremisesSecurityIdentifier 及其传递组成员身份。

配置

所有设置都是环境变量——请参阅 .env.example 和 SETUP.md 中的完整参考表。对于重用来说重要的一项:ROOT_PATH(加上 STORAGE_ACCOUNT_NAME/SHARE_NAME)是 唯一 需要更改以将此服务器重新指向不同文件夹、共享或客户端的内容。它在进程启动时读取一次,并且永远不会作为工具参数接受,因此调用者无法在运行时扩大范围。

要重新指向不同的文件夹/共享:

  1. 在 App Service 配置中更新 STORAGE_ACCOUNT_NAME、SHARE_NAME、ROOT_PATH。

  2. 确保目标 Entra 组在新存储帐户上具有 Storage File Data Privileged Reader。

  3. 重启应用。无需代码或构建更改。

本地运行

npm install
cp .env.example .env   # fill in real values
npm run dev

构建、类型检查、测试

npm run build   # tsc type-check + emit to dist/
npm test        # vitest - sddl parser, ACE evaluator, path-scope, SID cache, audit log unit tests

部署到 Azure App Service

有关完整演练,请参阅 SETUP.md。简短版本:

  1. 将 src/、package.json、package-lock.json 和 tsconfig.json 打包为 zip——绝不包含预构建的 dist/ 或 node_modules/。Azure 的 Oryx 构建器会在每次部署时在服务端重新编译(需要应用设置 SCM_DO_BUILD_DURING_DEPLOYMENT=true)。

  2. 将 zip 部署到 Linux App Service 计划,Node 20+,恰好一个实例(参见上文"架构"中的内存 OAuth 中继状态说明)。

  3. 将 .env.example 中的所有变量设置为 App Service 应用程序设置(而不是提交 .env 文件)。

  4. PUBLIC_BASE_URL 必须是 App Service 的真实 HTTPS URL,不带尾部斜杠——否则生成的 URL 中会出现双斜杠,并破坏 Entra 重定向 URI 匹配。

  5. 在连接 Claude 之前进行验证:GET /healthz 返回 ok,且 GET /.well-known/oauth-protected-resource/mcp 返回 JSON 元数据文档(根据 RFC 9728,/mcp 后缀是必需的,因为资源服务器 URL 本身包含 /mcp 路径组件)。

  6. Claude 连接的 MCP 端点是 POST {PUBLIC_BASE_URL}/mcp。

此服务器手工实现 OAuth(JWT 验证、/.well-known/oauth-* 元数据端点,以及现在通过 MCP SDK 的 mcpAuthRouter 实现的完整授权服务器中继),而不是依赖 App Service 内置的"Easy Auth"MCP 集成。该集成是真实存在的,但仍处于预览阶段,而且微软官方文档明确警告不要将其已验证的令牌转发给下游资源——无论哪种方式,你仍然需要自己为 Storage 编写 on-behalf-of 交换逻辑,所以它并不会减少这里的代码量,只会增加预览阶段的风险。

已知限制

  • 域本地 AD 组可能无法解析。 NTFS ACL 与本地 AD SID 匹配,通过 Microsoft Graph 的 onPremisesSecurityIdentifier 解析。域本地组无法可靠地同步/回写到 Entra ID,因此授予未同步的域本地组访问权限的 ACE 无法被匹配。此失败是闭合的:未解析的组 SID 永远无法满足 ALLOW ACE,所以最坏的情况是用户看到的权限少于其应得的,绝不会多(src/graph/sidResolver.ts、src/acl/evaluate.ts)。如果目标文件夹的 ACL 使用了域本地组,请在测试期间验证这不会导致真实用户权限不足;如果确实如此,修复方法是改用通用/全局组重新设置 ACL,或添加 LDAP 回退查找(此处未实现)。

  • 对祖先文件夹的目录遍历(FILE_TRAVERSE)未单独检查。 在大多数真实部署中,Windows 默认授予 Authenticated Users"绕过遍历检查"权限,因此这与典型的真实行为一致,但如果客户环境中共享根目录与 ROOT_PATH 之间的文件夹存在非默认的遍历限制,请在测试期间重新验证。

  • 仅限单个 App Service 实例——参见上文"架构"中的 OAuth 中继说明。

  • 不支持写入/删除/重命名,这是设计使然——不是缺陷,而是有意的约束。

  • 旧版二进制 .doc/.xls 和扫描件/纯图片 PDF 无法读取——参见上文"读取 PDF/Word/Excel 文件"。

  • 要求目标租户的本地 AD 已同步到 Entra(Entra Connect / Cloud Sync),且本地 SID 能够流通——整个按用户的 NTFS 强制模型都依赖于此。对于没有本地 AD 的纯云 Entra 原生租户,此方案无法工作。

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables KUIK Agent to access on-premise SMB/CIFS shares on a Windows Server using the employee's own credentials, enforcing live ACLs. Supports file operations such as list, read, write, search, move, copy, delete, and upload/download without mounting or duplicating data.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Gives Analysis Facility users read-only browse, read, stat, and grep access to their own files on shared NFS home and Ceph data areas, with kernel-enforced per-user permissions.
    48 PyPI
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables read-only file-management operations from AI assistants, including health checks, connection and folder listings, search, share-link resolution, metadata retrieval, and on-demand document reading.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables read-only access to private filesystem workspaces through MCP, allowing clients to list, inspect, read, and search files without uploading the workspace or opening inbound firewall ports.
    1
    MIT