Filesystem MCP Server 深度解析:目录访问控制、完整文件操作工具 API 与客户端部署实践
【免费下载链接】serversModel Context Protocol Servers项目地址: https://gitcode.com/GitHub_Trending/se/servers
本文为 MCP(Model Context Protocol)参考服务器集合中 Filesystem 服务器的完整技术指南。基于该服务器的官方文档与源码,你将掌握:如何为 LLM 客户端(如 Claude Desktop、VS Code)安全地暴露受控的文件系统访问能力,如何通过命令行参数与 MCP Roots 协议两种机制配置目录白名单,全部 13 个文件操作工具的完整参数与行为语义,以及路径校验、符号链接防护、原子写入等源码级安全实现细节。
一、项目定位:运行于 stdio 的文件系统 MCP 服务器
Filesystem 服务器是一个 Node.js 实现的 MCP 服务器,专为受控的文件系统操作而设计,发布在 npm 上的包名为@modelcontextprotocol/server-filesystem可以看到,当前版本为 0.6.3,可执行入口mcp-server-filesystem指向dist/index.js,核心依赖包括 MCP SDK(@modelcontextprotocol/sdk ^1.30.0)、用于生成 git 风格 diff 的diff库,以及用于 glob 模式匹配的minimatch。
它提供的核心能力(继承自 README 的 Features 清单):
- 读写文件(Read/write files)
- 创建/列出/删除目录(Create/list/delete directories)
- 移动文件或目录(Move files/directories)
- 文件搜索(Search files)
- 获取文件元数据(Get file metadata)
- 通过 MCP Roots 协议实现动态目录访问控制(Dynamic directory access control)
服务器通过StdioServerTransport以 stdio 方式与客户端通信,在 index.ts 的runServer()中完成连接,服务名称在代码中为secure-filesystem-server。
二、目录访问控制:两种配置方式与完整工作流程
安全是该服务器设计的核心:所有文件操作都被严格限制在“允许目录(allowed directories)”之内。目录白名单有两条配置途径。
2.1 方式一:命令行参数
启动服务器时直接传入允许的目录:
mcp-server-filesystem /path/to/dir1 /path/to/dir2从源码 index.ts 可以看到,每个参数会经过expandHome(展开~)、path.resolve(转绝对路径)、normalizePath(规范化,含 Windows/WSL 特殊处理)三步处理,然后通过fs.realpath解析符号链接,同时保留原始路径与解析后路径两份记录——这是为了兼容 macOS 上/tmp -> /private/tmp这类符号链接场景,使用户配置/tmp时仍能匹配到解析后的/private/tmp。
启动时的目录筛选逻辑(index.ts):
- 只保留真实存在且是目录的路径;
- 不可访问的目录仅输出警告并跳过;
- 只有当所有指定目录都不可访问时,才以退出码 1 终止进程。
这一行为有专门的测试佐证:startup-validation.test.ts 验证了“部分目录不可访问时跳过并继续运行”“全部不可访问时报错退出”两种场景。
2.2 方式二:MCP Roots(官方推荐)
支持 Roots 协议的 MCP 客户端可以动态更新允许目录。关键语义是:客户端通知的 Roots 会完全替换(completely replace)服务端已配置的允许目录,而不是与之合并。
重要约束:如果服务器启动时没有命令行参数,且客户端不支持 Roots 协议(或提供的 roots 为空),服务器将在初始化阶段抛出错误拒绝服务。
Roots 路径解析由 roots-utils.ts 的getValidRootDirectories完成:将file://URI 或纯路径(支持~)解析为真实目录路径,逐条验证“存在且是目录”,无效项仅记录日志跳过。
2.3 访问控制完整流程(How It Works)
官方文档(README)描述了完整的五阶段流程,与源码实现一一对应:
- 服务器启动:从命令行参数加载目录;无参数时以空允许目录启动。
- 客户端连接与初始化:客户端发送
initialize请求携带 capabilities;服务器检查客户端是否声明capabilities.roots。 - Roots 协议处理(客户端支持 roots 时):
- 初始化阶段:服务器通过
roots/list主动向客户端请求 roots; - 客户端返回其配置的 roots 后,服务器用客户端 roots 替换全部允许目录;
- 运行期更新:客户端可发送
notifications/roots/list_changed,服务器重新拉取并替换允许目录——无需重启即可切换工作目录。
- 初始化阶段:服务器通过
- 回退行为(客户端不支持 roots):服务器仅使用命令行目录,且无法动态更新。
- 访问控制:所有文件操作被限制在允许目录内;可使用
list_allowed_directories工具查看当前目录;服务器要求至少一个允许目录才能工作。
对应的源码位置:
- 初始化时的 roots 拉取与“无目录即报错”逻辑在 index.ts 的
server.server.oninitialized回调中——当客户端不支持 roots 且allowedDirectories为空时,会抛出 “Server cannot operate: No allowed directories available...” 错误; - 运行期
roots/list_changed通知处理在 index.ts,收到通知后调用listRoots()重新拉取并整体替换白名单; - 每次 roots 更新都会同步调用
setAllowedDirectories刷新 lib.ts 中维护的全局状态。
2.4 路径校验的核心算法
所有工具在触碰文件系统前都必须先经过 lib.ts 的validatePath,其三层校验策略是:
- 白名单前缀校验:由 path-validation.ts 的
isPathWithinAllowedDirectories实现。它先拒绝空输入与\x00空字节,再将请求路径与允许目录分别path.resolve + normalize,最后要求请求路径“等于允许目录”或“以允许目录 + 路径分隔符开头”。这个+ path.sep的细节专门防御前缀攻击(如允许/home/user/project时拦截/home/user/project2),path-validation.test.ts 中有对应的 "blocks similar directory names (prefix vulnerability)" 用例验证/home/user/project_backup、/home/user/projectile等均被拒绝。 - 符号链接解析校验:对请求路径执行
fs.realpath,确认链接的真实目标仍在允许目录内,防止通过符号链接逃逸出沙箱。 - 新建文件的父目录校验:若目标文件尚不存在(
ENOENT),改为校验其父目录的真实路径,从而保证不能通过“创建新文件”在授权位置之外落盘。
此外,相对路径的处理也有专门逻辑(lib.ts):相对路径会依次相对每个允许目录解析,取第一个落在白名单内的结果;全部失败时回退到第一个允许目录作为基准,避免相对路径成为绕过白名单的通道。
三、完整工具 API 参考
服务器共注册 13 个工具(含 1 个已弃用的兼容工具)。以下逐项覆盖官方文档的全部参数说明,并补充源码中的行为细节。
3.1 读取类工具
read_file(已弃用)
- 行为:读取文件完整文本内容;
- 源码中保留该工具仅为向后兼容,描述明确标注 “DEPRECATED: Use read_text_file instead”,且与
read_text_file共用同一个 handler(index.ts)。
read_text_file
- 读取文件完整内容作为文本,无论扩展名如何一律按 UTF-8 文本处理;
- 输入参数:
path(string)head(number, 可选):只读前 N 行tail(number, 可选):只读最后 N 行
head与tail不可同时指定,源码 handler 中会直接抛出错误(index.ts);- 性能实现值得注意:
tail采用从文件末尾按 1KB 分块向前读的流式算法(lib.ts 的tailFile),head则从文件头部逐块读取并累积完整行(headFile),因此即使处理 GB 级大日志也只需读取必要的尾部/头部数据,而不必将整个文件载入内存。
read_media_file
- 输入:
path(string); - 读取文件并以 base64 编码内容块 + MIME 类型返回。MIME 映射表在 index.ts 中硬编码:支持
.png/.jpg/.jpeg/.gif/.webp/.bmp/.svg与.mp3/.wav/.ogg/.flac,其余扩展名一律回退为application/octet-stream; - 返回形态按 MCP 规范区分:图片/音频返回
image/audio内容块;其他二进制文件返回内嵌resource块(含uri、mimeType、blob),因为规范的内容块联合类型不允许type:"blob"。文件通过readFileAsBase64Stream以流式方式读取拼接后再整体 base64 编码。
read_multiple_files
- 输入:
paths(string[]),至少 1 个路径; - 并发读取多个文件,单个文件读取失败不会中断整体操作——失败项以
路径: Error - 错误信息的形式出现在结果中,各文件内容以\n---\n分隔(index.ts)。
3.2 写入与编辑类工具
write_file
- 创建新文件或完全覆盖已有文件(官方提示 exercise caution with this);
- 输入:
path(string) 文件位置、content(string) 文件内容; - 底层实现(lib.ts)先以
wx独占创建标志写入以防穿过已存在的符号链接;若文件已存在(EEXIST),则写入随机十六进制命名的临时文件后再用fs.rename原子替换目标——rename 不会跟随符号链接,从而堵住“校验后、写入前被替换为符号链接”的竞态窗口。
edit_file
- 基于模式匹配的精细化行级编辑,输入:
path(string):目标文件edits(array):编辑操作列表,每项含oldText(string) 待查找文本(可为子串)、newText(string) 替换文本dryRun(boolean):只预览不应用(默认 false)
- 核心能力(由 lib.ts 的
applyFileEdits实现):- 优先精确子串匹配;未命中时退化为逐行匹配,比较时忽略行首尾空白差异(trim 后比较),支持行级与多行内容匹配;
- 命中后保留原文首行缩进,后续行按 oldText 与 newText 的相对缩进差换算,实现“空白规范化 + 缩进保持”;
- 多个编辑顺序应用,位置自动校正;
- 返回 git 风格的 unified diff(含上下文),diff 代码块会根据内容中反引号数量自动加长围栏避免渲染冲突;
- 应用阶段同样采用临时文件 + 原子 rename 的写入策略。
- 最佳实践:官方文档明确建议先以
dryRun: true预览变更再正式应用。由于idempotentHint为false(见下文注解表),重复应用同一批编辑可能失败或重复生效。
create_directory
- 输入:
path(string); - 创建新目录并确保其存在,自动创建所需的父目录;目录已存在时静默成功;
- 实现为
fs.mkdir(validPath, { recursive: true })(index.ts)。
move_file
- 输入:
source(string)、destination(string); - 移动/重命名文件或目录,跨目录移动与同目录重命名均可;
- 目标已存在时操作失败(底层为
fs.rename,不做覆盖)。
3.3 查询类工具
list_directory
- 输入:
path(string); - 列出目录内容,每项带
[FILE]或[DIR]前缀,用于区分文件与目录。
list_directory_with_sizes
- 输入:
path(string):要列出的目录sortBy(string, 可选):按"name"或"size"排序(默认"name")
- 返回带文件大小的详细列表与汇总统计:文件总数、目录总数、总大小(
formatSize以 B/KB/MB/GB/TB 展示,lib.ts);按 size 排序时为降序(index.ts);单个条目 stat 失败时该项大小记为 0 而不中断整体。
search_files
- 输入:
path(string):搜索起始目录pattern(string):glob 风格搜索模式(如*.ext匹配当前目录,**/*.ext匹配所有子目录)excludePatterns(string[]):排除模式
- 递归遍历,用
minimatch对“相对起始目录的路径”做匹配(lib.ts),返回所有匹配的完整路径;遍历过程中每个条目都再次经过validatePath,确保搜索不会跨越白名单。
directory_tree
- 输入:
path(string):起始目录excludePatterns(string[]):排除模式,支持 glob
- 返回递归 JSON 数组,每个条目包含:
name(string):文件/目录名type('file'|'directory'):条目类型children(array):仅目录拥有;空目录为空数组,文件则无此字段
- 输出以 2 空格缩进格式化(
JSON.stringify(treeData, null, 2),index.ts)。排除匹配对含*的模式直接做 glob 匹配,对精确名称模式额外尝试**/pattern与**/pattern/**以兼容“按名称在任意层级排除”的用法(index.ts)。
get_file_info
- 输入:
path(string); - 返回详细元数据:大小、创建时间(birthtime)、修改时间(mtime)、访问时间(atime)、类型(文件/目录)、权限(mode 的低 3 位八进制数,lib.ts)。
list_allowed_directories
- 无输入参数;
- 返回当前服务器可读写的所有允许目录列表;
- 建议在尝试访问文件前先调用它确认可用范围。
3.4 工具注解(MCP ToolAnnotations)
服务器为每个工具设置了 MCP 规范的 ToolAnnotations,使客户端可以程序化地区分工具的风险等级:
- 区分只读工具与可写工具;
- 识别哪些写操作是幂等的(相同参数重试安全);
- 高亮可能破坏性的操作(覆盖或大幅修改数据);
- 所有工具均设置
openWorldHint: false,表明它们不触及开放或外部世界——该服务器只访问允许目录内的本地文件系统。
完整映射表(继承自官方文档):
| Tool | readOnlyHint | idempotentHint | destructiveHint | Notes |
|---|---|---|---|---|
read_text_file | true | – | – | 纯读取 |
read_media_file | true | – | – | 纯读取 |
read_multiple_files | true | – | – | 纯读取 |
list_directory | true | – | – | 纯读取 |
list_directory_with_sizes | true | – | – | 纯读取 |
directory_tree | true | – | – | 纯读取 |
search_files | true | – | – | 纯读取 |
get_file_info | true | – | – | 纯读取 |
list_allowed_directories | true | – | – | 纯读取 |
create_directory | false | true | false | 重复创建同一目录是 no-op |
write_file | false | true | true | 会覆盖已存在的文件 |
edit_file | false | false | true | 重复应用编辑可能失败或重复生效 |
move_file | false | false | true | 会删除源文件 |
注意:按 MCP 规范,
idempotentHint与destructiveHint仅在readOnlyHint为false时才有意义。
四、客户端部署与配置
4.1 Claude Desktop 配置
可通过将目录挂载到/projects为服务器提供沙箱目录;对挂载加ro标志可使该目录对服务器只读。
Docker 方式(注意:所有目录默认必须挂载到/projects):
{ "mcpServers": { "filesystem": { "command": "docker", "args": [ "run", "-i", "--rm", "--mount", "type=bind,src=/Users/username/Desktop,dst=/projects/Desktop", "--mount", "type=bind,src=/path/to/other/allowed/dir,dst=/projects/other/allowed/dir,ro", "--mount", "type=bind,src=/path/to/file.txt,dst=/projects/path/to/file.txt", "mcp/filesystem", "/projects" ] } } }NPX 方式:
{ "mcpServers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop", "/path/to/other/allowed/dir" ] } } }Windows 下需通过cmd /c启动npx:
{ "mcpServers": { "filesystem": { "command": "cmd", "args": [ "/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop", "/path/to/other/allowed/dir" ] } } }4.2 VS Code 配置
VS Code 支持通过安装按钮一键配置(NPX 与 Docker 两种方式,分别面向 Stable 与 Insiders 通道),手动配置则有两个入口:
- 方式一(推荐):用户级配置——打开命令面板(
Ctrl + Shift + P)执行MCP: Open User Configuration,在用户级mcp.json中加入服务器配置; - 方式二:工作区配置——在工作区的
.vscode/mcp.json中添加配置,便于与团队共享。
VS Code 的配置键名为servers(区别于 Claude Desktop 的mcpServers),并可使用${workspaceFolder}变量指向当前工作区。Docker 方式下同样要求目录挂载到/projects,加ro标志可使目录只读。
Docker 方式:
{ "servers": { "filesystem": { "command": "docker", "args": [ "run", "-i", "--rm", "--mount", "type=bind,src=${workspaceFolder},dst=/projects/workspace", "mcp/filesystem", "/projects" ] } } }NPX 方式:
{ "servers": { "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "${workspaceFolder}" ] } } }Windows 下使用:
{ "servers": { "filesystem": { "command": "cmd", "args": [ "/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "${workspaceFolder}" ] } } }4.3 自行构建 Docker 镜像
官方提供多阶段构建的 Dockerfile:builder 阶段基于node:22.12-alpine安装依赖并编译 TypeScript,release 阶段基于node:22-alpine仅拷贝dist产物与生产依赖,入口为node /app/dist/index.js。构建命令(在仓库根目录执行,Dockerfile 内部会拷贝src/filesystem与根级tsconfig.json):
docker build -t mcp/filesystem -f src/filesystem/Dockerfile .构建完成后即可按上文 Docker 配置以mcp/filesystem镜像名启动。
五、跨平台路径处理的实现细节
从 path-utils.ts 可以看到该服务器对路径规范化投入了大量跨平台考量,这也是“允许目录”匹配在 Windows、WSL、macOS 上行为一致的基础:
- WSL 路径保护:
/mnt/c/...形式的路径被识别为 WSL 下的合法 Linux 路径,绝不会被转换为C:\形式(转换会导致 WSL 内 fs 操作失效); - Windows 路径规范化:处理
C:裸盘符补分隔符、UNC 路径前导双反斜杠保护、盘符大写化等边界; ~展开:命令行参数与 Roots URI 均支持~前缀展开为用户主目录(expandHome)。
六、测试体系与可验证依据
该服务器的关键安全行为均有对应测试文件覆盖,可作为行为事实的佐证:
- path-validation.test.ts:前缀攻击拦截、空字节拒绝、Windows/UNC 路径边界、符号链接支持检测(不支持时自动跳过符号链接用例,如 Windows 未开启开发者模式);
- startup-validation.test.ts:以子进程方式拉起编译后的服务器,验证“部分目录不可访问时降级继续”“全部不可访问时退出码为 1”;
- 其余测试覆盖 path-utils、directory_tree、roots-utils 与 lib。测试脚本为
vitest run --coverage(见 package.json)。
七、小结
Filesystem 服务器展示了 MCP 参考服务器的典型工程范式:
- 最小权限面:一切操作以“允许目录白名单”为前提,白名单支持静态命令行参数与动态 Roots 两种来源,且 Roots 采用“整体替换”语义,保证客户端对工作区拥有最终决定权;
- 纵深防御:相对路径归一化、前缀匹配防逃逸、符号链接 realpath 校验、新建文件父目录校验、
wx+ 原子 rename 写入,层层封堵沙箱逃逸路径; - 对 LLM 友好的工具设计:结构化输入(zod schema)、统一的
structuredContent输出、head/tail流式读取控制上下文长度、edit_file的dryRun预览机制与完整 ToolAnnotations 风险标注,使客户端可以安全地编排这些文件操作。
适用前提与限制:该服务器需要 Node.js 环境(Docker 镜像基于 Node 22);Docker 部署下所有允许目录必须挂载到/projects;无命令行参数时必须搭配支持 Roots 协议的客户端,否则初始化即失败。
【免费下载链接】serversModel Context Protocol Servers项目地址: https://gitcode.com/GitHub_Trending/se/servers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考