Skip to main content
Glama
andreasronge

ptc-fs-mcp

by andreasronge

ptc-fs-mcp

一个轻量级文件系统 MCP 服务器:通过 stdio 在一个受约束的根目录下读写文件。

演示软件。 它的存在是为了让智能体(agentic)运行时在教程、示例和集成测试中能有一个真实、确定性的外部工具可指向。它有意识地做到足够小,小到可以一次读完,并复制进你自己的项目。请不要把它部署为生产环境的文件服务。

它是为 PtcRunner 智能体框架构建的;在该框架中,文件系统能力完全通过宿主机配置获得,而不是写在运行时代码里。服务器中没有任何部分与 PtcRunner 绑定——它通过 stdio 讲标准 MCP 协议,所以任何 MCP 客户端都可以安装它。

npx -y ptc-fs-mcp --root ./workspace --include '**'

工具

工具

作用

返回

list_directory

读取

相对前缀之下的排序、分页目录项

search_files

读取

包含字面子串的排序、分页路径

search_text

读取

分页的字面匹配结果,带路径与行证据

read_text_file

读取

分页的精确 UTF-8 字节块

write_text_file

写入

替换一个普通文件,报告路径与字节数

四个读取工具接受可选的 cursor 与 limit,并恰好返回 items、next_cursor 与 content_hash。从无游标开始,沿 next_cursor 一直走到它为 null。对 read_text_file 来说,把各条目的 text 拼接即可精确还原该文件。

Related MCP server: Files MCP Server

实时字节

读取反映的是调用那一刻的文件系统状态,所以一次写入对紧随其后的读取立即可见。这正是此服务器的价值所在,并由此带来两个值得直接说明、而不是等用户踩坑才发现的情况。

游标宁可失败,也不撕裂。 游标携带其遍历所依赖状态的摘要。如果该状态发生变动,下一页会被拒绝,并给出错误讯 the filesystem changed since this cursor was issued; start the traversal again。一个撕裂的页面——一半来自变化之前,一半来自变化之后——才是唯一值得抛出一个错误来应对的结果。

结果实际依赖的状态才会被绑定,因此不相关的变动不会使游标失效:

工具

会使其失效的变动

可以存活的变动

list_directory

所列条目发生变化

某个被列出的子目录的更深处出现新文件

search_files

匹配路径的集合发生变化

匹配文件的内容被编辑

search_text

任一范围内文件的内容或其身份发生变化

搜索前缀之外的变动

read_text_file

该文件自身发生变化

任何其他文件发生变化

游标使用每进程的密钥签名,绑定到工具及参数上,并且必须原样呈现。来自另一段遍历、另一个进程,或经改动的游标字符串都会被拒绝。

每个结果都携带 content_hash——该调用返回字节的 SHA-256 摘要。因此引用所指向的是实际读取的那一字节序列,而不是在某个其他时刻碰巧存在的目录树。write_text_file 同样报告它对所写字节计算的摘要,于是写入与其后的读取可以互相对照。

这里不提供整棵树的哈希,也没有可安装的 snapshot_identity。摘要只能覆盖有界的扫描范围,而此服务器并不做整树扫描。

运行

ptc-fs-mcp --root ./workspace --include 'lib/**' --include 'docs/**' --exclude '**/secrets/**'

选项

含义

--root <dir>

约束到的根目录。必填。

--include <glob>

服务匹配的路径。必填,可重复。

--exclude <glob>

绝不服务匹配的路径。可重复;只能进一步收窄。

--max-file-bytes <n>

大于此字节数的文件不服务。

--max-write-bytes <n>

单次 write_text_file 的最大载荷。默认 65536。

--include 必传,默认是“什么文件都不服务”,所以不带它启动的服务器不会暴露任何东西。被排除的路径会在任何 stat 或 open 之前被跳过,因此永远不会被放入清单。glob 中 * 匹配单个路径段,** 跨段;lib/** 既选中 lib/a.ts,也选中 lib/deep/a.ts。

写入落在根目录里,所以 include 规则也必须达到根目录。 write_text_file 只接收一个基本文件名(basename),绝不接收目录,因此每次写入都直接落在根目录内。如果一个 include 集合只切入子目录——比如 --include 'lib/**'——那么这些文件可以提供服务以供读取,但一个次写入也接不了,并且每次尝试都会以 no --include pattern of this root matches a file in the root 被拒绝。对于只读安装来说,这是一个完全合法的配置,因此服务器照常启动,并在 stderr 上说明这一点:

ptc-fs-mcp: no --include pattern matches a file in the root itself, so
write_text_file will refuse every call.

凡是映射了写入工具的位置,请使用 --include '**',或者在目录级模式之外再加一条根级模式,比如 --include '*.md'。

从宿主文档安装时,请用固定版本的方式:

"transport": {
  "type": "stdio",
  "command": "npx",
  "args": ["-y", "ptc-fs-mcp@0.1.0", "--root", "workspace", "--include", "**"],
  "inherit_environment": true
}

在没有继承环境的情况下启动进程

那种写法需要在 PATH 里依赖两次:npx 要从 PATH 里找到;而安装后的二进制文件以 #!/usr/bin/env node 开头,同样从 PATH 里解析出运行解释器。一个以净化(穷化)环境来启动的宿主机——即 PtcRunner 的 inherit_environment: false,它自己的端到端测试就是这么用——将无法启动这个服务器,而且失败会表现为 acquisition 错误,例如 provider_unavailable,而不会提及 PATH。版本管理器让这种情况更尖锐而非更缓和:nvm 解释器位于类似 ~/.nvm/versions/node/v20.19.0/bin/node 的路径,别处都不存在。

两种配置互斥。要想在封闭环境(hermetically)启动,请预先安装该包,并把解释器和启动脚本都用绝对路径写全,从而规避掉 npx 和 shebang 两处:

npm install ptc-fs-mcp@0.1.0
node -p process.execPath
node -p "require.resolve('ptc-fs-mcp/package.json').replace(/package\.json$/, 'dist/cli.js')"
"transport": {
  "type": "stdio",
  "command": "/absolute/path/to/bin/node",
  "args": [
    "/absolute/path/to/node_modules/ptc-fs-mcp/dist/cli.js",
    "--root",
    "/absolute/path/to/workspace",
    "--include",
    "**"
  ],
  "inherit_environment": false,
  "env": {}
}

服务器本身对环境没有依赖:它不启动任何子进程,不打开网络连接,也不读取自己的环境变量。--root 是相对工作目录解析的,所以除非宿主机设置并由你控制 cwd,请把它也写成绝对路径。examples/ptc-host.json 里的 hermetic_workspace 就是这种形态。

拆分不同权限,而不拆分服务器

MCP 宿主决定哪些上游工具变成能力,因此这一个包的某次安装可以只映射 read_text_file,而第二次安装——指向另一个根目录——只映射 write_text_file。这样生成的读取程序就完全解析不到写入工具。参见 examples/ptc-host.json。

在 Node 中使用 Node

该包本身也是一个库。openRoot 校验配置并固定根目录;createServer 构建与二进制所服务同一个 McpServer,你可以为它挂接任意传输层。

import { createServer, openRoot } from 'ptc-fs-mcp'

const root = openRoot({ root: './workspace', include: ['**'], exclude: ['*.secret'] })
const server = createServer(root)

await server.connect(myTransport)

examples/embed.mjs 是一个可运行示例,会在 SDK 的内存传输上、于同一进程内写入一个文件、读回并搜索它:

npm run build && node examples/embed.mjs

openRoot 在配置不可用时抛 ConfigError,各个工具抛 ToolError;两者都会导出,并同时导出 normalizeRelative、compileGlob、createSelector 与 DEFAULT_LIMITS,这样宿主无需重做即可复用同样的路径约定。TypeScript 声明随包附带。

协议

仅支持 2026-07-28。没有 initialize 回退,没有降级协商,也没有兼容性分支:2025 时代的握手会被拒绝,拒绝时为 unsupported-protocol-version 错误,并指明本服务器实现的协议档位。只发布 tools 这一个能力——没有 Roots、没有 Trim、没有 Logging、没有 Tasks。

约束

  • 只接受相对路径。绝对路径、. 与 .. 段、NUL 字节以及 Windows 分隔符会被直接拒绝,而非被解析。

  • 符号链接会被跳过,绝不被跟随,因此根目录内的一条链接无法触及目录外字节。最终 open 使用 O_NOFOLLOW,因此在检查之后被换入的链接仍会失败。

  • 某个目录之所以出现在清单中,只因为它的内部有被服务的项;未被服务的目录名不会泄露。

  • write_text_file 只接受一个全部小写基本名——不接受目录,不做目录遍历——并对载荷设置上限,且通过它将要写入的同一描述符确认目标是普通文件,而不是通过某个独立的 stat 让符号链接抢跑。目标位于 --include 之外会被拒绝,因为一个写读不回来的写入不是功能,而是陷阱。由于写入直接落到根目录,只伸进子目录的 include 规则会拒绝所有写入;参见运行。

  • 路径清单对内容不敏感;内容工具拒绝它们无法解码的内容。read_text_file 遇到非有效 UTF-8 文件的失败;search_text 会跳过该行,该行字节若不解码,就会整行跳过,因此一行要么全部报告,要么完全不报告。

  • 结果被劣化,负担整个已解码 MCP 结果,文本搜索另有扫描字节预算。因此,当稀疏文件需要更多扫描时,空搜索页可以携带进度游标。

  • 错误信息是简短的可操作文本——不加堆栈,不返回宿主路径。

  • 不 spawn 任何进程,不使用任何网络,stdout 只承载协议消息;诊断输出发到 stderr。

它不防什么

根目录必须是可信静的,且没有足够权限的调用端:可移植的 Node 路径 API 无法对上游每个目录做描述符级隔离,因此一个能在调用中调换父目录的角色不在防御范围之内。服务器拒绝可观测的符号链接,并在最终 open 的关闭了 follow 位;它并不声称在被攻击乡等变化中的恶意源根上进行防御。

游标过期检测依据 size、mtime、ctime 与 inode 编号。在时间戳化粒度的文件系统上,同 mbit 相同长度的复写如果发生在同一个时间刻度内,是无法探测的。本程序所支持的主流文件系统常记录纳秒时间,并且 ctime 不能由用户空间设置。

开发

npm install
npm run build        # tsc to dist/, with declarations and source maps
npm test             # builds, then runs the suite against the built binary
npm run verify       # format check, typecheck, and tests

测试套件把构建出的 dist/cli.js 作为真实子进程,经由真实的 stdio 驱动,因此发布的版本经过实测。测试无关的根目录是被逐测试生成的,而不是随仓库提交,因为这个服务器既读也写。

何为 License

MIT。参见 LICENSE。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A lightweight, stdio-based MCP server enabling AI assistants to perform local file system operations like reading, writing, searching, and executing commands.
    3,012 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Stdio MCP server for sandboxed file access — read files, search content, safely edit with checksums, and manage file structure.
    13 npm
    ISC
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that gives AI agents file system access: read, write, search, hash, directory trees — 12 tools, zero dependencies, pure Python stdlib.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A sandboxed file-access MCP server for Claude Desktop, enabling safe read/write access to selected directories with atomic verified writes, undo, and read-only git operations.
    1
    MIT