@j0hanz/filesystem-mcp 1.1.2 → 1.2.1

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 (62) hide show
  1. package/README.md +514 -188
  2. package/dist/cli.js +29 -12
  3. package/dist/completions.js +50 -24
  4. package/dist/config.d.ts +4 -2
  5. package/dist/config.js +2 -1
  6. package/dist/index.js +14 -12
  7. package/dist/instructions.md +109 -97
  8. package/dist/lib/constants.js +25 -14
  9. package/dist/lib/errors.js +15 -8
  10. package/dist/lib/file-operations/common.d.ts +4 -0
  11. package/dist/lib/file-operations/common.js +9 -0
  12. package/dist/lib/file-operations/file-info.js +22 -10
  13. package/dist/lib/file-operations/gitignore.js +14 -11
  14. package/dist/lib/file-operations/glob-engine.d.ts +1 -0
  15. package/dist/lib/file-operations/glob-engine.js +46 -33
  16. package/dist/lib/file-operations/list-directory.js +31 -35
  17. package/dist/lib/file-operations/read-multiple-files.js +70 -62
  18. package/dist/lib/file-operations/search-content.js +83 -64
  19. package/dist/lib/file-operations/search-files.js +32 -30
  20. package/dist/lib/file-operations/search-worker.js +22 -12
  21. package/dist/lib/file-operations/tree.js +43 -34
  22. package/dist/lib/fs-helpers.js +61 -124
  23. package/dist/lib/observability.js +29 -28
  24. package/dist/lib/path-format.d.ts +1 -0
  25. package/dist/lib/path-format.js +7 -0
  26. package/dist/lib/path-policy.js +22 -20
  27. package/dist/lib/path-validation.js +13 -7
  28. package/dist/lib/resource-store.d.ts +2 -0
  29. package/dist/lib/resource-store.js +26 -5
  30. package/dist/lib/type-guards.d.ts +1 -0
  31. package/dist/lib/type-guards.js +3 -0
  32. package/dist/prompts.d.ts +1 -5
  33. package/dist/prompts.js +9 -16
  34. package/dist/resources.d.ts +1 -5
  35. package/dist/resources.js +12 -26
  36. package/dist/schemas.d.ts +232 -30
  37. package/dist/schemas.js +52 -90
  38. package/dist/server.js +96 -44
  39. package/dist/tools/apply-patch.js +23 -22
  40. package/dist/tools/calculate-hash.js +41 -43
  41. package/dist/tools/create-directory.js +17 -19
  42. package/dist/tools/delete-file.js +35 -37
  43. package/dist/tools/diff-files.js +15 -19
  44. package/dist/tools/edit-file.js +15 -18
  45. package/dist/tools/list-directory.js +24 -23
  46. package/dist/tools/move-file.js +17 -19
  47. package/dist/tools/read-multiple.js +55 -66
  48. package/dist/tools/read.js +26 -30
  49. package/dist/tools/replace-in-files.js +27 -33
  50. package/dist/tools/roots.js +8 -8
  51. package/dist/tools/search-content.js +73 -72
  52. package/dist/tools/search-files.js +44 -50
  53. package/dist/tools/shared.d.ts +44 -6
  54. package/dist/tools/shared.js +86 -64
  55. package/dist/tools/stat-many.js +44 -66
  56. package/dist/tools/stat.js +10 -37
  57. package/dist/tools/task-support.d.ts +9 -1
  58. package/dist/tools/task-support.js +86 -81
  59. package/dist/tools/tree.js +12 -28
  60. package/dist/tools/write-file.js +17 -19
  61. package/dist/tools.js +23 -18
  62. package/package.json +6 -7
package/README.md CHANGED
@@ -2,180 +2,104 @@
2
2
 
3
3
  ![npm version](https://img.shields.io/npm/v/@j0hanz/filesystem-mcp) ![License](https://img.shields.io/npm/l/@j0hanz/filesystem-mcp) ![Node.js Version](https://img.shields.io/node/v/@j0hanz/filesystem-mcp) ![Docker Image](https://ghcr-badge.egpl.dev/j0hanz/filesystem-mcp/latest_tag?trim=major&label=docker)
4
4
 
5
- [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0078d7?logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%7B%22name%22%3A%22filesystem-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40j0hanz%2Ffilesystem-mcp%40latest%22%5D%7D) [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect?url=vscode-insiders%3Amcp%2Finstall%3F%7B%22name%22%3A%22filesystem-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40j0hanz%2Ffilesystem-mcp%40latest%22%5D%7D) [![Install in Claude Desktop](https://img.shields.io/badge/Claude_Desktop-Install-f79a2e?logo=claude&logoColor=white)](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-server) [![Install in Cursor](https://img.shields.io/badge/Cursor-Install-000000?logo=cursor&logoColor=white)](https://cursor.com/deeplink/mcp-install?name=filesystem-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBqMGhhbnovZmlsZXN5c3RlbS1tY3BAbGF0ZXN0Il19)
5
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=filesystem-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40j0hanz%2Ffilesystem-mcp%40latest%22%5D%7D) [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=filesystem-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40j0hanz%2Ffilesystem-mcp%40latest%22%5D%7D&quality=insiders)
6
6
 
7
- MCP Server that enables LLMs to interact with the local filesystem. Provides tools for navigation, file management, searching, and analysis, all within a secure, allowed set of directories. Ideal for agents needing to read/write files, explore directory structures, or perform file operations as part of their tasks.
7
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/deeplink/mcp-install?name=filesystem-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBqMGhhbnovZmlsZXN5c3RlbS1tY3BAbGF0ZXN0Il19)
8
8
 
9
- ## Key Features
10
-
11
- - **Navigation**: List directories (`ls`), view trees (`tree`), and discover workspace roots (`roots`).
12
- - **File Management**: Create (`mkdir`), write (`write`), edit (`edit`), move (`mv`), and delete (`rm`) files/directories.
13
- - **Search**: Find files by glob pattern (`find`) or search content using regex (`grep`).
14
- - **Analysis**: Inspect metadata (`stat`), calculate hashes (`calculate_hash`), and compare files (`diff_files`).
15
- - **Batch Operations**: Read multiple files (`read_many`) or replace text across many files (`search_and_replace`).
16
- - **Security**: Strictly scoped to allowed directories provided at startup.
9
+ MCP Server that enables LLMs to interact with the local filesystem. Provides tools for navigation, search, file management, and analysis — all scoped to allowed directories.
17
10
 
18
- ## Tech Stack
11
+ ## Overview
19
12
 
20
- - **Runtime**: Node.js >= 24
21
- - **Language**: TypeScript
22
- - **SDK**: `@modelcontextprotocol/sdk`
23
- - **Libraries**: `zod`, `commander`, `re2`, `diff`
13
+ Filesystem MCP exposes a rich set of tools for reading, writing, searching, and inspecting files and directories. All operations are strictly bounded to the directories you provide at startup, preventing access to any path outside those roots.
24
14
 
25
- ## Repository Structure
15
+ ## Key Features
26
16
 
27
- ```text
28
- .
29
- ├── src/
30
- │ ├── index.ts # Entry point
31
- │ ├── server.ts # MCP Server implementation
32
- │ ├── tools/ # Individual tool implementations
33
- │ └── lib/ # Shared utilities
34
- ├── assets/ # Images and static resources
35
- ├── scripts/ # Build and maintenance scripts
36
- └── package.json
37
- ```
17
+ - **Navigation**: List directory contents (`ls`), render trees (`tree`), and query workspace roots (`roots`).
18
+ - **File I/O**: Read single or multiple files (`read`, `read_many`); write, edit, move, and delete (`write`, `edit`, `mv`, `rm`).
19
+ - **Search**: Find files by glob pattern (`find`) or search content with full regex support (`grep`).
20
+ - **Analysis**: Metadata and token estimates (`stat`, `stat_many`), SHA-256 hashing (`calculate_hash`), and unified diffs (`diff_files`).
21
+ - **Patch & Replace**: Apply unified patches (`apply_patch`) and bulk search-and-replace across files (`search_and_replace`).
22
+ - **Tasks**: Long-running tools support background task execution with progress notifications and cancellation.
23
+ - **Large Output Handling**: Oversized results are externalized to ephemeral resource URIs instead of truncating inline.
24
+ - **Security**: Strict path validation, safe regex via RE2, `.gitignore`-aware operations, and atomic writes.
38
25
 
39
26
  ## Requirements
40
27
 
41
- - Node.js >= 24
42
-
43
- ## Quickstart
28
+ - **Node.js** `>= 24`
44
29
 
45
- Run directly with `npx`:
30
+ ## Quick Start
46
31
 
47
- ```bash
48
- npx -y @j0hanz/filesystem-mcp@latest "C:\path\to\allowed\directory"
49
- ```
50
-
51
- ## Installation
52
-
53
- ### NPX (Recommended)
54
-
55
- ```bash
56
- npx -y @j0hanz/filesystem-mcp@latest [options] [directories...]
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "filesystem-mcp": {
36
+ "command": "npx",
37
+ "args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/allowed/dir"]
38
+ }
39
+ }
40
+ }
57
41
  ```
58
42
 
59
- ### Docker
43
+ Run directly:
60
44
 
61
45
  ```bash
62
- docker run -i --rm \
63
- -v /path/to/your/project:/projects/workspace:ro \
64
- ghcr.io/j0hanz/filesystem-mcp:latest \
65
- /projects/workspace
46
+ npx -y @j0hanz/filesystem-mcp@latest /path/to/allowed/dir
66
47
  ```
67
48
 
68
- > Mount host directories as volumes to `/projects/` and pass the container paths as arguments.
69
-
70
- ### From Source
71
-
72
- 1. Clone the repository
73
- 2. Install dependencies:
74
-
75
- ```bash
76
- npm ci
77
- ```
78
-
79
- 3. Build the project:
80
-
81
- ```bash
82
- npm run build
83
- ```
84
-
85
- 4. Run:
49
+ ## Client Configuration
86
50
 
87
- ```bash
88
- node dist/index.js [options] [directories...]
89
- ```
90
-
91
- ## Configuration
92
-
93
- Allowed directories can be provided via command-line arguments, via the MCP Roots protocol, or by using `--allow-cwd`.
94
-
95
- ### Arguments
96
-
97
- | Argument | Description |
98
- | :----------------- | :-------------------------------------------------------------------------- |
99
- | `[allowedDirs...]` | Positional arguments specifying the root directories the server can access. |
100
-
101
- ### Options
102
-
103
- | Option | Description |
104
- | :-------------- | :--------------------------------------------------------- |
105
- | `--allow-cwd` | Allow the current working directory as an additional root. |
106
- | `-v, --version` | Display server version. |
107
- | `-h, --help` | Display command help. |
51
+ <details>
52
+ <summary><b>Install in VS Code</b></summary>
108
53
 
109
- ## Usage
54
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=filesystem-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40j0hanz%2Ffilesystem-mcp%40latest%22%5D%7D)
110
55
 
111
- ### Stdio Transport
56
+ Or add manually to `.vscode/mcp.json`:
112
57
 
113
- The server communicates via `stdio`. Ensure your MCP client is configured to run the server command and capture standard input/output.
58
+ ```json
59
+ {
60
+ "servers": {
61
+ "filesystem-mcp": {
62
+ "command": "npx",
63
+ "args": ["-y", "@j0hanz/filesystem-mcp@latest", "${workspaceFolder}"]
64
+ }
65
+ }
66
+ }
67
+ ```
114
68
 
115
- ## MCP Surface
69
+ CLI:
116
70
 
117
- ### Tools
71
+ ```bash
72
+ code --add-mcp '{"name":"filesystem-mcp","command":"npx","args":["-y","@j0hanz/filesystem-mcp@latest","${workspaceFolder}"]}'
73
+ ```
118
74
 
119
- | Tool | Description | Key Parameters |
120
- | :------------------- | :---------------------------------- | :-------------------------------------------- |
121
- | `roots` | List allowed workspace roots | None |
122
- | `ls` | List directory contents | `path`, `includeHidden` |
123
- | `find` | Find files by glob pattern | `pattern`, `path`, `maxDepth` |
124
- | `tree` | Generate directory tree | `path`, `maxDepth` |
125
- | `read` | Read file content | `path`, `head` |
126
- | `read_many` | Read multiple files | `paths` |
127
- | `grep` | Search file content (regex/literal) | `pattern`, `path`, `isRegex` |
128
- | `stat` | Get file metadata | `path` |
129
- | `stat_many` | Get metadata for multiple files | `paths` |
130
- | `calculate_hash` | Calculate SHA-256 hash | `path` |
131
- | `mkdir` | Create directory (recursive) | `path` |
132
- | `write` | Write file (create/overwrite) | `path`, `content` |
133
- | `edit` | Edit file (string replacement) | `path`, `edits` |
134
- | `mv` | Move or rename file/directory | `source`, `destination` |
135
- | `rm` | Delete file or directory | `path`, `recursive` |
136
- | `diff_files` | Generate unified diff | `original`, `modified` |
137
- | `apply_patch` | Apply unified patch | `path`, `patch` |
138
- | `search_and_replace` | Search & replace across files | `filePattern`, `searchPattern`, `replacement` |
139
-
140
- ### Behavioral Notes
141
-
142
- - `rm` with `recursive: false`:
143
- - Deletes files and empty directories.
144
- - Returns `E_INVALID_INPUT` for non-empty directories with guidance to use `recursive: true`.
145
- - `includeIgnored: false` (default) for navigation/search tools:
146
- - Excludes common generated/vendor directories such as `node_modules`, `dist`, `.git`, and similar patterns.
147
- - Set `includeIgnored: true` to include those entries.
75
+ </details>
148
76
 
149
- ### Resources
77
+ <details>
78
+ <summary><b>Install in VS Code Insiders</b></summary>
150
79
 
151
- | URI Pattern | Description |
152
- | :----------------------------- | :----------------------------------------------- |
153
- | `internal://instructions` | Usage guidance and documentation |
154
- | `filesystem-mcp://result/{id}` | Ephemeral cached tool output (for large results) |
80
+ [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=filesystem-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40j0hanz%2Ffilesystem-mcp%40latest%22%5D%7D&quality=insiders)
155
81
 
156
- ### Prompts
82
+ CLI:
157
83
 
158
- | Prompt | Description |
159
- | :--------- | :---------------------------------------- |
160
- | `get-help` | Returns usage instructions for the server |
84
+ ```bash
85
+ code-insiders --add-mcp '{"name":"filesystem-mcp","command":"npx","args":["-y","@j0hanz/filesystem-mcp@latest","${workspaceFolder}"]}'
86
+ ```
161
87
 
162
- ## Client Configuration Examples
88
+ </details>
163
89
 
164
90
  <details>
165
- <summary><strong>Claude Desktop</strong></summary>
91
+ <summary><b>Install in Cursor</b></summary>
166
92
 
167
- Add to your `claude_desktop_config.json`:
93
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/deeplink/mcp-install?name=filesystem-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBqMGhhbnovZmlsZXN5c3RlbS1tY3BAbGF0ZXN0Il19)
94
+
95
+ Or add to `~/.cursor/mcp.json`:
168
96
 
169
97
  ```json
170
98
  {
171
99
  "mcpServers": {
172
- "filesystem": {
100
+ "filesystem-mcp": {
173
101
  "command": "npx",
174
- "args": [
175
- "-y",
176
- "@j0hanz/filesystem-mcp@latest",
177
- "C:\\path\\to\\allowed\\directory"
178
- ]
102
+ "args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/dir"]
179
103
  }
180
104
  }
181
105
  }
@@ -184,32 +108,41 @@ Add to your `claude_desktop_config.json`:
184
108
  </details>
185
109
 
186
110
  <details>
187
- <summary><strong>Cursor</strong></summary>
111
+ <summary><b>Install in Claude Desktop</b></summary>
188
112
 
189
- Add to your `.cursor/mcp.json` or configure via UI:
113
+ Add to `claude_desktop_config.json`:
190
114
 
191
115
  ```json
192
116
  {
193
- "id": "filesystem",
194
- "name": "filesystem",
195
- "command": "npx",
196
- "args": ["-y", "@j0hanz/filesystem-mcp@latest", "${workspaceFolder}"]
117
+ "mcpServers": {
118
+ "filesystem-mcp": {
119
+ "command": "npx",
120
+ "args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/dir"]
121
+ }
122
+ }
197
123
  }
198
124
  ```
199
125
 
200
126
  </details>
201
127
 
202
128
  <details>
203
- <summary><strong>VS Code</strong></summary>
129
+ <summary><b>Install in Claude Code</b></summary>
130
+
131
+ ```bash
132
+ claude mcp add filesystem-mcp -- npx -y @j0hanz/filesystem-mcp@latest /path/to/dir
133
+ ```
204
134
 
205
- Add to your `.vscode/mcp.json`:
135
+ </details>
136
+
137
+ <details>
138
+ <summary><b>Install in Windsurf</b></summary>
206
139
 
207
140
  ```json
208
141
  {
209
142
  "mcpServers": {
210
- "filesystem": {
143
+ "filesystem-mcp": {
211
144
  "command": "npx",
212
- "args": ["-y", "@j0hanz/filesystem-mcp@latest", "${workspaceFolder}"]
145
+ "args": ["-y", "@j0hanz/filesystem-mcp@latest", "/path/to/dir"]
213
146
  }
214
147
  }
215
148
  }
@@ -218,14 +151,32 @@ Add to your `.vscode/mcp.json`:
218
151
  </details>
219
152
 
220
153
  <details>
221
- <summary><strong>Docker (any MCP client)</strong></summary>
154
+ <summary><b>Install in Codex</b></summary>
155
+
156
+ ```toml
157
+ [mcp_servers.filesystem-mcp]
158
+ command = "npx"
159
+ args = ["-y", "@j0hanz/filesystem-mcp@latest", "${workspaceFolder}"]
160
+ ```
222
161
 
223
- Use the Docker image with any MCP client that supports stdio transport:
162
+ </details>
163
+
164
+ <details>
165
+ <summary><b>Docker</b></summary>
166
+
167
+ ```bash
168
+ docker run -i --rm \
169
+ -v /path/to/your/project:/projects/workspace:ro \
170
+ ghcr.io/j0hanz/filesystem-mcp:latest \
171
+ /projects/workspace
172
+ ```
173
+
174
+ MCP config:
224
175
 
225
176
  ```json
226
177
  {
227
178
  "mcpServers": {
228
- "filesystem": {
179
+ "filesystem-mcp": {
229
180
  "command": "docker",
230
181
  "args": [
231
182
  "run",
@@ -243,64 +194,439 @@ Use the Docker image with any MCP client that supports stdio transport:
243
194
 
244
195
  </details>
245
196
 
246
- <details>
247
- <summary><strong>Codex</strong></summary>
197
+ ## MCP Surface
198
+
199
+ ### Tools
248
200
 
249
- Add to your configuration:
201
+ | Tool | Description |
202
+ | :------------------- | :--------------------------------------------------- |
203
+ | `roots` | List workspace roots the server can access |
204
+ | `ls` | List directory contents (non-recursive) |
205
+ | `find` | Find files by glob pattern |
206
+ | `tree` | Render a bounded directory tree |
207
+ | `read` | Read text content of a file |
208
+ | `read_many` | Read multiple files in one request |
209
+ | `stat` | Get file or directory metadata |
210
+ | `stat_many` | Get metadata for multiple paths |
211
+ | `grep` | Search file content by literal or RE2 regex |
212
+ | `calculate_hash` | Compute SHA-256 hash of a file or directory |
213
+ | `diff_files` | Generate a unified diff between two files |
214
+ | `mkdir` | Create a directory (recursive) |
215
+ | `write` | Write content to a file (create or overwrite) |
216
+ | `edit` | Edit a file via sequential string replacements |
217
+ | `mv` | Move or rename a file or directory |
218
+ | `rm` | Delete a file or directory |
219
+ | `apply_patch` | Apply a unified diff patch to a file |
220
+ | `search_and_replace` | Search and replace text across files matching a glob |
250
221
 
251
- ```toml
252
- [mcp_servers.filesystem-mcp]
253
- command = "npx"
254
- args = ["-y", "@j0hanz/filesystem-mcp@latest", "${workspaceFolder}"]
222
+ ---
223
+
224
+ #### `roots` — List workspace roots
225
+
226
+ Enumerate the directories the server is allowed to access. Call this first in any session.
227
+
228
+ No input parameters.
229
+
230
+ ---
231
+
232
+ #### `ls` — List directory contents
233
+
234
+ List immediate directory contents (non-recursive). Returns name, type, size, and modified date per entry.
235
+
236
+ | Parameter | Type | Required | Default | Description |
237
+ | :---------------------- | :--------------------------------- | :------: | :------ | :-------------------------------------------------------- |
238
+ | `path` | string | No | root | Base directory |
239
+ | `includeHidden` | boolean | No | `false` | Include hidden items (`.`) |
240
+ | `includeIgnored` | boolean | No | `false` | Include `node_modules`, `.git`, etc. |
241
+ | `pattern` | string | No | — | Glob filter enabling recursive traversal (e.g. `**/*.ts`) |
242
+ | `sortBy` | `name`\|`size`\|`modified`\|`type` | No | `name` | Sort field |
243
+ | `maxDepth` | integer | No | — | Max recursion depth when `pattern` is set (1–50) |
244
+ | `maxEntries` | integer | No | — | Truncation limit (1–20,000) |
245
+ | `includeSymlinkTargets` | boolean | No | `false` | Resolve and include symlink targets |
246
+
247
+ ---
248
+
249
+ #### `find` — Find files by glob
250
+
251
+ Locate files matching a glob pattern. Returns relative paths and metadata.
252
+
253
+ | Parameter | Type | Required | Default | Description |
254
+ | :--------------- | :--------------------------------- | :------: | :------ | :-------------------------------------------------- |
255
+ | `pattern` | string | Yes | — | Glob pattern (e.g. `**/*.ts`, `src/*.js`) |
256
+ | `path` | string | No | root | Search root |
257
+ | `maxResults` | integer | No | `100` | Max results (1–10,000) |
258
+ | `maxDepth` | integer | No | — | Max directory depth to scan (0–100) |
259
+ | `sortBy` | `name`\|`size`\|`modified`\|`path` | No | `path` | Sort field |
260
+ | `includeHidden` | boolean | No | `false` | Include hidden files |
261
+ | `includeIgnored` | boolean | No | `false` | Include ignored directories (disables `.gitignore`) |
262
+
263
+ > [!TIP]
264
+ > Supports background task execution with progress reporting.
265
+
266
+ ---
267
+
268
+ #### `tree` — Render directory tree
269
+
270
+ Returns both an ASCII tree (text) and a structured JSON tree.
271
+
272
+ | Parameter | Type | Required | Default | Description |
273
+ | :--------------- | :------ | :------: | :------ | :-------------------------------------------------- |
274
+ | `path` | string | No | root | Base directory |
275
+ | `maxDepth` | integer | No | `5` | Max depth (0–50); `0` = root node only, no children |
276
+ | `maxEntries` | integer | No | `1000` | Max entries (1–20,000) |
277
+ | `includeHidden` | boolean | No | `false` | Include hidden items |
278
+ | `includeIgnored` | boolean | No | `false` | Include ignored items (disables `.gitignore`) |
279
+
280
+ ---
281
+
282
+ #### `read` — Read file content
283
+
284
+ Read text content of a single file with optional line-range or head preview.
285
+
286
+ | Parameter | Type | Required | Default | Description |
287
+ | :---------- | :------ | :------: | :------ | :---------------------------------------------------------------------------- |
288
+ | `path` | string | Yes | — | Absolute path to file |
289
+ | `head` | integer | No | — | Read first N lines (1–100,000); mutually exclusive with `startLine`/`endLine` |
290
+ | `startLine` | integer | No | — | Start line (1-based, inclusive) |
291
+ | `endLine` | integer | No | — | End line (1-based, inclusive); requires `startLine` |
292
+
293
+ Large files return a `resourceUri`; call `resources/read` on that URI for full content.
294
+
295
+ ---
296
+
297
+ #### `read_many` — Read multiple files
298
+
299
+ Batch-read up to 100 files in a single request.
300
+
301
+ | Parameter | Type | Required | Default | Description |
302
+ | :---------- | :------- | :------: | :------ | :--------------------------------------- |
303
+ | `paths` | string[] | Yes | — | File paths (1–100 items) |
304
+ | `head` | integer | No | — | Read first N lines of each file |
305
+ | `startLine` | integer | No | — | Start line per file |
306
+ | `endLine` | integer | No | — | End line per file (requires `startLine`) |
307
+
308
+ Per-file `truncationReason` can be `head`, `range`, or `externalized`. Total read budget is capped internally.
309
+
310
+ ---
311
+
312
+ #### `stat` — Get file/directory metadata
313
+
314
+ Returns name, type, size, created/modified/accessed timestamps, permissions, MIME type, and a token estimate (`size ÷ 4`).
315
+
316
+ | Parameter | Type | Required | Default | Description |
317
+ | :-------- | :----- | :------: | :------ | :------------ |
318
+ | `path` | string | Yes | — | Absolute path |
319
+
320
+ ---
321
+
322
+ #### `stat_many` — Get metadata for multiple paths
323
+
324
+ Batch version of `stat`.
325
+
326
+ | Parameter | Type | Required | Default | Description |
327
+ | :-------- | :------- | :------: | :------ | :------------------ |
328
+ | `paths` | string[] | Yes | — | Paths (1–100 items) |
329
+
330
+ ---
331
+
332
+ #### `grep` — Search file content
333
+
334
+ Search for text within files using literal match or RE2 regex. Returns matching lines with optional context.
335
+
336
+ | Parameter | Type | Required | Default | Description |
337
+ | :--------------- | :------ | :------: | :------ | :------------------------------------------------------------------ |
338
+ | `pattern` | string | Yes | — | Text to search (literal by default, RE2 regex when `isRegex: true`) |
339
+ | `path` | string | No | root | Search root (file or directory) |
340
+ | `isRegex` | boolean | No | `false` | Treat `pattern` as RE2 regex |
341
+ | `caseSensitive` | boolean | No | `false` | Case-sensitive matching |
342
+ | `wholeWord` | boolean | No | `false` | Match whole words only |
343
+ | `contextLines` | integer | No | `0` | Lines of context before/after each match (0–50) |
344
+ | `maxResults` | integer | No | `500` | Max match rows returned (0–10,000) |
345
+ | `filePattern` | string | No | `**/*` | Glob to restrict candidate files (e.g. `**/*.ts`) |
346
+ | `includeHidden` | boolean | No | `false` | Include hidden files |
347
+ | `includeIgnored` | boolean | No | `false` | Include ignored directories |
348
+
349
+ > [!NOTE]
350
+ > RE2 does not support lookahead, lookbehind, or backreferences. Results exceeding 50 inline matches are externalized via `resourceUri`.
351
+
352
+ ---
353
+
354
+ #### `calculate_hash` — SHA-256 hash
355
+
356
+ Compute a SHA-256 hash. For directories, produces a deterministic composite hash of all contained files (lexicographically sorted, `.gitignore`-aware).
357
+
358
+ | Parameter | Type | Required | Default | Description |
359
+ | :-------- | :----- | :------: | :------ | :--------------------- |
360
+ | `path` | string | Yes | — | File or directory path |
361
+
362
+ ---
363
+
364
+ #### `diff_files` — Generate unified diff
365
+
366
+ Create a unified diff between two files. Check `isIdentical` in the response — if `true`, the files match and no patch is needed.
367
+
368
+ | Parameter | Type | Required | Default | Description |
369
+ | :----------------- | :------ | :------: | :------ | :--------------------------------- |
370
+ | `original` | string | Yes | — | Original file path |
371
+ | `modified` | string | Yes | — | Modified file path |
372
+ | `context` | integer | No | — | Lines of context in the diff |
373
+ | `ignoreWhitespace` | boolean | No | `false` | Ignore leading/trailing whitespace |
374
+ | `stripTrailingCr` | boolean | No | `false` | Strip trailing carriage returns |
375
+
376
+ Large diffs are externalized to a `resourceUri`.
377
+
378
+ ---
379
+
380
+ #### `mkdir` — Create directory
381
+
382
+ Create a directory and all missing parent directories (recursive).
383
+
384
+ | Parameter | Type | Required | Default | Description |
385
+ | :-------- | :----- | :------: | :------ | :----------------------- |
386
+ | `path` | string | Yes | — | Directory path to create |
387
+
388
+ ---
389
+
390
+ #### `write` — Write file
391
+
392
+ Create or overwrite a file. Parent directories are created automatically.
393
+
394
+ | Parameter | Type | Required | Default | Description |
395
+ | :-------- | :----- | :------: | :------ | :--------------- |
396
+ | `path` | string | Yes | — | File path |
397
+ | `content` | string | Yes | — | Content to write |
398
+
399
+ > [!CAUTION]
400
+ > Overwrites existing file content without confirmation.
401
+
402
+ ---
403
+
404
+ #### `edit` — Edit file
405
+
406
+ Apply sequential literal string replacements to an existing file. Replaces the **first** occurrence of each `oldText`.
407
+
408
+ | Parameter | Type | Required | Default | Description |
409
+ | :-------- | :--------------------- | :------: | :------ | :------------------------------------- |
410
+ | `path` | string | Yes | — | File to edit |
411
+ | `edits` | `{oldText, newText}[]` | Yes | — | Ordered list of replacement operations |
412
+ | `dryRun` | boolean | No | `false` | Validate edits without writing |
413
+
414
+ Include 3–5 lines of surrounding context in `oldText` to uniquely target the location. Unmatched edits are reported in `unmatchedEdits`.
415
+
416
+ ---
417
+
418
+ #### `mv` — Move or rename
419
+
420
+ Move or rename a file or directory. Parent directories of the destination are created automatically. Falls back to copy+delete for cross-device moves.
421
+
422
+ | Parameter | Type | Required | Default | Description |
423
+ | :------------ | :----- | :------: | :------ | :--------------- |
424
+ | `source` | string | Yes | — | Source path |
425
+ | `destination` | string | Yes | — | Destination path |
426
+
427
+ ---
428
+
429
+ #### `rm` — Delete file or directory
430
+
431
+ Delete a file or directory.
432
+
433
+ | Parameter | Type | Required | Default | Description |
434
+ | :------------------ | :------ | :------: | :------ | :------------------------------ |
435
+ | `path` | string | Yes | — | Path to delete |
436
+ | `recursive` | boolean | No | `false` | Delete non-empty directories |
437
+ | `ignoreIfNotExists` | boolean | No | `false` | No error if the path is missing |
438
+
439
+ > [!WARNING]
440
+ > Non-empty directories with `recursive: false` return `E_INVALID_INPUT` with guidance to retry using `recursive: true`.
441
+
442
+ ---
443
+
444
+ #### `apply_patch` — Apply unified patch
445
+
446
+ Apply a unified diff patch to a file. Always validate with `dryRun: true` before writing.
447
+
448
+ | Parameter | Type | Required | Default | Description |
449
+ | :----------------------- | :------ | :------: | :------ | :----------------------------------------------------- |
450
+ | `path` | string | Yes | — | Target file path |
451
+ | `patch` | string | Yes | — | Unified diff patch content (must include hunk headers) |
452
+ | `fuzzFactor` | integer | No | `0` | Fuzzy matching tolerance |
453
+ | `autoConvertLineEndings` | boolean | No | `true` | Auto-convert line endings to match the target file |
454
+ | `dryRun` | boolean | No | `false` | Validate without writing |
455
+
456
+ If patch application fails, regenerate a fresh patch via `diff_files` against the current file content and retry.
457
+
458
+ ---
459
+
460
+ #### `search_and_replace` — Search and replace across files
461
+
462
+ Replace text in all files matching a glob. Replaces **all** occurrences per file. Use `dryRun: true` to preview scope before writing.
463
+
464
+ | Parameter | Type | Required | Default | Description |
465
+ | :-------------- | :------ | :------: | :------ | :----------------------------------------------------------------------- |
466
+ | `filePattern` | string | Yes | — | Glob for target files (e.g. `**/*.ts`) |
467
+ | `searchPattern` | string | Yes | — | Text to find |
468
+ | `replacement` | string | Yes | — | Replacement text |
469
+ | `path` | string | No | root | Search root directory |
470
+ | `isRegex` | boolean | No | `false` | Treat `searchPattern` as RE2 regex; supports capture groups (`$1`, `$2`) |
471
+ | `dryRun` | boolean | No | `false` | Preview matches without writing |
472
+
473
+ ---
474
+
475
+ ### Resources
476
+
477
+ | URI | Description | MIME Type |
478
+ | :----------------------------- | :--------------------------------- | :-------------- |
479
+ | `internal://instructions` | Usage guidance for models | `text/markdown` |
480
+ | `filesystem-mcp://result/{id}` | Ephemeral cached large tool output | varies |
481
+
482
+ ### Prompts
483
+
484
+ | Prompt | Description |
485
+ | :--------- | :---------------------------------------- |
486
+ | `get-help` | Returns usage instructions for the server |
487
+
488
+ ### Tasks (Background Execution)
489
+
490
+ The server declares full task capabilities (`tasks/list`, `tasks/cancel`). The following tools support task-based invocation with progress notifications:
491
+
492
+ `find`, `tree`, `read`, `read_many`, `stat_many`, `grep`, `mkdir`, `write`, `mv`, `rm`, `calculate_hash`, `apply_patch`, `search_and_replace`
493
+
494
+ Include `_meta.progressToken` in a `tools/call` request to receive `notifications/progress` updates. Use `tools/call` with a `task` field to invoke as a background task, then poll `tasks/get` and retrieve output via `tasks/result`.
495
+
496
+ ## Configuration
497
+
498
+ ### CLI
499
+
500
+ ```text
501
+ filesystem-mcp [options] [allowedDirs...]
255
502
  ```
256
503
 
257
- </details>
504
+ | Option | Description |
505
+ | :----------------- | :-------------------------------------------------------- |
506
+ | `[allowedDirs...]` | Positional: one or more directories the server may access |
507
+ | `--allow-cwd` | Allow the current working directory as an additional root |
508
+ | `-v, --version` | Display server version |
509
+ | `-h, --help` | Display help |
510
+
511
+ Examples:
512
+
513
+ ```bash
514
+ # Single directory
515
+ filesystem-mcp /project/src
516
+
517
+ # Multiple directories
518
+ filesystem-mcp /project/src /project/tests
519
+
520
+ # Current working directory
521
+ filesystem-mcp --allow-cwd
522
+
523
+ # Combined
524
+ filesystem-mcp /project/src --allow-cwd
525
+ ```
526
+
527
+ ### Allowed Directories
528
+
529
+ Directories are resolved from three sources, merged at runtime:
530
+
531
+ 1. **CLI arguments** — positional directory paths passed at startup.
532
+ 2. **MCP Roots protocol** — directories provided by the connected client after initialization (accepted only if they are within the CLI baseline when CLI directories are set).
533
+ 3. **`--allow-cwd`** — the current working directory is added automatically.
534
+
535
+ > [!TIP]
536
+ > If no directories are configured at startup and the connected client does not supply MCP Roots, all tool calls will fail. Pass at least one directory argument or use `--allow-cwd`.
258
537
 
259
538
  ## Security
260
539
 
261
- - **Path Restrictions**: All file operations are strictly validated against the allowed root directories provided at startup.
262
- - **Path Validation**: Uses `isPathWithinDirectories` to prevent path traversal attacks.
263
- - **Hidden Files**: Hidden files (starting with `.`) are excluded by default in listings and searches unless explicitly requested.
264
- - **Ignored Directories**: Ignored directories (for example `node_modules`, `.git`, `dist`) are excluded by default unless `includeIgnored=true`.
540
+ - **Path validation**: All operations use `isPathWithinDirectories` to prevent path traversal attacks.
541
+ - **Glob safety**: Glob patterns are validated to reject absolute paths and `..` traversal before execution.
542
+ - **Safe regex**: `re2` executes regex (no catastrophic backtracking); `safe-regex2` rejects unsafe patterns before use.
543
+ - **Hidden files**: Excluded from listings and searches by default; opt in with `includeHidden: true`.
544
+ - **Ignored directories**: `node_modules`, `.git`, `dist`, and similar directories are excluded by default; opt in with `includeIgnored: true`.
545
+ - **Windows safety**: Reserved device names (e.g. `CON`, `NUL`, `COM1`) and drive-relative paths (e.g. `C:path`) are rejected at the CLI.
546
+ - **Input limits**: Paths are bounded to 4,096 characters; patterns to 1,000 characters.
547
+ - **Atomic writes**: File writes use an atomic write-then-rename strategy to prevent partial writes.
548
+ - **Docker**: The container runs as a non-root user (`mcp`).
549
+
550
+ > [!IMPORTANT]
551
+ > All diagnostic output goes to `stderr`. Tool handlers must never write to `stdout`, as doing so would corrupt the stdio transport.
552
+
553
+ ## Development
554
+
555
+ ### Install
556
+
557
+ ```bash
558
+ npm ci
559
+ ```
560
+
561
+ ### Scripts
562
+
563
+ | Script | Command | Purpose |
564
+ | :-------------- | :-------------------------------------------------------- | :---------------------------------- |
565
+ | `dev` | `tsc --watch` | Watch-mode TypeScript compilation |
566
+ | `dev:run` | `node --watch dist/index.js` | Run built server with file watching |
567
+ | `build` | `node scripts/tasks.mjs build` | Production build |
568
+ | `test` | `node scripts/tasks.mjs test` | Run full test suite |
569
+ | `test:fast` | `node --test --import tsx/esm src/__tests__/**/*.test.ts` | Fast test runner (no build step) |
570
+ | `test:coverage` | `node scripts/tasks.mjs test --coverage` | Test with coverage |
571
+ | `lint` | `eslint .` | Lint source files |
572
+ | `lint:fix` | `eslint . --fix` | Auto-fix lint issues |
573
+ | `format` | `prettier --write .` | Format all files |
574
+ | `type-check` | `node scripts/tasks.mjs type-check` | TypeScript type checking |
575
+
576
+ ### MCP Inspector
577
+
578
+ ```bash
579
+ npm run inspector
580
+ ```
581
+
582
+ Or manually:
583
+
584
+ ```bash
585
+ npx @modelcontextprotocol/inspector node dist/index.js /path/to/dir
586
+ ```
265
587
 
266
- ## Testing Notes
588
+ ## Build & Release
267
589
 
268
- - For protocol-level validation, prefer an MCP SDK client (`listTools`, `listResources`, `listPrompts`, `callTool`, `readResource`) as source-of-truth.
269
- - Some third-party MCP CLIs may have URI parsing limitations when reading resources; if this happens, verify resource behavior through SDK client calls.
590
+ Releases are triggered manually via [GitHub Actions](.github/workflows/release.yml) (`workflow_dispatch`). The pipeline:
270
591
 
271
- ## Development Workflow
592
+ 1. Bumps `package.json` and `server.json` to the selected version (patch / minor / major or custom).
593
+ 2. Runs lint, type-check, tests, and build.
594
+ 3. Commits, tags (`vX.Y.Z`), and creates a GitHub Release with auto-generated notes.
595
+ 4. Publishes to **npm** (`@j0hanz/filesystem-mcp`) with OIDC provenance.
596
+ 5. Publishes to the **MCP Registry** (`io.github.j0hanz/filesystem-mcp`).
597
+ 6. Builds and pushes the **Docker image** (`ghcr.io/j0hanz/filesystem-mcp`) for `linux/amd64` and `linux/arm64`.
272
598
 
273
- 1. **Install Dependencies**:
599
+ The [Glama](https://glama.ai/mcp/servers/j0hanz/filesystem-mcp) listing requires a separate manual release step on the Glama dashboard.
274
600
 
275
- ```bash
276
- npm install
277
- ```
601
+ ### Docker Build (local)
602
+
603
+ ```bash
604
+ docker build -t filesystem-mcp .
605
+ ```
278
606
 
279
- 2. **Development Mode** (watch):
607
+ ## Troubleshooting
280
608
 
281
- ```bash
282
- npm run dev
283
- ```
609
+ **No directories configured**
610
+ If no directories are provided at startup and the client doesn't supply MCP Roots, all tool calls fail with `E_ACCESS_DENIED`. Use `roots` to inspect configured roots.
284
611
 
285
- 3. **Run Locally**:
612
+ **Path outside allowed directories**
613
+ Tools return `E_ACCESS_DENIED` when a path is outside all allowed roots. Use `roots` first to see what is available.
286
614
 
287
- ```bash
288
- npm start -- --allow-cwd
289
- ```
615
+ **Empty directory or no matches**
616
+ `find` and `grep` return empty results rather than errors when nothing matches. Verify the pattern and root path.
290
617
 
291
- 4. **Test**:
618
+ **Pattern rejected (`E_INVALID_PATTERN`)**
619
+ Glob patterns cannot be absolute or use `..` to traverse upward. RE2 patterns are validated before use.
292
620
 
293
- ```bash
294
- npm run test
295
- ```
621
+ **Non-empty directory delete fails**
622
+ `rm` returns `E_INVALID_INPUT` for non-empty directories without `recursive: true`. Either set `recursive: true` or remove contents first.
296
623
 
297
- 5. **Lint & Format**:
624
+ **Patch application failed**
625
+ `apply_patch` requires valid unified hunk headers (`@@ -N,M +N,M @@`). Regenerate the patch with `diff_files` against the current file content and retry.
298
626
 
299
- ```bash
300
- npm run lint
301
- npm run format
302
- ```
627
+ **Stdout contamination**
628
+ The server uses stdio transport. Never write to `stdout` in custom integrations. All diagnostic output goes to `stderr`. For Claude Desktop, check `~/Library/Logs/Claude/mcp*.log` (macOS) or the equivalent on Windows.
303
629
 
304
630
  ## License
305
631
 
306
- MIT
632
+ [MIT](LICENSE)