@j0hanz/filesystem-mcp 1.5.0 → 1.5.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.
@@ -11,8 +11,17 @@ export declare const DEFAULT_SEARCH_TIMEOUT_MS: number;
11
11
  */
12
12
  export declare const SEARCH_WORKERS: number;
13
13
  export declare const DEFAULT_MAX_DEPTH = 10;
14
- export declare const DEFAULT_LIST_MAX_ENTRIES = 10000;
14
+ export declare const DEFAULT_LIST_MAX_ENTRIES = 20000;
15
15
  export declare const DEFAULT_SEARCH_MAX_FILES = 20000;
16
+ export declare const MAX_TREE_DEPTH = 50;
17
+ export declare const DEFAULT_TREE_DEPTH = 5;
18
+ export declare const MAX_TREE_ENTRIES = 20000;
19
+ export declare const DEFAULT_TREE_ENTRIES = 1000;
20
+ export declare const MAX_LIST_ENTRIES = 20000;
21
+ export declare const MAX_SEARCH_RESULTS = 10000;
22
+ export declare const DEFAULT_SEARCH_RESULTS = 100;
23
+ export declare const MAX_SEARCH_DEPTH = 100;
24
+ export declare const DEFAULT_SEARCH_CONTENT_RESULTS = 500;
16
25
  export declare const MAX_LINE_CONTENT_LENGTH = 200;
17
26
  export declare const BINARY_CHECK_BUFFER_SIZE = 512;
18
27
  export declare const SENSITIVE_FILE_DENYLIST: string[];
@@ -109,8 +109,18 @@ const ENV_ALLOWLIST = parseEnvList('FS_CONTEXT_ALLOWLIST');
109
109
  export const SEARCH_WORKERS = parseEnvInt('FS_CONTEXT_SEARCH_WORKERS', getDefaultSearchWorkers(), 1, 16);
110
110
  // Hardcoded defaults
111
111
  export const DEFAULT_MAX_DEPTH = 10;
112
- export const DEFAULT_LIST_MAX_ENTRIES = 10000;
112
+ export const DEFAULT_LIST_MAX_ENTRIES = 20000;
113
113
  export const DEFAULT_SEARCH_MAX_FILES = 20000;
114
+ // Schema limits and defaults
115
+ export const MAX_TREE_DEPTH = 50;
116
+ export const DEFAULT_TREE_DEPTH = 5;
117
+ export const MAX_TREE_ENTRIES = 20000;
118
+ export const DEFAULT_TREE_ENTRIES = 1000;
119
+ export const MAX_LIST_ENTRIES = 20000;
120
+ export const MAX_SEARCH_RESULTS = 10000;
121
+ export const DEFAULT_SEARCH_RESULTS = 100;
122
+ export const MAX_SEARCH_DEPTH = 100;
123
+ export const DEFAULT_SEARCH_CONTENT_RESULTS = 500;
114
124
  // Non-configurable constants
115
125
  export const MAX_LINE_CONTENT_LENGTH = 200;
116
126
  export const BINARY_CHECK_BUFFER_SIZE = 512;
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_SEARCH_CONTENT_RESULTS, MAX_SEARCH_RESULTS, MAX_TEXT_FILE_SIZE, } from '../lib/constants.js';
1
2
  import { ALL_TOOLS } from '../tools.js';
2
3
  function toEntry(contract) {
3
4
  const annotations = [];
@@ -38,7 +39,7 @@ export function getSharedConstraints() {
38
39
  return [
39
40
  'Allowed roots only (negotiated via CLI).',
40
41
  'Sensitive files denylisted by default.',
41
- 'Max file size & search results enforced.',
42
+ `Max file size (${Math.floor(MAX_TEXT_FILE_SIZE / 1024 / 1024)}MB) & search results (${MAX_SEARCH_RESULTS} files, ${DEFAULT_SEARCH_CONTENT_RESULTS} lines) enforced.`,
42
43
  'Externalized results are ephemeral (in-memory).',
43
44
  ];
44
45
  }
package/dist/schemas.d.ts CHANGED
@@ -38,7 +38,7 @@ export declare const ListDirectoryInputSchema: z.ZodObject<{
38
38
  includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
39
39
  includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
40
40
  maxDepth: z.ZodOptional<z.ZodNumber>;
41
- maxEntries: z.ZodOptional<z.ZodNumber>;
41
+ maxEntries: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
42
42
  sortBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
43
43
  name: "name";
44
44
  size: "size";
package/dist/schemas.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { DEFAULT_LIST_MAX_ENTRIES, DEFAULT_SEARCH_CONTENT_RESULTS, DEFAULT_SEARCH_RESULTS, DEFAULT_TREE_DEPTH, DEFAULT_TREE_ENTRIES, MAX_LIST_ENTRIES, MAX_SEARCH_DEPTH, MAX_SEARCH_RESULTS, MAX_TREE_DEPTH, MAX_TREE_ENTRIES, } from './lib/constants.js';
2
3
  import { ErrorCode } from './lib/errors.js';
3
4
  function isSafeGlobPattern(value) {
4
5
  if (value.length === 0)
@@ -111,16 +112,17 @@ export const ListDirectoryInputSchema = z.strictObject({
111
112
  .number()
112
113
  .int({ error: 'Must be integer' })
113
114
  .min(1, 'Min: 1')
114
- .max(50, 'Max: 50')
115
+ .max(MAX_TREE_DEPTH, `Max: ${MAX_TREE_DEPTH}`)
115
116
  .optional()
116
117
  .describe('Max recursion depth when pattern is provided'),
117
118
  maxEntries: z
118
119
  .number()
119
120
  .int({ error: 'Must be integer' })
120
121
  .min(1, 'Min: 1')
121
- .max(20000, 'Max: 20,000')
122
+ .max(MAX_LIST_ENTRIES, `Max: ${MAX_LIST_ENTRIES}`)
122
123
  .optional()
123
- .describe('Maximum entries to return before truncation'),
124
+ .default(DEFAULT_LIST_MAX_ENTRIES)
125
+ .describe(`Maximum entries to return before truncation. Default: ${DEFAULT_LIST_MAX_ENTRIES}`),
124
126
  sortBy: ListDirectorySortSchema.optional()
125
127
  .default('name')
126
128
  .describe('Sort field (name, size, modified, type)'),
@@ -157,10 +159,10 @@ export const SearchFilesInputSchema = z.strictObject({
157
159
  .number()
158
160
  .int({ error: 'Must be integer' })
159
161
  .min(1, 'Min: 1')
160
- .max(10000, 'Max: 10,000')
162
+ .max(MAX_SEARCH_RESULTS, `Max: ${MAX_SEARCH_RESULTS}`)
161
163
  .optional()
162
- .default(100)
163
- .describe('Max results (1-10000)'),
164
+ .default(DEFAULT_SEARCH_RESULTS)
165
+ .describe(`Max results (1-${MAX_SEARCH_RESULTS}). Default: ${DEFAULT_SEARCH_RESULTS}`),
164
166
  includeIgnored: z
165
167
  .boolean()
166
168
  .optional()
@@ -178,7 +180,7 @@ export const SearchFilesInputSchema = z.strictObject({
178
180
  .number()
179
181
  .int({ error: 'Must be integer' })
180
182
  .min(0, 'Min: 0')
181
- .max(100, 'Max: 100')
183
+ .max(MAX_SEARCH_DEPTH, `Max: ${MAX_SEARCH_DEPTH}`)
182
184
  .optional()
183
185
  .describe('Maximum directory depth to scan'),
184
186
  cursor: z
@@ -192,18 +194,18 @@ export const TreeInputSchema = z.strictObject({
192
194
  .number()
193
195
  .int({ error: 'Must be integer' })
194
196
  .min(0, 'Min: 0')
195
- .max(50, 'Max: 50')
197
+ .max(MAX_TREE_DEPTH, `Max: ${MAX_TREE_DEPTH}`)
196
198
  .optional()
197
- .default(5)
198
- .describe('Depth (0=root node only, no children). Default: 5'),
199
+ .default(DEFAULT_TREE_DEPTH)
200
+ .describe(`Depth (0=root node only, no children). Default: ${DEFAULT_TREE_DEPTH}`),
199
201
  maxEntries: z
200
202
  .number()
201
203
  .int({ error: 'Must be integer' })
202
204
  .min(1, 'Min: 1')
203
- .max(20000, 'Max: 20,000')
205
+ .max(MAX_TREE_ENTRIES, `Max: ${MAX_TREE_ENTRIES}`)
204
206
  .optional()
205
- .default(1000)
206
- .describe('Max entries (Default: 1000)'),
207
+ .default(DEFAULT_TREE_ENTRIES)
208
+ .describe(`Max entries. Default: ${DEFAULT_TREE_ENTRIES}`),
207
209
  includeHidden: z
208
210
  .boolean()
209
211
  .optional()
@@ -249,10 +251,10 @@ export const SearchContentInputSchema = z.strictObject({
249
251
  .number()
250
252
  .int({ error: 'Must be integer' })
251
253
  .min(0, 'Min: 0')
252
- .max(10000, 'Max: 10,000')
254
+ .max(MAX_SEARCH_RESULTS, `Max: ${MAX_SEARCH_RESULTS}`)
253
255
  .optional()
254
- .default(500)
255
- .describe('Maximum match rows to return'),
256
+ .default(DEFAULT_SEARCH_CONTENT_RESULTS)
257
+ .describe(`Maximum match rows to return. Default: ${DEFAULT_SEARCH_CONTENT_RESULTS}`),
256
258
  filePattern: z
257
259
  .string()
258
260
  .min(1, 'Pattern required')
@@ -171,7 +171,7 @@ export function registerCalculateHashTool(server, options = {}) {
171
171
  baseReporter({
172
172
  current,
173
173
  ...(total !== undefined ? { total } : {}),
174
- message: `🕮 calculate_hash: ${baseName} ${current} ${fileWord} hashed`,
174
+ message: `🕮 calculate_hash: ${baseName} [${current} ${fileWord} hashed]`,
175
175
  });
176
176
  };
177
177
  try {
@@ -34,11 +34,11 @@ export function registerCreateDirectoryTool(server, options = {}) {
34
34
  const wrappedHandler = wrapToolHandler(handler, {
35
35
  guard: options.isInitialized,
36
36
  progressMessage: (args) => {
37
- const name = path.basename(args.path) || args.path;
37
+ const name = path.basename(args.path) || '.';
38
38
  return `🛠 mkdir: ${name}`;
39
39
  },
40
40
  completionMessage: (args, result) => {
41
- const name = path.basename(args.path) || args.path;
41
+ const name = path.basename(args.path) || '.';
42
42
  if (result.isError)
43
43
  return `🛠 mkdir: ${name} • failed`;
44
44
  return `🛠 mkdir: ${name} • created`;
@@ -96,7 +96,8 @@ export function registerEditFileTool(server, options = {}) {
96
96
  guard: options.isInitialized,
97
97
  progressMessage: (args) => {
98
98
  const name = path.basename(args.path);
99
- return `🛠 edit: ${name} [${args.edits.length} edits]`;
99
+ const dryTag = args.dryRun ? ' [dry run]' : '';
100
+ return `🛠 edit: ${name} [${args.edits.length} edits]${dryTag}`;
100
101
  },
101
102
  completionMessage: (args, result) => {
102
103
  const name = path.basename(args.path);
@@ -105,10 +106,16 @@ export function registerEditFileTool(server, options = {}) {
105
106
  const sc = result.structuredContent;
106
107
  if (!sc.ok)
107
108
  return `🛠 edit: ${name} • failed`;
109
+ const applied = sc.appliedEdits ?? 0;
110
+ const unmatched = sc.unmatchedEdits?.length ?? 0;
111
+ const dryPrefix = args.dryRun ? 'dry run — ' : '';
112
+ if (unmatched > 0) {
113
+ return `🛠 edit: ${name} • ${dryPrefix}${applied} applied, ${unmatched} unmatched`;
114
+ }
108
115
  if (sc.lineRange) {
109
- return `🛠 edit: ${name} • [${sc.lineRange[0]}-${sc.lineRange[1]}]`;
116
+ return `🛠 edit: ${name} • ${dryPrefix}lines ${sc.lineRange[0]}–${sc.lineRange[1]}`;
110
117
  }
111
- return `🛠 edit: ${name} • [${sc.appliedEdits ?? 0} edits]`;
118
+ return `🛠 edit: ${name} • ${dryPrefix}${applied} applied`;
112
119
  },
113
120
  });
114
121
  const validatedHandler = withValidatedArgs(EditFileInputSchema, wrappedHandler);
@@ -98,7 +98,7 @@ function decodeCursor(cursor) {
98
98
  async function handleListDirectory(args, signal) {
99
99
  const dirPath = resolvePathOrRoot(args.path);
100
100
  const cursorOffset = args.cursor !== undefined ? decodeCursor(args.cursor) : 0;
101
- const pageSize = args.maxEntries ?? 20_000;
101
+ const pageSize = args.maxEntries;
102
102
  const options = {
103
103
  includeHidden: args.includeHidden,
104
104
  excludePatterns: args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS,
@@ -265,7 +265,7 @@ export function registerSearchAndReplaceTool(server, options = {}) {
265
265
  baseReporter({
266
266
  current,
267
267
  ...(total !== undefined ? { total } : {}),
268
- message: `🛠 search_and_replace: "${args.searchPattern}" ${current} files processed`,
268
+ message: `🛠 search_and_replace: ${args.searchPattern} [${current} files processed]`,
269
269
  });
270
270
  };
271
271
  try {
@@ -220,7 +220,7 @@ export function registerSearchContentTool(server, options = {}) {
220
220
  baseReporter({
221
221
  current,
222
222
  ...(total !== undefined ? { total } : {}),
223
- message: `🔎︎ grep: ${pattern} ${current} ${fileWord} scanned`,
223
+ message: `🔎︎ grep: ${pattern} [${current} ${fileWord} scanned]`,
224
224
  });
225
225
  };
226
226
  try {
@@ -8,6 +8,7 @@ export { type ToolContract } from './contract.js';
8
8
  export declare const READ_ONLY_TOOL_ANNOTATIONS: {
9
9
  readonly readOnlyHint: true;
10
10
  readonly idempotentHint: true;
11
+ readonly destructiveHint: false;
11
12
  readonly openWorldHint: false;
12
13
  };
13
14
  export declare const DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS: {
@@ -18,6 +19,7 @@ export declare const DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS: {
18
19
  export declare const IDEMPOTENT_WRITE_TOOL_ANNOTATIONS: {
19
20
  readonly readOnlyHint: false;
20
21
  readonly idempotentHint: true;
22
+ readonly destructiveHint: false;
21
23
  readonly openWorldHint: false;
22
24
  };
23
25
  export declare function shouldStripStructuredOutput(): boolean;
@@ -18,6 +18,7 @@ function publishContextDiagnostics(event) {
18
18
  export const READ_ONLY_TOOL_ANNOTATIONS = {
19
19
  readOnlyHint: true,
20
20
  idempotentHint: true,
21
+ destructiveHint: false,
21
22
  openWorldHint: false,
22
23
  };
23
24
  export const DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS = {
@@ -28,6 +29,7 @@ export const DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS = {
28
29
  export const IDEMPOTENT_WRITE_TOOL_ANNOTATIONS = {
29
30
  readOnlyHint: false,
30
31
  idempotentHint: true,
32
+ destructiveHint: false,
31
33
  openWorldHint: false,
32
34
  };
33
35
  export function shouldStripStructuredOutput() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/filesystem-mcp",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "mcpName": "io.github.j0hanz/filesystem-mcp",
5
5
  "description": "MCP Server that enables LLMs to interact with the local filesystem.",
6
6
  "type": "module",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@eslint/js": "^10.0.1",
76
- "eslint": "^10.0.0",
76
+ "eslint": "^10.0.1",
77
77
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
78
78
  "@types/node": "^24",
79
79
  "eslint-config-prettier": "^10.1.8",
@@ -81,7 +81,7 @@
81
81
  "eslint-plugin-depend": "^1.4.0",
82
82
  "eslint-plugin-unused-imports": "^4.4.1",
83
83
  "jscpd": "^4.0.8",
84
- "knip": "^5.84.1",
84
+ "knip": "^5.85.0",
85
85
  "prettier": "^3.8.1",
86
86
  "tsx": "^4.21.0",
87
87
  "typescript": "^5.9.3",