mcp-read-file-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +314 -0
  3. package/SKILL.md +169 -0
  4. package/index.js +435 -0
  5. package/package.json +51 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hebulin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,314 @@
1
+ # 文件操作 MCP Server(加密软件环境明文读写)
2
+
3
+ ## 简介
4
+
5
+ 加密环境文件操作工具。当 Node.js 是加密软件白名单进程时,通过 fs 模块自动解密读写文件明文,替代 AI Agent 内置文件工具,解决加密环境下读到密文的问题。适用于任何支持 MCP 协议的 AI Agent。
6
+
7
+ ## 适用场景
8
+
9
+ 电脑安装了文件加密软件(如天锐绿盾、IP-Guard、亿赛通、深信服等),磁盘上的文件是密文。AI Agent(Claude Code、Cursor、Windsurf、Cline 等)是独立进程,内置文件工具不在白名单内,只能读到密文。而 Node.js 进程在白名单内,通过 MCP Server 提供的替代工具可以正常读写明文。
10
+
11
+ **前提条件:Node.js 进程已被加密软件列为白名单(受信任进程)。**
12
+
13
+ ## 支持的 AI Agent
14
+
15
+ 本 MCP Server 遵循标准 MCP 协议,任何支持 MCP 的 Agent 均可使用:
16
+
17
+ | Agent | 配置方式 |
18
+ |-------|---------|
19
+ | Claude Code CLI | `.mcp.json` 或 `claude mcp add` |
20
+ | Cursor | Settings → MCP → 添加 Server |
21
+ | Windsurf | MCP 配置中添加 |
22
+ | Cline / Roo Code | MCP 设置中添加 |
23
+ | Continue.dev | `config.json` 中配置 MCP |
24
+ | Zed | `settings.json` 中配置 MCP |
25
+ | 其他支持 MCP 的工具 | 按各自文档配置 |
26
+
27
+ ## 架构
28
+
29
+ ```
30
+ AI Agent --(MCP/stdio)--> Node.js MCP Server --(fs.readFileSync)--> 读取明文
31
+ ```
32
+
33
+ ## 文件结构
34
+
35
+ ```
36
+ mcp-read-file-server/
37
+ ├── README.md # 本文档
38
+ ├── SKILL.md # 配套 Skill(可选,让 AI 学会自动选用本工具)
39
+ ├── index.js # MCP Server 主程序(含 shebang,可作可执行入口)
40
+ ├── package.json # 包配置(bin/files/依赖声明,可 npm publish)
41
+ ├── smithery.yaml # Smithery 市场上架配置
42
+ ├── .gitignore # Git 忽略规则
43
+ └── node_modules/ # 依赖(@modelcontextprotocol/sdk、zod,不随包发布)
44
+ ```
45
+
46
+ ## 安装
47
+
48
+ ### 前置条件
49
+ - Node.js v18+(推荐 v20+)
50
+ - Node.js 已被加密软件列为白名单进程
51
+
52
+ ### 方式一:通过 npx 运行(推荐,无需手动安装)
53
+
54
+ 已发布到 npm,可直接通过 `npx` 运行,无需 `git clone` 和 `npm install`:
55
+
56
+ ```bash
57
+ npx -y mcp-read-file-server
58
+ ```
59
+
60
+ 首次运行 npx 会自动下载本包及其依赖到临时目录并启动。配置 Agent 时将 `command` 设为 `npx`、`args` 设为 `["-y", "mcp-read-file-server"]` 即可(见下文「配置」)。
61
+
62
+ ### 方式二:从源码运行(开发 / 离线场景)
63
+
64
+ ```bash
65
+ git clone https://github.com/hebulin/mcp-read-file-server.git
66
+ cd mcp-read-file-server
67
+ npm install
68
+ ```
69
+
70
+ 此时配置中使用 `node` + 本地 `index.js` 绝对路径。
71
+
72
+ ## 配置
73
+
74
+ 所有 Agent 配置 MCP Server 的核心信息相同,只是配置文件位置和格式略有差异。
75
+
76
+ ### npx 方式(推荐)
77
+
78
+ 通过 npm 包运行,无需关心本地路径:
79
+
80
+ ```json
81
+ {
82
+ "mcpServers": {
83
+ "read-file-server": {
84
+ "command": "npx",
85
+ "args": ["-y", "mcp-read-file-server"]
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### 本地源码方式
92
+
93
+ 若用「方式二」从源码运行,则指向本地 `index.js`:
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "read-file-server": {
99
+ "command": "node",
100
+ "args": ["/path/to/mcp-read-file-server/index.js"]
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### Claude Code CLI
107
+
108
+ #### 方式一:项目级配置(仅当前项目可用)
109
+
110
+ 在项目根目录创建 `.mcp.json` 文件:
111
+
112
+ ```json
113
+ {
114
+ "mcpServers": {
115
+ "read-file-server": {
116
+ "command": "npx",
117
+ "args": ["-y", "mcp-read-file-server"]
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ #### 方式二:全局配置(所有项目可用)
124
+
125
+ ```bash
126
+ claude mcp add read-file-server -s user -- npx -y mcp-read-file-server
127
+ ```
128
+
129
+ 参数说明:
130
+ - `read-file-server`:MCP Server 名称(自定义)
131
+ - `-s user`:作用域为全局(所有项目可用),不写则默认项目级
132
+ - `--`:分隔符,后面是实际执行的命令
133
+ - `npx -y mcp-read-file-server`:实际执行的命令(自动从 npm 拉取并运行)
134
+
135
+ #### 方式三:手动编辑全局配置文件
136
+
137
+ 直接编辑 `C:\Users\你的用户名\.claude.json`,添加:
138
+
139
+ ```json
140
+ {
141
+ "mcpServers": {
142
+ "read-file-server": {
143
+ "command": "npx",
144
+ "args": ["-y", "mcp-read-file-server"]
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ #### 验证配置
151
+
152
+ ```bash
153
+ claude mcp list
154
+ ```
155
+
156
+ 应该能看到 `read-file-server` 在列表中。
157
+
158
+ ### Cursor / Windsurf / Cline 等
159
+
160
+ 在各自设置界面的 MCP 配置中,添加上述 JSON 配置。
161
+
162
+ ## 提供的工具
163
+
164
+ | 工具名 | 替代内置 | 功能 | 参数 |
165
+ |--------|---------|------|------|
166
+ | `read_file` | Read | 读取单个文件明文 | `path`: 文件路径 |
167
+ | `read_files` | 多次 Read | 批量读取多个文件明文 | `paths`: 逗号分隔的路径 |
168
+ | `read_file_partial` | Read(局部) | 局部读取文件(前N字符 / 指定行范围) | `path`、`mode`、`charCount`、`startLine`、`endLine` |
169
+ | `write_file` | Write | 写入文件(自动加密落盘) | `path`、`content` |
170
+ | `edit_file` | Edit/MultiEdit | 精确字符串/正则替换后写回 | `path`、`oldString`、`newString`、`useRegex`、`replaceAll`、`ignoreCase` |
171
+ | `search_files` | Grep | 递归搜索文件内容 | `pattern`、`path`、`include`、`ignoreCase`、`onlyMatching`、`maxResults` |
172
+ | `create_directory` | - | 递归创建目录 | `path` |
173
+ | `file_info` | - | 查询文件/目录信息 | `path` |
174
+ | `check_status` | - | 检查工具运行状态 | 无 |
175
+
176
+ ### `read_file_partial` 参数详解
177
+
178
+ | 参数 | 类型 | 必填 | 默认 | 说明 |
179
+ |------|------|------|------|------|
180
+ | `path` | string | ✅ | - | 文件路径,支持相对路径或绝对路径 |
181
+ | `mode` | enum: `chars` / `lines` | ✅ | - | 读取模式:`chars`=按字符数读取前N个字符;`lines`=按行号读取指定行或行范围 |
182
+ | `charCount` | number | `mode=chars` 时必填 | - | 读取前 N 个字符 |
183
+ | `startLine` | number | `mode=lines` 时必填 | - | 起始行号(从 1 开始) |
184
+ | `endLine` | number | ❌ | =`startLine` | 结束行号(含该行)。不传则只读取 `startLine` 一行 |
185
+
186
+ **使用示例:**
187
+
188
+ - 读取文件前 500 个字符:`mode="chars"`, `charCount=500`
189
+ - 读取第 10 行:`mode="lines"`, `startLine=10`
190
+ - 读取第 5-20 行:`mode="lines"`, `startLine=5`, `endLine=20`
191
+
192
+ > 返回内容会带文件名、读取范围、总字符数/总行数的头部信息,行模式下每行带行号前缀。超出文件范围时自动截断并提示。
193
+
194
+ ## 使用
195
+
196
+ 配置好后,在 Agent 中直接说需求即可。Agent 会自动调用 MCP 工具读写文件明文。
197
+
198
+ ## 配套 Skill(可选)
199
+
200
+ 本工具附带一份 Skill:`SKILL.md`,位于本目录根下。
201
+
202
+ | 项 | 说明 |
203
+ |----|------|
204
+ | 作用 | 教 AI Agent 在加密环境下主动选用 `mcp__read-file-server__*` 工具,避开内置 Read/Write/Edit/Grep |
205
+ | 当前状态 | `SKILL.md` 在工具根目录,**未安装**,需要按下面步骤复制到对应位置才生效 |
206
+ | 触发关键词 | 天锐、绿盾、密文、TSD、IP-Guard、亿赛通、白名单、读不到文件等 |
207
+
208
+ ### 1. 安装到 Claude Code
209
+
210
+ Claude Code 启动时会自动扫描 `skills/` 目录,每个 Skill 必须是 `skill-name/SKILL.md` 的子目录结构。
211
+
212
+ | 作用域 | 安装位置 |
213
+ |--------|----------|
214
+ | 项目级(仅本项目) | `<本仓库>/.claude/skills/encryption-file-ops/SKILL.md` |
215
+ | 用户级(所有项目) | `~/.claude/skills/encryption-file-ops/SKILL.md` |
216
+
217
+ 安装示例(项目级,从本目录执行):
218
+
219
+ ```bash
220
+ mkdir -p ../.claude/skills/encryption-file-ops
221
+ cp SKILL.md ../.claude/skills/encryption-file-ops/SKILL.md
222
+ ```
223
+
224
+ 安装后**重启 Claude Code** 即可生效。
225
+
226
+ ### 2. 安装到 OpenClaw(小龙虾)
227
+
228
+ > ⚠️ 以下为通用约定写法,OpenClaw 的 Skill 加载机制请以其官方文档为准,确认后可对本节做相应调整。
229
+
230
+ **方式一:让 OpenClaw 自动安装**
231
+
232
+ 把 `SKILL.md` 交给 OpenClaw,用自然语言让它自己装:
233
+
234
+ ```
235
+ 我把一个 Skill 文件放在 D:/AiJiamiToolsPlugins/mcp-read-file-server/SKILL.md,
236
+ 请按 OpenClaw 的 Skill 规范把它安装到我的 skills 目录,并确认能否被加载。
237
+ ```
238
+
239
+ OpenClaw 会读取文件、确认 frontmatter(`name` / `description`),并复制到它自己的 Skill 目录。装完后可让它自检:
240
+
241
+ ```
242
+ 列出你当前已加载的所有 Skill,确认 encryption-file-ops 是否在其中。
243
+ ```
244
+
245
+ **方式二:手动复制**
246
+
247
+ ```bash
248
+ # 以 OpenClaw 默认 skill 目录 ~/.openclaw/skills 为例
249
+ mkdir -p ~/.openclaw/skills/encryption-file-ops
250
+ cp SKILL.md ~/.openclaw/skills/encryption-file-ops/SKILL.md
251
+ ```
252
+
253
+ > 配置目录名(`.openclaw`)仅为示例,请替换为 OpenClaw 实际使用的目录。
254
+
255
+ **方式三:项目级随仓库分发**
256
+
257
+ 如果希望 Skill 跟随项目走(团队成员拉代码即生效),把 SKILL.md 放进项目的 Skill 扫描目录(例如 `<仓库根>/.openclaw/skills/encryption-file-ops/SKILL.md`),与 OpenClaw 的项目级 Skill 约定保持一致即可。
258
+
259
+ ### 3. 验证安装
260
+
261
+ 无论哪种 Agent,安装后重启客户端,然后:
262
+
263
+ 1. 输入触发关键词测试,例如:"这个项目里有文件加密,读文件是密文,怎么办?"
264
+ 2. 观察是否自动选用 `mcp__read-file-server__*` 工具而非内置 Read/Grep
265
+
266
+
267
+ ## 在新电脑上使用
268
+
269
+ 已发布到 npm,新电脑上**无需拷贝文件**,只要装了 Node.js(v18+),直接配置 Agent 使用 `npx -y mcp-read-file-server` 即可。
270
+
271
+ > 若需离线使用或二次开发,再按「安装 -> 方式二」从源码克隆运行。
272
+
273
+ ## 上架 MCP 市场
274
+
275
+ 本包已具备 `npx` 直接运行能力,可上架到各 MCP 市场:
276
+
277
+ | 市场 | 上架方式 |
278
+ |------|---------|
279
+ | npm | `npm publish`(包名 `mcp-read-file-server`,已配置 `bin` 与 `files`) |
280
+ | Smithery | 在 https://smithery.ai 提交包名,仓库根已提供 `smithery.yaml`(上架前以官方文档核对) |
281
+ | mcp.so | 在 https://mcp.so 提交 npm 包名与启动命令 `npx -y mcp-read-file-server` |
282
+ | PulseMCP | 在 https://www.pulsemcp.com 提交包名 |
283
+
284
+ 发布到 npm 前建议本地预检:
285
+
286
+ ```bash
287
+ # 预览将发布到 npm 的文件清单(应只有 index.js / README.md / SKILL.md / LICENSE / package.json)
288
+ npm pack --dry-run
289
+
290
+ # 登录并发布
291
+ npm login
292
+ npm publish
293
+ ```
294
+
295
+ 发布后,他人即可通过 `npx -y mcp-read-file-server` 一行命令运行,无需手动 `npm install`。
296
+
297
+ ## 故障排查
298
+
299
+ ### 读取到的仍是密文
300
+
301
+ 说明 Node.js 未被加密软件列为白名单。解决方法:
302
+ - 联系加密软件管理员,将 `node.exe` 加入白名单
303
+ - 确认加密软件的受信任进程列表中包含 Node.js
304
+
305
+ ### MCP Server 无法启动
306
+
307
+ ```bash
308
+ # 验证 Node.js 和依赖
309
+ node --version
310
+ cd mcp-read-file-server && npm install
311
+
312
+ # 测试启动
313
+ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | node index.js
314
+ ```
package/SKILL.md ADDED
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: encryption-file-ops
3
+ description: 在文件加密软件(天锐绿盾 / IP-Guard / 亿赛通 / 深信服等)环境下,使用 mcp__read-file-server__* 工具替代 AI Agent 内置 Read/Write/Edit/Grep 工具读写明文。当内置工具返回密文/乱码,或用户提到"加密、绿盾、密文、白名单、读不到文件"等关键词时激活。
4
+ ---
5
+
6
+ # 加密环境文件操作 Skill
7
+
8
+ > **本 Skill 负责"教策略",MCP Server 负责"执行"。**
9
+ > 本 Skill 不替代任何 MCP 工具,仅提供使用指引。
10
+
11
+ ## 一、适用场景
12
+
13
+ ### 触发条件(满足任一即激活)
14
+ - 内置 `Read/Write/Edit/Grep` 工具返回密文、十六进制乱码、`%TSD-Header-###%` 文件头
15
+ - 用户提到关键词:天锐、绿盾、TSD、IP-Guard、亿赛通、深信服、加密软件、白名单、密文、解密失败
16
+ - 项目文件头出现 `%TSD-Header-###%` 等加密标识
17
+
18
+ ### 前置条件
19
+ - Node.js 进程已被加密软件列为**白名单(受信任进程)**
20
+ - `mcp-read-file-server` MCP Server 已配置(见项目 `README.md`)
21
+ - 在 `claude mcp list` 中能看到 `read-file-server`
22
+
23
+ ---
24
+
25
+ ## 二、工具映射表
26
+
27
+ | 场景 | 内置工具(禁用) | MCP 工具(用这个) |
28
+ |------|------------------|---------------------|
29
+ | 读单个文件 | `Read` | `mcp__read-file-server__read_file` |
30
+ | 读 ≥2 个文件 | 多次 `Read` | `mcp__read-file-server__read_files` |
31
+ | 局部读文件(前N字符/指定行) | `Read`(局部) | `mcp__read-file-server__read_file_partial` |
32
+ | 写文件 | `Write` | `mcp__read-file-server__write_file` |
33
+ | 精确编辑 | `Edit` / `MultiEdit` | `mcp__read-file-server__edit_file` |
34
+ | 搜索内容 | `Grep` | `mcp__read-file-server__search_files` |
35
+ | 创建目录 | (无) | `mcp__read-file-server__create_directory` |
36
+ | 查文件信息 | (无) | `mcp__read-file-server__file_info` |
37
+ | 健康检查 | (无) | `mcp__read-file-server__check_status` |
38
+
39
+ > **强约束**:在加密环境下,**禁止使用** `Read/Write/Edit/MultiEdit/Grep` 内置工具--它们会读到密文或破坏加密结构。
40
+
41
+ ---
42
+
43
+ ## 三、决策树
44
+
45
+ ```
46
+ 需要读文件
47
+ ├─ 单个(全文)-> mcp__read-file-server__read_file
48
+ ├─ 多个(≥2)-> mcp__read-file-server__read_files(批量更高效)
49
+ └─ 局部读取(大文件预览/定位特定行)-> mcp__read-file-server__read_file_partial
50
+ ├─ 读前N字符 -> mode="chars", charCount=N
51
+ └─ 读指定行范围 -> mode="lines", startLine=X, endLine=Y(endLine 不传则只读一行)
52
+
53
+ 需要修改文件
54
+ ├─ 已读过 -> 直接 mcp__read-file-server__edit_file
55
+ └─ 未读过 -> 先 read_file 拿到明文 -> 再 edit_file
56
+
57
+ 需要新建/覆盖文件
58
+ └─ mcp__read-file-server__write_file
59
+
60
+ 需要搜索内容
61
+ └─ mcp__read-file-server__search_files
62
+ ├─ 限定文件类型 -> 用 include(如 "*.java,*.xml")
63
+ └─ 控制返回数量 -> 用 maxResults
64
+
65
+ 需要创建目录
66
+ └─ mcp__read-file-server__create_directory
67
+
68
+ 需要判断文件是否存在 / 查大小
69
+ └─ mcp__read-file-server__file_info
70
+
71
+ 工具异常 / 不确定是否生效
72
+ └─ mcp__read-file-server__check_status
73
+ ```
74
+
75
+ ---
76
+
77
+ ## 四、典型工作流
78
+
79
+ ### 流程 1:读取并修改文件
80
+ ```
81
+ 1. mcp__read-file-server__read_file 读取明文
82
+ 2. 分析内容
83
+ 3. mcp__read-file-server__edit_file 修改
84
+ - oldString 必须从第 1 步读到的内容里**原样复制**(含空格、缩进、换行)
85
+ 4. 必要时再 read_file 验证修改结果
86
+ ```
87
+
88
+ ### 流程 2:批量读多个相关文件
89
+ ```
90
+ 1. mcp__read-file-server__read_files
91
+ - paths 参数用英文逗号分隔,如 "D:/proj/A.java,D:/proj/B.java"
92
+ 2. 统一分析(输出会带"========== 文件: xxx =========="分隔)
93
+ ```
94
+
95
+ ### 流程 3:在项目中搜索特定代码
96
+ ```
97
+ 1. mcp__read-file-server__search_files
98
+ - pattern: 正则表达式(如 "function\s+\w+"、"@GetMapping")
99
+ - path: 搜索根目录
100
+ - include: 文件名过滤(可选)
101
+ 2. 根据返回的 file:line 定位,用 read_file 读取具体文件
102
+ ```
103
+
104
+ ### 流程 4:从零创建新模块
105
+ ```
106
+ 1. mcp__read-file-server__create_directory 建包目录
107
+ 2. mcp__read-file-server__write_file 逐个创建文件
108
+ 3. read_file 验证(可选)
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 五、`edit_file` 关键参数详解
114
+
115
+ | 参数 | 类型 | 必填 | 默认 | 说明 |
116
+ |------|------|------|------|------|
117
+ | `path` | string | ✅ | - | 文件绝对路径 |
118
+ | `oldString` | string | ✅ | - | 要替换的原内容,必须**精确匹配** |
119
+ | `newString` | string | ✅ | - | 替换后的新内容 |
120
+ | `useRegex` | boolean | ❌ | false | true 时 oldString 当正则,可用 `$1 $2` 引用捕获组 |
121
+ | `replaceAll` | boolean | ❌ | false | true 时替换所有匹配项;false 时仅替换第一处 |
122
+ | `ignoreCase` | boolean | ❌ | false | 是否忽略大小写(仅字符串模式生效) |
123
+
124
+ ### 常见用法
125
+ - **单点替换**:`useRegex=false, replaceAll=false`(默认)
126
+ - **批量替换**:`useRegex=true, replaceAll=true`(如改命名)
127
+ - **正则提取后重组**:`useRegex=true, newString` 里用 `$1` `$2`
128
+
129
+ ---
130
+
131
+ ## 六、注意事项
132
+
133
+ 1. **路径**:用**绝对路径**最稳(如 `D:/AiJiamiToolsPlugins/...`),相对路径以 MCP Server 启动目录为基准
134
+ 2. **`edit_file` 前必读**:必须先 `read_file` 拿到明文,再从原文里**原样复制** `oldString`,否则会因为空格/缩进不匹配而失败
135
+ 3. **不要在 `oldString` 里漏换行**:多行替换时,行末换行符也要复制完整
136
+ 4. **`write_file` 是覆盖写**:会清空原文件再写入,重要文件修改前建议先 `read_file` 备份内容
137
+ 5. **`search_files` 自动跳过**:`node_modules`、`.git`、`target`、`build`、`dist`、`.idea`、`.vscode`
138
+ 6. **大批量搜索**:用 `maxResults` 控制返回数量,避免一次性返回过多结果
139
+ 7. **工具调用顺序**:复杂任务先 `check_status` 确认 MCP 正常,再正式操作
140
+
141
+ ---
142
+
143
+ ## 七、故障排查
144
+
145
+ | 现象 | 可能原因 | 解决方案 |
146
+ |------|----------|----------|
147
+ | 读到的还是密文/乱码 | Node.js 不在加密软件白名单 | 联系管理员把 `node.exe` 加入白名单 |
148
+ | `mcp__read-file-server__*` 工具全部不可见 | MCP Server 未配置或未启动 | 见 `README.md` 配置 `.mcp.json` |
149
+ | `edit_file` 报"未找到匹配内容" | `oldString` 拼写、缩进、换行不对 | 重新 `read_file` 复制原文,**不要凭记忆写** |
150
+ | `edit_file` 报"匹配到 N 处" | 文件中存在重复内容 | 加更长/更唯一的 `oldString` 唯一定位,或 `replaceAll=true` |
151
+ | `search_files` 报"正则表达式无效" | 正则语法错误 | 检查 `pattern` 是否需要转义特殊字符 |
152
+ | `write_file` 报权限错误 | 文件被占用或目录无写权限 | 关闭占用进程 / 检查目录权限 |
153
+ | 工具调用超时 | 文件过大 | 改用 `search_files` 定位后只 `read_file` 关键段,或用 `read_file_partial` 局部读取 |
154
+
155
+ ---
156
+
157
+ ## 八、与其他工具的关系
158
+
159
+ | 工具类型 | 在加密环境下 | 备注 |
160
+ |----------|--------------|------|
161
+ | 内置 `Read/Write/Edit/Grep` | ❌ 禁用 | 会读到密文或破坏加密 |
162
+ | 内置 `Bash` | ⚠️ 慎用 | Bash 进程通常不在白名单,`cat`/`sed` 也会读到密文 |
163
+ | 内置 `Glob` | ✅ 可用 | 只列文件名,不读内容 |
164
+ | 内置 `NotebookEdit` | ⚠️ 慎用 | 同 Edit |
165
+ | `mcp__read-file-server__*` | ✅ 主用 | 本 Skill 推广的工具集 |
166
+ | `TaskCreate` / `TaskList` | ✅ 可用 | 任务管理,不涉及文件内容 |
167
+ | `WebFetch` / `WebSearch` | ✅ 可用 | 网络工具,与本地加密无关 |
168
+
169
+ > **Bash 特别注意**:在加密环境下,`Bash` 工具的 `cat`/`sed`/`grep` 也会读到密文。如需在终端操作文件,应使用 `node -e` 走 Node.js 白名单进程。
package/index.js ADDED
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server: 文件操作工具集(加密软件环境明文读写)
4
+ *
5
+ * 通用场景:当电脑安装了文件加密软件(如天锐绿盾、IP-Guard、亿赛通等),
6
+ * 且 Node.js 进程被列为白名单(受信任进程)时,fs.readFileSync / fs.writeFileSync
7
+ * 可自动解密/加密,读到明文。而 Claude Code CLI 内置工具(Read/Write/Edit/Grep)
8
+ * 是独立进程,不在白名单内,只能读到密文。
9
+ *
10
+ * 本 MCP Server 通过 Node.js 进程提供文件读写工具,替代 Claude Code 内置工具,
11
+ * 适用于任何「Node.js 是加密软件白名单进程」的场景。
12
+ *
13
+ * 提供工具:
14
+ * - read_file 读取单个文件明文(替代内置 Read)
15
+ * - read_files 批量读取多个文件明文
16
+ * - read_file_partial 局部读取文件(前N字符 / 指定行范围)
17
+ * - write_file 写入文件,自动加密落盘(替代内置 Write)
18
+ * - edit_file 精确字符串/正则替换后写回(替代内置 Edit/MultiEdit)
19
+ * - search_files 递归搜索文件内容(替代内置 Grep)
20
+ * - create_directory 递归创建目录
21
+ * - file_info 查询文件/目录信息
22
+ * - check_status 检查工具运行状态
23
+ */
24
+ const fs = require("fs");
25
+ const path = require("path");
26
+ const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
27
+ const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
28
+ const { z } = require("zod");
29
+
30
+ // 版本号读取自 package.json,与发布版本保持同步,避免硬编码漂移
31
+ const pkg = require("./package.json");
32
+ const server = new McpServer({ name: "read-file-server", version: pkg.version });
33
+
34
+ /**
35
+ * 读取文件内容。Node.js 进程被加密软件列为白名单,fs.readFileSync 可自动解密读到明文。
36
+ */
37
+ function readFileContent(filePath) {
38
+ try {
39
+ const content = fs.readFileSync(filePath, "utf-8");
40
+ // 加密环境下 stat.size 是密文字节数,与明文长度不一致,改用明文字节数避免误导
41
+ return { ok: true, content, size: Buffer.byteLength(content, "utf-8") };
42
+ } catch (e) {
43
+ if (e.code === "ENOENT") {
44
+ return { ok: false, error: "文件不存在: " + filePath };
45
+ }
46
+ return { ok: false, error: "读取失败(可能是密文,请确认 Node.js 是否被加密软件列为白名单进程): " + e.message };
47
+ }
48
+ }
49
+
50
+ // 注册 read_file 工具
51
+ server.tool(
52
+ "read_file",
53
+ "读取指定路径的文件内容(明文)。加密软件环境下,Node.js 进程作为白名单可自动解密读取明文。适用于读取代码、配置、文档等文本文件。替代内置 Read 工具。",
54
+ { path: z.string().describe("文件路径,支持相对路径或绝对路径") },
55
+ { readOnlyHint: true },
56
+ async ({ path: filePath }) => {
57
+ const result = readFileContent(filePath);
58
+ if (result.ok) {
59
+ return { content: [{ type: "text", text: result.content }] };
60
+ } else {
61
+ return { content: [{ type: "text", text: "❌ " + result.error }], isError: true };
62
+ }
63
+ }
64
+ );
65
+
66
+ // 注册 read_files 工具(批量读取)
67
+ server.tool(
68
+ "read_files",
69
+ "批量读取多个文件的内容(明文)。多个路径用逗号分隔。加密软件环境下通过 Node.js 白名单进程自动解密。",
70
+ { paths: z.string().describe("文件路径列表,多个路径用英文逗号分隔") },
71
+ { readOnlyHint: true },
72
+ async ({ paths: pathsStr }) => {
73
+ const pathList = pathsStr.split(",").map(p => p.trim()).filter(p => p);
74
+ if (!pathList.length) {
75
+ return { content: [{ type: "text", text: "❌ 未提供任何文件路径" }], isError: true };
76
+ }
77
+ const results = [];
78
+ for (const p of pathList) {
79
+ const result = readFileContent(p);
80
+ if (result.ok) {
81
+ results.push("========== 文件: " + p + " ==========\n" + result.content);
82
+ } else {
83
+ results.push("========== 文件: " + p + " 【读取失败】 ==========\n❌ " + result.error);
84
+ }
85
+ }
86
+ return { content: [{ type: "text", text: results.join("\n\n") }] };
87
+ }
88
+ );
89
+
90
+ // 注册 read_file_partial 工具(局部读取,按字符数或行号范围)
91
+ server.tool(
92
+ "read_file_partial",
93
+ "局部读取文件内容(明文)。支持两种模式:①按字符数读取前N个字符;②按行号读取指定行或行范围(如第10行、第5-20行)。加密软件环境下同样通过 Node.js 白名单进程自动解密。适用于大文件预览、定位特定行内容等场景。",
94
+ {
95
+ path: z.string().describe("文件路径,支持相对路径或绝对路径"),
96
+ mode: z.enum(["chars", "lines"]).describe("读取模式:chars=按字符数读取前N个字符;lines=按行号读取指定行或行范围"),
97
+ charCount: z.number().int().positive().optional().describe("mode=chars 时必填,读取前N个字符"),
98
+ startLine: z.number().int().min(1).optional().describe("mode=lines 时必填,起始行号(从1开始)"),
99
+ endLine: z.number().int().min(1).optional().describe("mode=lines 时可选,结束行号(含)。不传则只读取 startLine 一行"),
100
+ },
101
+ { readOnlyHint: true },
102
+ async ({ path: filePath, mode, charCount, startLine, endLine }) => {
103
+ const result = readFileContent(filePath);
104
+ if (!result.ok) {
105
+ return { content: [{ type: "text", text: "❌ " + result.error }], isError: true };
106
+ }
107
+ const content = result.content;
108
+ const totalChars = content.length;
109
+
110
+ if (mode === "chars") {
111
+ if (charCount === undefined) {
112
+ return { content: [{ type: "text", text: "❌ mode=chars 时必须提供 charCount 参数" }], isError: true };
113
+ }
114
+ const slice = content.slice(0, charCount);
115
+ const header = "📄 文件: " + filePath + "\n模式: 前 " + charCount + " 字符(共 " + totalChars + " 字符)\n";
116
+ const footer = charCount < totalChars ? "\n\n...(已截断,还有 " + (totalChars - charCount) + " 字符未显示)" : "";
117
+ return { content: [{ type: "text", text: header + "──────────────────────\n" + slice + footer }] };
118
+ }
119
+
120
+ // mode === "lines"
121
+ if (startLine === undefined) {
122
+ return { content: [{ type: "text", text: "❌ mode=lines 时必须提供 startLine 参数" }], isError: true };
123
+ }
124
+ const lines = content.split(/\r?\n/);
125
+ const totalLines = lines.length;
126
+ const sLine = startLine;
127
+ const eLine = endLine !== undefined ? endLine : startLine;
128
+ if (eLine < sLine) {
129
+ return { content: [{ type: "text", text: "❌ endLine 不能小于 startLine" }], isError: true };
130
+ }
131
+ // 行号从1开始,数组索引从0开始
132
+ const startIdx = Math.max(0, sLine - 1);
133
+ const endIdx = Math.min(totalLines, eLine); // slice 不含 endIdx,所以用 eLine(因为已经 +1 偏移)
134
+ const selected = lines.slice(startIdx, endIdx);
135
+ // 为每行添加行号前缀
136
+ const numbered = selected.map((line, i) => {
137
+ const lineNo = startIdx + i + 1;
138
+ return String(lineNo).padStart(6, " ") + " | " + line;
139
+ });
140
+ const actualStart = startIdx + 1;
141
+ const actualEnd = startIdx + selected.length;
142
+ const header = "📄 文件: " + filePath + "\n模式: 第 " + actualStart + " - " + actualEnd + " 行(共 " + totalLines + " 行)\n";
143
+ const footer = eLine > totalLines ? "\n\n⚠️ 请求的结束行 " + eLine + " 超出文件总行数 " + totalLines + ",已自动截断" : "";
144
+ return { content: [{ type: "text", text: header + "──────────────────────\n" + numbered.join("\n") + footer }] };
145
+ }
146
+ );
147
+
148
+ // 注册 write_file 工具(加密软件环境下安全写回)
149
+ server.tool(
150
+ "write_file",
151
+ "将内容写入指定路径(明文)。加密软件环境下,Node.js 白名单进程写入会自动加密落盘,适用于安全写回加密文件。替代内置 Write 工具。",
152
+ {
153
+ path: z.string().describe("文件路径,支持相对路径或绝对路径"),
154
+ content: z.string().describe("写入的文件内容(明文)"),
155
+ },
156
+ async ({ path: filePath, content }) => {
157
+ try {
158
+ // 自动创建父目录,避免新文件路径不存在时直接报错
159
+ const parent = path.dirname(filePath);
160
+ if (parent && !fs.existsSync(parent)) {
161
+ fs.mkdirSync(parent, { recursive: true });
162
+ }
163
+ fs.writeFileSync(filePath, content, "utf-8");
164
+ return { content: [{ type: "text", text: "✅ 写入成功: " + filePath }] };
165
+ } catch (e) {
166
+ return { content: [{ type: "text", text: "❌ 写入失败: " + e.message }], isError: true };
167
+ }
168
+ }
169
+ );
170
+
171
+ // 注册 edit_file 工具(精确替换,替代受加密影响的 Edit/MultiEdit)
172
+ server.tool(
173
+ "edit_file",
174
+ "对文件内容做精确字符串或正则替换后写回(明文)。加密软件环境下内置 Edit/MultiEdit 直写会破坏加密,本工具用 Node.js fs 读改写,自动加密落盘。替代内置 Edit/MultiEdit 工具。",
175
+ {
176
+ path: z.string().describe("文件路径,支持相对路径或绝对路径"),
177
+ oldString: z.string().describe("要被替换的原字符串。useRegex=true 时作为正则表达式"),
178
+ newString: z.string().describe("替换后的字符串。正则模式下可用 $1 $2 等捕获组引用"),
179
+ useRegex: z.boolean().optional().describe("是否将 oldString 当作正则表达式,默认 false(纯字符串匹配)"),
180
+ replaceAll: z.boolean().optional().describe("是否替换全部匹配项,默认 false 仅替换第一处"),
181
+ ignoreCase: z.boolean().optional().describe("是否忽略大小写,默认 false。仅在非正则的字符串模式下生效"),
182
+ },
183
+ async ({ path: filePath, oldString, newString, useRegex, replaceAll, ignoreCase }) => {
184
+ try {
185
+ const original = fs.readFileSync(filePath, "utf-8");
186
+ let matcher;
187
+ if (useRegex) {
188
+ try {
189
+ const flags = replaceAll ? "g" : "";
190
+ matcher = new RegExp(oldString, ignoreCase ? flags + "i" : flags);
191
+ } catch (e) {
192
+ return { content: [{ type: "text", text: "❌ 正则表达式无效: " + e.message }], isError: true };
193
+ }
194
+ }
195
+ let count;
196
+ let updated;
197
+ if (useRegex) {
198
+ const globalMatcher = new RegExp(matcher.source, "g" + (ignoreCase ? "i" : ""));
199
+ const matches = original.match(globalMatcher);
200
+ count = matches ? matches.length : 0;
201
+ const replaceMatcher = replaceAll ? globalMatcher : new RegExp(matcher.source, ignoreCase ? "i" : "");
202
+ updated = original.replace(replaceMatcher, newString);
203
+ } else {
204
+ if (oldString === "") {
205
+ return { content: [{ type: "text", text: "❌ oldString 不能为空字符串" }], isError: true };
206
+ }
207
+ let idx = 0, c = 0;
208
+ const hay = ignoreCase ? original.toLowerCase() : original;
209
+ const needle = ignoreCase ? oldString.toLowerCase() : oldString;
210
+ while ((idx = hay.indexOf(needle, idx)) !== -1) { c++; idx += needle.length; }
211
+ count = c;
212
+ if (replaceAll) {
213
+ const esc = oldString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
214
+ updated = original.replace(new RegExp(esc, ignoreCase ? "gi" : "g"), newString);
215
+ } else {
216
+ const pos = ignoreCase ? hay.indexOf(needle) : original.indexOf(oldString);
217
+ if (pos === -1) {
218
+ updated = original;
219
+ } else {
220
+ updated = original.slice(0, pos) + newString + original.slice(pos + oldString.length);
221
+ }
222
+ }
223
+ }
224
+ if (count === 0) {
225
+ return {
226
+ content: [{ type: "text", text: "⚠️ 未找到匹配内容,文件未修改。请检查 oldString(或正则)是否正确: " + filePath }],
227
+ isError: true,
228
+ };
229
+ }
230
+ let warning = "";
231
+ if (!replaceAll && count > 1) {
232
+ warning = "\n⚠️ 注意:共匹配 " + count + " 处,但 replaceAll=false 仅替换了第一处。如需全部替换请设 replaceAll=true。";
233
+ }
234
+ if (updated === original) {
235
+ return { content: [{ type: "text", text: "⚠️ 替换后内容无变化,文件未修改: " + filePath }] };
236
+ }
237
+ fs.writeFileSync(filePath, updated, "utf-8");
238
+ return {
239
+ content: [{ type: "text", text: "✅ 替换成功: " + filePath + "\n替换 " + (replaceAll ? count : 1) + "/" + count + " 处" + warning }],
240
+ };
241
+ } catch (e) {
242
+ if (e.code === "ENOENT") {
243
+ return { content: [{ type: "text", text: "❌ 文件不存在: " + filePath }], isError: true };
244
+ }
245
+ return { content: [{ type: "text", text: "❌ 替换失败: " + e.message }], isError: true };
246
+ }
247
+ }
248
+ );
249
+
250
+ // 注册 search_files 工具(内容搜索,替代受加密影响的 Grep)
251
+ server.tool(
252
+ "search_files",
253
+ "在指定目录递归搜索文件内容(明文)。加密软件环境下内置 Grep(ripgrep) 只能读到密文搜不到内容,本工具用 Node.js fs 读取后正则匹配。替代内置 Grep 工具。",
254
+ {
255
+ pattern: z.string().describe("正则表达式(如 log.*Error、function\\s+\\w+)"),
256
+ path: z.string().describe("搜索根目录,支持相对路径或绝对路径"),
257
+ include: z.string().optional().describe("文件名 glob 过滤,多个用逗号分隔(如 *.java,*.xml)。不传则搜索全部文件"),
258
+ ignoreCase: z.boolean().optional().describe("是否忽略大小写,默认 false"),
259
+ onlyMatching: z.boolean().optional().describe("是否只输出匹配部分(非整行),默认 false 输出整行"),
260
+ maxResults: z.number().optional().describe("最大返回匹配数,默认 200。超过会在末尾提示被截断"),
261
+ },
262
+ { readOnlyHint: true },
263
+ async ({ pattern, path: rootDir, include, ignoreCase, onlyMatching, maxResults }) => {
264
+ try {
265
+ // 修正:忽略大小写时需同时携带 g 与 i 标志,否则 ignoreCase 参数失效
266
+ const flags = ignoreCase ? "gi" : "g";
267
+ let regex;
268
+ try {
269
+ regex = new RegExp(pattern, flags);
270
+ } catch (e) {
271
+ return { content: [{ type: "text", text: "❌ 正则表达式无效: " + e.message }], isError: true };
272
+ }
273
+ const includeList = include
274
+ ? include.split(",").map((s) => s.trim()).filter(Boolean)
275
+ : null;
276
+ // include glob 匹配: 仅比对文件名(不含目录), * -> .*, 其余转义
277
+ const matchesInclude = (name) => {
278
+ if (!includeList) return true;
279
+ return includeList.some((pat) => {
280
+ const re = new RegExp("^" + pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$");
281
+ return re.test(name);
282
+ });
283
+ };
284
+ const limit = maxResults || 200;
285
+ const results = [];
286
+ let truncated = false;
287
+ let scanned = 0;
288
+ let matchedFiles = 0;
289
+ // 对单个文件做内容匹配, 复用与目录遍历相同的行级逻辑
290
+ const scanFile = (full) => {
291
+ if (!matchesInclude(path.basename(full))) return;
292
+ scanned++;
293
+ let content;
294
+ try {
295
+ content = fs.readFileSync(full, "utf-8");
296
+ } catch (e) {
297
+ return;
298
+ }
299
+ const lines = content.split(/\r?\n/);
300
+ let fileHit = false;
301
+ for (let i = 0; i < lines.length; i++) {
302
+ if (truncated) break;
303
+ const line = lines[i];
304
+ if (onlyMatching) {
305
+ // onlyMatching 模式:输出一行内的所有匹配片段(原实现仅取第一个)
306
+ regex.lastIndex = 0;
307
+ let m;
308
+ while ((m = regex.exec(line)) !== null) {
309
+ if (!fileHit) { fileHit = true; matchedFiles++; }
310
+ results.push(full + ":" + (i + 1) + ":" + m[0]);
311
+ if (results.length >= limit) { truncated = true; break; }
312
+ // 防止零宽匹配导致死循环
313
+ if (m.index === regex.lastIndex) regex.lastIndex++;
314
+ }
315
+ } else {
316
+ // 整行模式:一行只输出一条
317
+ regex.lastIndex = 0;
318
+ if (regex.exec(line)) {
319
+ if (!fileHit) { fileHit = true; matchedFiles++; }
320
+ results.push(full + ":" + (i + 1) + ":" + line);
321
+ if (results.length >= limit) { truncated = true; }
322
+ }
323
+ }
324
+ }
325
+ };
326
+ const walk = (dir) => {
327
+ if (truncated) return;
328
+ let entries;
329
+ try {
330
+ entries = fs.readdirSync(dir, { withFileTypes: true });
331
+ } catch (e) {
332
+ return;
333
+ }
334
+ for (const entry of entries) {
335
+ if (truncated) return;
336
+ const full = path.join(dir, entry.name);
337
+ if (entry.isDirectory()) {
338
+ if (["node_modules", ".git", "target", "build", "dist", ".idea", ".vscode"].includes(entry.name)) continue;
339
+ walk(full);
340
+ } else if (entry.isFile()) {
341
+ scanFile(full);
342
+ }
343
+ }
344
+ };
345
+ // path 既可能是目录也可能是单个文件: 文件直接搜, 目录递归遍历
346
+ // 修复: 旧实现无视 path 类型一律 readdirSync, 传入文件路径时 ENOTDIR 被吞,
347
+ // 静默返回"扫描 0 个文件", 误导调用方以为无匹配
348
+ let stat;
349
+ try {
350
+ stat = fs.statSync(rootDir);
351
+ } catch (e) {
352
+ if (e.code === "ENOENT") {
353
+ return { content: [{ type: "text", text: "❌ 路径不存在: " + rootDir }], isError: true };
354
+ }
355
+ return { content: [{ type: "text", text: "❌ 无法访问路径: " + e.message }], isError: true };
356
+ }
357
+ if (stat.isFile()) {
358
+ scanFile(rootDir);
359
+ } else if (stat.isDirectory()) {
360
+ walk(rootDir);
361
+ }
362
+ let text = results.join("\n");
363
+ if (results.length === 0) {
364
+ text = "未找到匹配项(扫描 " + scanned + " 个文件,根目录: " + rootDir + ")";
365
+ } else {
366
+ text = "找到 " + results.length + " 处匹配(" + matchedFiles + " 个文件,扫描 " + scanned + " 个文件):\n" + text;
367
+ if (truncated) text += "\n... 结果已达上限 " + limit + ",被截断。可通过 maxResults 调大。";
368
+ }
369
+ return { content: [{ type: "text", text }] };
370
+ } catch (e) {
371
+ return { content: [{ type: "text", text: "❌ 搜索失败: " + e.message }], isError: true };
372
+ }
373
+ }
374
+ );
375
+
376
+ // 注册 create_directory 工具(递归创建目录)
377
+ server.tool(
378
+ "create_directory",
379
+ "递归创建目录(类似 mkdir -p)。加密软件环境下,Node.js 白名单进程操作目录同样安全。",
380
+ { path: z.string().describe("要创建的目录路径,支持相对路径或绝对路径") },
381
+ async ({ path: dirPath }) => {
382
+ try {
383
+ fs.mkdirSync(dirPath, { recursive: true });
384
+ return { content: [{ type: "text", text: "✅ 目录已创建(或已存在): " + dirPath }] };
385
+ } catch (e) {
386
+ return { content: [{ type: "text", text: "❌ 创建目录失败: " + e.message }], isError: true };
387
+ }
388
+ }
389
+ );
390
+
391
+ // 注册 file_info 工具(查询文件/目录信息)
392
+ server.tool(
393
+ "file_info",
394
+ "查询文件或目录的信息:是否存在、类型、大小、修改时间等。加密软件环境下 stat 不读内容,结果准确。",
395
+ { path: z.string().describe("文件或目录路径,支持相对路径或绝对路径") },
396
+ { readOnlyHint: true },
397
+ async ({ path: filePath }) => {
398
+ try {
399
+ const stat = fs.statSync(filePath);
400
+ const info = {
401
+ path: filePath,
402
+ exists: true,
403
+ type: stat.isDirectory() ? "directory" : "file",
404
+ size: stat.size,
405
+ modifiedTime: stat.mtime.toISOString(),
406
+ createdTime: stat.birthtime.toISOString(),
407
+ };
408
+ return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
409
+ } catch (e) {
410
+ if (e.code === "ENOENT") {
411
+ return { content: [{ type: "text", text: JSON.stringify({ path: filePath, exists: false }, null, 2) }] };
412
+ }
413
+ return { content: [{ type: "text", text: "❌ 查询失败: " + e.message }], isError: true };
414
+ }
415
+ }
416
+ );
417
+
418
+ // 注册 check_status 工具
419
+ server.tool(
420
+ "check_status",
421
+ "检查文件操作工具的运行状态,确认 Node.js 进程能否正常读取加密文件明文。",
422
+ {},
423
+ { readOnlyHint: true },
424
+ async () => {
425
+ return { content: [{ type: "text", text: "✅ read-file-server 运行中\n平台: Node.js " + process.version + "\n功能: 通过 Node.js fs 读写文件明文(加密软件白名单中的 Node.js 进程自动解密/加密)" }] };
426
+ }
427
+ );
428
+
429
+ // 启动服务
430
+ async function main() {
431
+ const transport = new StdioServerTransport();
432
+ await server.connect(transport);
433
+ }
434
+
435
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "mcp-read-file-server",
3
+ "version": "1.0.0",
4
+ "description": "加密环境文件操作 MCP 工具。当 Node.js 是加密软件白名单进程时,通过 fs 模块自动解密读写文件明文,替代 AI Agent 内置文件工具,适用于任何支持 MCP 协议的 Agent。",
5
+ "type": "commonjs",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "mcp-read-file-server": "index.js"
9
+ },
10
+ "files": [
11
+ "index.js",
12
+ "README.md",
13
+ "SKILL.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "start": "node index.js",
18
+ "test": "echo \"Error: no test specified\" && exit 1"
19
+ },
20
+ "keywords": [
21
+ "mcp",
22
+ "mcp-server",
23
+ "model-context-protocol",
24
+ "file-operations",
25
+ "file-reader",
26
+ "encryption",
27
+ "claude",
28
+ "ai-agent",
29
+ "stdio"
30
+ ],
31
+ "author": "hebulin",
32
+ "license": "MIT",
33
+ "homepage": "https://github.com/hebulin/mcp-read-file-server#readme",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/hebulin/mcp-read-file-server.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/hebulin/mcp-read-file-server/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "dependencies": {
48
+ "@modelcontextprotocol/sdk": "^1.29.0",
49
+ "zod": "^4.4.3"
50
+ }
51
+ }