@j0hanz/filesystem-mcp 1.13.1 → 1.14.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 (65) hide show
  1. package/README.md +117 -100
  2. package/dist/completions.d.ts +0 -11
  3. package/dist/completions.js +1 -1
  4. package/dist/config.d.ts +0 -1
  5. package/dist/lib/constants.d.ts +0 -1
  6. package/dist/lib/constants.js +0 -1
  7. package/dist/lib/errors.d.ts +0 -1
  8. package/dist/lib/errors.js +5 -9
  9. package/dist/lib/file-operations/core.d.ts +3 -2
  10. package/dist/lib/file-operations/metadata.js +9 -3
  11. package/dist/lib/file-operations/search.d.ts +1 -59
  12. package/dist/lib/file-operations/search.js +9 -22
  13. package/dist/lib/file-operations/traversal.d.ts +2 -2
  14. package/dist/lib/fs-helpers.d.ts +1 -2
  15. package/dist/lib/fs-helpers.js +10 -12
  16. package/dist/lib/globs.d.ts +2 -0
  17. package/dist/lib/globs.js +19 -0
  18. package/dist/lib/paths.d.ts +0 -3
  19. package/dist/lib/paths.js +1 -4
  20. package/dist/lib/utils.d.ts +4 -4
  21. package/dist/lib/utils.js +1 -10
  22. package/dist/lib/zod-codecs.d.ts +2 -0
  23. package/dist/lib/zod-codecs.js +18 -0
  24. package/dist/pkg-info.d.ts +1 -0
  25. package/dist/pkg-info.js +2 -2
  26. package/dist/prompts.js +3 -3
  27. package/dist/resources/generated-instructions.js +3 -12
  28. package/dist/resources/tool-catalog.js +10 -41
  29. package/dist/resources/tool-info.d.ts +0 -1
  30. package/dist/resources/tool-info.js +24 -39
  31. package/dist/resources/workflows.js +8 -1
  32. package/dist/schemas.d.ts +179 -481
  33. package/dist/schemas.js +156 -169
  34. package/dist/server/bootstrap.d.ts +0 -10
  35. package/dist/server/bootstrap.js +3 -3
  36. package/dist/server/roots-manager.js +1 -1
  37. package/dist/tools/apply-patch.js +19 -8
  38. package/dist/tools/calculate-hash.js +3 -5
  39. package/dist/tools/create-directory.d.ts +1 -4
  40. package/dist/tools/create-directory.js +2 -2
  41. package/dist/tools/delete-file.js +2 -4
  42. package/dist/tools/diff-files.js +1 -3
  43. package/dist/tools/edit-file.d.ts +1 -7
  44. package/dist/tools/edit-file.js +6 -3
  45. package/dist/tools/list-directory.js +10 -15
  46. package/dist/tools/move-file.d.ts +1 -4
  47. package/dist/tools/move-file.js +15 -27
  48. package/dist/tools/read-multiple.js +12 -7
  49. package/dist/tools/read.js +1 -2
  50. package/dist/tools/replace-in-files.d.ts +1 -10
  51. package/dist/tools/replace-in-files.js +62 -97
  52. package/dist/tools/roots.js +2 -6
  53. package/dist/tools/search-content.js +152 -188
  54. package/dist/tools/search-files.js +10 -13
  55. package/dist/tools/shared.d.ts +10 -3
  56. package/dist/tools/shared.js +53 -12
  57. package/dist/tools/stat-many.js +6 -4
  58. package/dist/tools/stat.js +2 -2
  59. package/dist/tools/task-support.d.ts +0 -7
  60. package/dist/tools/task-support.js +7 -6
  61. package/dist/tools/tree.js +1 -1
  62. package/dist/tools/write-file.js +1 -5
  63. package/dist/tools.d.ts +0 -1
  64. package/dist/tools.js +0 -1
  65. package/package.json +5 -5
package/dist/schemas.js CHANGED
@@ -1,26 +1,20 @@
1
1
  import { z } from 'zod';
2
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';
3
3
  import { ErrorCode } from './lib/errors.js';
4
- function isSafeGlobPattern(value) {
5
- if (value.length === 0)
6
- return false;
7
- if (value.includes('**/**/**'))
8
- return false;
9
- const absolutePattern = /^([/\\]|[A-Za-z]:[/\\]|\\\\)/u;
10
- if (absolutePattern.test(value)) {
11
- return false;
12
- }
13
- if (/[\\/]\.\.(?:[/\\]|$)/u.test(value) || value.startsWith('..')) {
14
- return false;
15
- }
16
- return true;
17
- }
4
+ import { isSafeGlobPattern } from './lib/globs.js';
18
5
  const MAX_PATH_LENGTH = 4096;
19
6
  const DESC_PATH_ROOT = 'Base directory (default: root). Absolute path required if multiple roots.';
20
7
  const DESC_PATH_REQUIRED = 'Absolute path to file or directory.';
21
8
  function defaultFalseBoolean(description) {
22
9
  return z.boolean().optional().default(false).describe(description);
23
10
  }
11
+ const SuccessFlagSchema = z.literal(true);
12
+ const NonNegativeIntegerSchema = z.int().min(0, 'Min: 0');
13
+ const PositiveIntegerSchema = z.int().min(1, 'Min: 1');
14
+ const IsoDateTimeSchema = z.iso.datetime();
15
+ const Sha256HexSchema = z
16
+ .string()
17
+ .regex(/^[a-f0-9]{64}$/u, 'Expected SHA-256 hex digest');
24
18
  const PathSchemaBase = z
25
19
  .string()
26
20
  .max(MAX_PATH_LENGTH, `Path too long (max ${MAX_PATH_LENGTH} chars)`);
@@ -48,10 +42,6 @@ const ErrorSchema = z.strictObject({
48
42
  path: z.string().optional().describe('Relevant path'),
49
43
  suggestion: z.string().optional().describe('Fix suggestion'),
50
44
  });
51
- export const ToolErrorResponseSchema = z.strictObject({
52
- ok: z.literal(false).describe('Operation failed'),
53
- error: ErrorSchema.describe('Error details'),
54
- });
55
45
  const HeadLinesSchema = z
56
46
  .int({ error: 'Must be integer' })
57
47
  .min(1, 'Min: 1')
@@ -65,11 +55,12 @@ const TailLinesSchema = z
65
55
  .optional()
66
56
  .describe('Read last N lines');
67
57
  const LineNumberSchema = z.int({ error: 'Must be integer' }).min(1, 'Min: 1');
68
- function addReadRangeIssue(ctx, path, message) {
58
+ function addReadRangeIssue(ctx, input, path, message) {
69
59
  ctx.addIssue({
70
60
  code: 'custom',
71
61
  path: [path],
72
62
  message,
63
+ input,
73
64
  });
74
65
  }
75
66
  const validateReadRange = (value, ctx) => {
@@ -78,18 +69,14 @@ const validateReadRange = (value, ctx) => {
78
69
  const hasStart = value.startLine !== undefined;
79
70
  const hasEnd = value.endLine !== undefined;
80
71
  if (hasHead && (hasStart || hasEnd)) {
81
- addReadRangeIssue(ctx, 'head', "Cannot use 'head' with 'startLine'/'endLine'");
72
+ addReadRangeIssue(ctx, value, 'head', "Cannot use 'head' with 'startLine'/'endLine'");
82
73
  }
83
74
  if (hasTail && (hasHead || hasStart || hasEnd)) {
84
- addReadRangeIssue(ctx, 'tail', "Cannot use 'tail' with 'head'/'startLine'/'endLine'");
75
+ addReadRangeIssue(ctx, value, 'tail', "Cannot use 'tail' with 'head'/'startLine'/'endLine'");
85
76
  }
86
- if (hasEnd && !hasStart) {
87
- addReadRangeIssue(ctx, 'endLine', "'endLine' requires 'startLine'");
88
- }
89
- if (value.startLine !== undefined &&
90
- value.endLine !== undefined &&
91
- value.endLine < value.startLine) {
92
- addReadRangeIssue(ctx, 'endLine', "'endLine' must be >= 'startLine'");
77
+ const effectiveStart = value.startLine ?? 1;
78
+ if (value.endLine !== undefined && value.endLine < effectiveStart) {
79
+ addReadRangeIssue(ctx, value, 'endLine', "'endLine' must be >= 'startLine'");
93
80
  }
94
81
  };
95
82
  function createReadRangeInputFields(descriptions) {
@@ -104,20 +91,20 @@ const FileInfoSchema = z.strictObject({
104
91
  name: z.string().describe('Name'),
105
92
  path: z.string().describe('Absolute path'),
106
93
  type: FileTypeSchema.describe('Type'),
107
- size: z.number().describe('Size (bytes)'),
108
- tokenEstimate: z.number().optional().describe('Est. tokens (size/4)'),
109
- created: z.string().describe('Created'),
110
- modified: z.string().describe('Modified'),
111
- accessed: z.string().describe('Accessed'),
94
+ size: NonNegativeIntegerSchema.describe('Size (bytes)'),
95
+ tokenEstimate: NonNegativeIntegerSchema.optional().describe('Est. tokens (size/4)'),
96
+ created: IsoDateTimeSchema.describe('Created'),
97
+ modified: IsoDateTimeSchema.describe('Modified'),
98
+ accessed: IsoDateTimeSchema.describe('Accessed'),
112
99
  permissions: z.string().describe('Permissions'),
113
100
  isHidden: z.boolean().describe('Hidden?'),
114
101
  mimeType: z.string().optional().describe('MIME type'),
115
102
  symlinkTarget: z.string().optional().describe('Target (symlink)'),
116
103
  });
117
104
  const OperationSummarySchema = z.strictObject({
118
- total: z.number().describe('Total'),
119
- succeeded: z.number().describe('Succeeded'),
120
- failed: z.number().describe('Failed'),
105
+ total: NonNegativeIntegerSchema.describe('Total'),
106
+ succeeded: NonNegativeIntegerSchema.describe('Succeeded'),
107
+ failed: NonNegativeIntegerSchema.describe('Failed'),
121
108
  });
122
109
  export const ListDirectoryInputSchema = z.strictObject({
123
110
  path: OptionalPathSchema.describe(DESC_PATH_ROOT),
@@ -143,6 +130,9 @@ export const ListDirectoryInputSchema = z.strictObject({
143
130
  .string()
144
131
  .min(1, 'Pattern required')
145
132
  .max(1000, 'Max 1000 chars')
133
+ .refine((val) => isSafeGlobPattern(val), {
134
+ error: 'Invalid glob or unsafe path (absolute/.. forbidden)',
135
+ })
146
136
  .optional()
147
137
  .describe('Optional glob pattern filter (e.g. "**/*.ts")'),
148
138
  includeSymlinkTargets: defaultFalseBoolean('Resolve and include symlink targets in results'),
@@ -240,7 +230,6 @@ export const SearchContentInputSchema = z.strictObject({
240
230
  .describe('Glob for candidate files (e.g. "**/*.ts")'),
241
231
  includeHidden: defaultFalseBoolean('Include hidden items (starting with .)'),
242
232
  includeIgnored: defaultFalseBoolean('Include ignored items (node_modules, etc).'),
243
- multiline: defaultFalseBoolean('Multi-line mode. ^ and $ match line boundaries when isRegex=true.'),
244
233
  });
245
234
  export const ReadFileInputSchema = z
246
235
  .strictObject({
@@ -248,13 +237,13 @@ export const ReadFileInputSchema = z
248
237
  ...createReadRangeInputFields({
249
238
  head: 'Read first N lines (preview)',
250
239
  tail: 'Read last N lines',
251
- startLine: 'Start line (1-based, inclusive)',
252
- endLine: 'End line (1-based, inclusive). Requires startLine.',
240
+ startLine: 'Start line (1-based, inclusive). Defaults to 1 when endLine is set.',
241
+ endLine: 'End line (1-based, inclusive). Defaults to last line when startLine is set.',
253
242
  }),
254
243
  includeHash: defaultFalseBoolean('Include SHA-256 hash of full file content'),
255
244
  })
256
245
  .superRefine(validateReadRange)
257
- .describe("Use one read mode only: 'head', 'tail', or 'startLine'/'endLine'. 'endLine' requires 'startLine'.");
246
+ .describe("Use one read mode only: 'head', 'tail', or 'startLine'/'endLine'.");
258
247
  export const ReadMultipleFilesInputSchema = z
259
248
  .strictObject({
260
249
  paths: z
@@ -265,12 +254,12 @@ export const ReadMultipleFilesInputSchema = z
265
254
  ...createReadRangeInputFields({
266
255
  head: 'Read first N lines of each file',
267
256
  tail: 'Read last N lines of each file',
268
- startLine: 'Start line (1-based, inclusive) per file',
269
- endLine: 'End line (1-based, inclusive) per file. Requires startLine.',
257
+ startLine: 'Start line (1-based, inclusive) per file. Defaults to 1 when endLine is set.',
258
+ endLine: 'End line (1-based, inclusive) per file. Defaults to last line when startLine is set.',
270
259
  }),
271
260
  })
272
261
  .superRefine(validateReadRange)
273
- .describe("Use one read mode only: 'head', 'tail', or 'startLine'/'endLine'. 'endLine' requires 'startLine'.");
262
+ .describe("Use one read mode only: 'head', 'tail', or 'startLine'/'endLine'.");
274
263
  export const GetFileInfoInputSchema = z.strictObject({
275
264
  path: RequiredPathSchema.describe(DESC_PATH_REQUIRED),
276
265
  });
@@ -282,62 +271,49 @@ export const GetMultipleFileInfoInputSchema = z.strictObject({
282
271
  .describe('File/directory paths. e.g. ["src", "lib"]'),
283
272
  });
284
273
  export const ListAllowedDirectoriesOutputSchema = z.strictObject({
285
- ok: z.boolean(),
274
+ ok: SuccessFlagSchema,
286
275
  directories: z.array(z.string()).optional().describe('Allowed directories'),
287
- rootsCount: z.number().optional().describe('Number of roots'),
288
- hasMultipleRoots: z
289
- .boolean()
290
- .optional()
291
- .describe('Multiple roots configured'),
292
- error: ErrorSchema.optional(),
293
276
  });
294
277
  export const ListDirectoryOutputSchema = z.strictObject({
295
- ok: z.boolean(),
278
+ ok: SuccessFlagSchema,
296
279
  path: z.string().optional(),
297
280
  entries: z
298
281
  .array(z.strictObject({
299
282
  name: z.string().describe('Entry name'),
300
283
  relativePath: z.string().optional(),
301
284
  type: FileTypeSchema,
302
- size: z.number().optional(),
303
- modified: z.string().optional(),
285
+ size: NonNegativeIntegerSchema.optional(),
286
+ modified: IsoDateTimeSchema.optional(),
304
287
  }))
305
288
  .optional(),
306
- totalEntries: z.number().optional(),
289
+ totalEntries: NonNegativeIntegerSchema.optional(),
307
290
  truncated: z.boolean().optional(),
308
- entriesScanned: z.number().optional(),
309
- entriesVisible: z.number().optional(),
310
- totalFiles: z.number().optional(),
311
- totalDirectories: z.number().optional(),
312
- maxDepthReached: z.number().optional(),
291
+ totalFiles: NonNegativeIntegerSchema.optional(),
292
+ totalDirectories: NonNegativeIntegerSchema.optional(),
313
293
  stoppedReason: ListDirectoryStopReasonSchema.optional(),
314
- skippedInaccessible: z.number().optional(),
315
- symlinksNotFollowed: z.number().optional(),
294
+ skippedInaccessible: NonNegativeIntegerSchema.optional(),
316
295
  nextCursor: z
317
296
  .string()
318
297
  .optional()
319
298
  .describe('Cursor for the next page; absent on the final page'),
320
- error: ErrorSchema.optional(),
321
299
  });
322
300
  const SearchSummarySchema = z.strictObject({
323
- totalMatches: z.number().optional().describe('Total matches found'),
301
+ totalMatches: NonNegativeIntegerSchema.optional().describe('Total matches found'),
324
302
  truncated: z.boolean().optional().describe('Results truncated?'),
325
303
  resourceUri: z.string().optional().describe('Full results URI'),
326
- error: ErrorSchema.optional(),
327
304
  });
328
305
  export const SearchFilesOutputSchema = SearchSummarySchema.extend({
329
- ok: z.boolean(),
306
+ ok: SuccessFlagSchema,
330
307
  root: z.string().optional().describe('Search root'),
331
- pattern: z.string().optional().describe('Glob pattern used'),
332
308
  results: z
333
309
  .array(z.strictObject({
334
310
  path: z.string().describe('Relative path'),
335
- size: z.number().optional(),
336
- modified: z.string().optional(),
311
+ size: NonNegativeIntegerSchema.optional(),
312
+ modified: IsoDateTimeSchema.optional(),
337
313
  }))
338
314
  .optional(),
339
- filesScanned: z.number().optional().describe('Files scanned'),
340
- skippedInaccessible: z.number().optional().describe('Inaccessible files'),
315
+ filesScanned: NonNegativeIntegerSchema.optional().describe('Files scanned'),
316
+ skippedInaccessible: NonNegativeIntegerSchema.optional().describe('Inaccessible files'),
341
317
  stoppedReason: SearchStopReasonSchema.optional().describe('Why search stopped'),
342
318
  nextCursor: z
343
319
  .string()
@@ -345,70 +321,49 @@ export const SearchFilesOutputSchema = SearchSummarySchema.extend({
345
321
  .describe('Cursor for the next page; absent on the final page'),
346
322
  });
347
323
  export const SearchContentOutputSchema = SearchSummarySchema.extend({
348
- ok: z.boolean(),
349
- patternType: z
350
- .enum(['literal', 'regex'])
351
- .optional()
352
- .describe('Pattern interpretation'),
353
- caseSensitive: z.boolean().optional().describe('Case-sensitive matching'),
324
+ ok: SuccessFlagSchema,
354
325
  matches: z
355
326
  .array(z.strictObject({
356
327
  file: z.string().describe('Relative path'),
357
- line: z.number(),
358
- column: z
359
- .number()
360
- .optional()
361
- .describe('Column of first match (0-based)'),
328
+ line: PositiveIntegerSchema,
329
+ column: NonNegativeIntegerSchema.optional().describe('Column of first match (0-based)'),
362
330
  content: z.string(),
363
- matchCount: z.number(),
331
+ matchCount: PositiveIntegerSchema,
364
332
  contextBefore: z.array(z.string()).optional(),
365
333
  contextAfter: z.array(z.string()).optional(),
366
334
  }))
367
335
  .optional(),
368
- filesScanned: z.number().optional().describe('Files scanned'),
369
- filesMatched: z.number().optional().describe('Files with matches'),
370
- skippedTooLarge: z.number().optional().describe('Files skipped: too large'),
371
- skippedBinary: z.number().optional().describe('Files skipped: binary'),
372
- skippedInaccessible: z
373
- .number()
374
- .optional()
375
- .describe('Files skipped: inaccessible'),
376
- linesSkippedDueToRegexTimeout: z
377
- .number()
378
- .optional()
379
- .describe('Lines skipped due to regex timeout'),
336
+ filesScanned: NonNegativeIntegerSchema.optional().describe('Files scanned'),
337
+ filesMatched: NonNegativeIntegerSchema.optional().describe('Files with matches'),
338
+ skippedTooLarge: NonNegativeIntegerSchema.optional().describe('Files skipped: too large'),
339
+ skippedBinary: NonNegativeIntegerSchema.optional().describe('Files skipped: binary'),
340
+ skippedInaccessible: NonNegativeIntegerSchema.optional().describe('Files skipped: inaccessible'),
380
341
  stoppedReason: SearchStopReasonSchema.optional().describe('Why search stopped'),
381
342
  });
382
343
  export const TreeOutputSchema = z.strictObject({
383
- ok: z.boolean(),
344
+ ok: SuccessFlagSchema,
384
345
  root: z.string().optional(),
385
346
  tree: TreeEntrySchema.optional(),
386
347
  ascii: z.string().optional(),
387
348
  truncated: z.boolean().optional(),
388
- totalEntries: z.number().optional(),
389
- error: ErrorSchema.optional(),
349
+ totalEntries: NonNegativeIntegerSchema.optional(),
390
350
  });
391
351
  const ReadResultSchema = z.strictObject({
392
352
  content: z.string().optional().describe('Content'),
393
353
  truncated: z.boolean().optional().describe('Truncated?'),
394
354
  resourceUri: z.string().optional().describe('Full content URI'),
395
- totalLines: z.number().optional().describe('Total lines'),
396
- readMode: z
397
- .enum(['full', 'head', 'tail', 'range'])
398
- .optional()
399
- .describe('Mode'),
400
- head: z.number().optional().describe('Head lines'),
401
- tail: z.number().optional().describe('Tail lines'),
402
- startLine: z.number().optional().describe('Start line'),
403
- endLine: z.number().optional().describe('End line'),
404
- linesRead: z.number().optional().describe('Lines read'),
355
+ totalLines: NonNegativeIntegerSchema.optional().describe('Total lines'),
356
+ head: PositiveIntegerSchema.optional().describe('Head lines'),
357
+ tail: PositiveIntegerSchema.optional().describe('Tail lines'),
358
+ startLine: PositiveIntegerSchema.optional().describe('Start line'),
359
+ endLine: PositiveIntegerSchema.optional().describe('End line'),
360
+ linesRead: NonNegativeIntegerSchema.optional().describe('Lines read'),
405
361
  hasMoreLines: z.boolean().optional().describe('More lines?'),
406
362
  });
407
363
  export const ReadFileOutputSchema = ReadResultSchema.extend({
408
- ok: z.boolean(),
364
+ ok: SuccessFlagSchema,
409
365
  path: z.string().optional(),
410
- contentHash: z.string().optional().describe('SHA-256 of full file content'),
411
- error: ErrorSchema.optional(),
366
+ contentHash: Sha256HexSchema.optional().describe('SHA-256 of full file content'),
412
367
  });
413
368
  const ReadMultipleFileResultSchema = ReadResultSchema.extend({
414
369
  path: z.string().describe('File path'),
@@ -416,60 +371,77 @@ const ReadMultipleFileResultSchema = ReadResultSchema.extend({
416
371
  .enum(['head', 'tail', 'range', 'externalized'])
417
372
  .optional()
418
373
  .describe('Why content was truncated'),
419
- maxTotalSize: z.number().optional().describe('Max total size budget'),
420
- error: z.string().optional().describe('Error message'),
374
+ error: ErrorSchema.optional().describe('Structured error details'),
421
375
  });
422
376
  export const ReadMultipleFilesOutputSchema = z.strictObject({
423
- ok: z.boolean(),
377
+ ok: SuccessFlagSchema,
424
378
  results: z.array(ReadMultipleFileResultSchema).optional(),
425
379
  summary: OperationSummarySchema.optional(),
426
- error: ErrorSchema.optional(),
427
380
  });
428
381
  export const GetFileInfoOutputSchema = z.strictObject({
429
- ok: z.boolean(),
382
+ ok: SuccessFlagSchema,
430
383
  info: FileInfoSchema.optional(),
431
- error: ErrorSchema.optional(),
432
384
  });
433
385
  export const GetMultipleFileInfoOutputSchema = z.strictObject({
434
- ok: z.boolean(),
386
+ ok: SuccessFlagSchema,
435
387
  results: z
436
388
  .array(z.strictObject({
437
389
  path: z.string(),
438
390
  info: FileInfoSchema.optional(),
439
- error: z.string().optional(),
391
+ error: ErrorSchema.optional(),
440
392
  }))
441
393
  .optional(),
442
394
  summary: OperationSummarySchema.optional(),
443
- error: ErrorSchema.optional(),
444
395
  });
445
396
  export const CreateDirectoryInputSchema = z
446
397
  .strictObject({
447
398
  path: RequiredPathSchema.optional().describe(DESC_PATH_REQUIRED),
448
399
  paths: z
449
400
  .array(RequiredPathSchema)
401
+ .min(1, 'Min 1 path required')
450
402
  .optional()
451
403
  .describe('Absolute paths to directories to create'),
452
404
  })
453
- .refine((data) => data.path !== undefined || data.paths !== undefined, {
454
- error: "Either 'path' or 'paths' must be provided",
455
- path: ['path'],
405
+ .superRefine((data, ctx) => {
406
+ const hasPath = data.path !== undefined;
407
+ const hasPaths = data.paths !== undefined;
408
+ if (!hasPath && !hasPaths) {
409
+ ctx.addIssue({
410
+ code: 'custom',
411
+ path: ['path'],
412
+ message: "Either 'path' or 'paths' must be provided",
413
+ input: data,
414
+ });
415
+ }
416
+ if (hasPath && hasPaths) {
417
+ ctx.addIssue({
418
+ code: 'custom',
419
+ path: ['path'],
420
+ message: "Provide either 'path' or 'paths', not both",
421
+ input: data,
422
+ });
423
+ ctx.addIssue({
424
+ code: 'custom',
425
+ path: ['paths'],
426
+ message: "Provide either 'path' or 'paths', not both",
427
+ input: data,
428
+ });
429
+ }
456
430
  })
457
431
  .describe("Provide either 'path' or 'paths'.");
458
432
  export const CreateDirectoryOutputSchema = z.strictObject({
459
- ok: z.boolean(),
433
+ ok: SuccessFlagSchema,
460
434
  path: z.string().optional(),
461
435
  paths: z.array(z.string()).optional(),
462
- error: ErrorSchema.optional(),
463
436
  });
464
437
  export const WriteFileInputSchema = z.strictObject({
465
438
  path: RequiredPathSchema.describe(DESC_PATH_REQUIRED),
466
439
  content: z.string().describe('Content to write'),
467
440
  });
468
441
  export const WriteFileOutputSchema = z.strictObject({
469
- ok: z.boolean(),
442
+ ok: SuccessFlagSchema,
470
443
  path: z.string().optional(),
471
- bytesWritten: z.number().optional(),
472
- error: ErrorSchema.optional(),
444
+ bytesWritten: NonNegativeIntegerSchema.optional(),
473
445
  });
474
446
  export const EditFileInputSchema = z.strictObject({
475
447
  path: RequiredPathSchema.describe(DESC_PATH_REQUIRED),
@@ -490,11 +462,11 @@ export const EditFileInputSchema = z.strictObject({
490
462
  export const EditFileOutputSchema = z.strictObject({
491
463
  ok: z.boolean(),
492
464
  path: z.string().optional(),
493
- appliedEdits: z.number().optional(),
494
- linesAdded: z.number().optional().describe('Lines added'),
495
- linesRemoved: z.number().optional().describe('Lines removed'),
465
+ appliedEdits: NonNegativeIntegerSchema.optional(),
466
+ linesAdded: NonNegativeIntegerSchema.optional().describe('Lines added'),
467
+ linesRemoved: NonNegativeIntegerSchema.optional().describe('Lines removed'),
496
468
  lineRange: z
497
- .tuple([z.number(), z.number()])
469
+ .tuple([PositiveIntegerSchema, PositiveIntegerSchema])
498
470
  .optional()
499
471
  .describe('Line range modified [start, end] (1-based)'),
500
472
  unmatchedEdits: z
@@ -502,17 +474,42 @@ export const EditFileOutputSchema = z.strictObject({
502
474
  .optional()
503
475
  .describe('Edits that could not be applied'),
504
476
  diff: z.string().optional().describe('Unified diff of changes (dryRun)'),
505
- error: ErrorSchema.optional(),
506
477
  });
507
478
  export const MoveFileInputSchema = z
508
479
  .strictObject({
509
480
  source: RequiredPathSchema.optional().describe('Path to move (deprecated: use sources)'),
510
- sources: z.array(RequiredPathSchema).optional().describe('Paths to move'),
481
+ sources: z
482
+ .array(RequiredPathSchema)
483
+ .min(1, 'Min 1 source required')
484
+ .optional()
485
+ .describe('Paths to move'),
511
486
  destination: RequiredPathSchema.describe('New path'),
512
487
  })
513
- .refine((data) => (data.source ?? data.sources) !== undefined, {
514
- error: "Either 'source' or 'sources' must be provided",
515
- path: ['source'],
488
+ .superRefine((data, ctx) => {
489
+ const hasSource = data.source !== undefined;
490
+ const hasSources = data.sources !== undefined;
491
+ if (!hasSource && !hasSources) {
492
+ ctx.addIssue({
493
+ code: 'custom',
494
+ path: ['source'],
495
+ message: "Either 'source' or 'sources' must be provided",
496
+ input: data,
497
+ });
498
+ }
499
+ if (hasSource && hasSources) {
500
+ ctx.addIssue({
501
+ code: 'custom',
502
+ path: ['source'],
503
+ message: "Provide either 'source' or 'sources', not both",
504
+ input: data,
505
+ });
506
+ ctx.addIssue({
507
+ code: 'custom',
508
+ path: ['sources'],
509
+ message: "Provide either 'source' or 'sources', not both",
510
+ input: data,
511
+ });
512
+ }
516
513
  })
517
514
  .describe("Provide either 'source' or 'sources'.");
518
515
  export const MoveFileOutputSchema = z.strictObject({
@@ -523,11 +520,10 @@ export const MoveFileOutputSchema = z.strictObject({
523
520
  failed: z
524
521
  .array(z.strictObject({
525
522
  source: z.string().describe('Source path'),
526
- error: z.string().describe('Error message'),
523
+ error: ErrorSchema.describe('Structured error details'),
527
524
  }))
528
525
  .optional()
529
526
  .describe('List of files that failed to move'),
530
- error: ErrorSchema.optional(),
531
527
  });
532
528
  export const DeleteFileInputSchema = z.strictObject({
533
529
  path: RequiredPathSchema.describe(DESC_PATH_REQUIRED),
@@ -535,23 +531,18 @@ export const DeleteFileInputSchema = z.strictObject({
535
531
  ignoreIfNotExists: defaultFalseBoolean('No error if missing'),
536
532
  });
537
533
  export const DeleteFileOutputSchema = z.strictObject({
538
- ok: z.boolean(),
534
+ ok: SuccessFlagSchema,
539
535
  path: z.string().optional(),
540
- error: ErrorSchema.optional(),
541
536
  });
542
537
  export const CalculateHashInputSchema = z.strictObject({
543
538
  path: RequiredPathSchema.describe(DESC_PATH_REQUIRED),
544
539
  });
545
540
  export const CalculateHashOutputSchema = z.strictObject({
546
- ok: z.boolean(),
541
+ ok: SuccessFlagSchema,
547
542
  path: z.string().optional(),
548
- hash: z.string().optional().describe('SHA-256 hash'),
543
+ hash: Sha256HexSchema.optional().describe('SHA-256 hash'),
549
544
  isDirectory: z.boolean().optional().describe('True if path is a directory'),
550
- fileCount: z
551
- .number()
552
- .optional()
553
- .describe('Number of files hashed (directories only)'),
554
- error: ErrorSchema.optional(),
545
+ fileCount: NonNegativeIntegerSchema.optional().describe('Number of files hashed (directories only)'),
555
546
  });
556
547
  export const DiffFilesInputSchema = z.strictObject({
557
548
  original: RequiredPathSchema.describe('Path to original file'),
@@ -574,15 +565,14 @@ export const DiffFilesInputSchema = z.strictObject({
574
565
  .describe('Strip trailing carriage returns before diffing'),
575
566
  });
576
567
  export const DiffFilesOutputSchema = z.strictObject({
577
- ok: z.boolean(),
568
+ ok: SuccessFlagSchema,
578
569
  diff: z.string().optional().describe('Unified diff content'),
579
570
  isIdentical: z.boolean().optional().describe('True if files are identical'),
580
- linesAdded: z.number().optional().describe('Lines added'),
581
- linesRemoved: z.number().optional().describe('Lines removed'),
582
- hunksCount: z.number().optional().describe('Number of diff hunks'),
571
+ linesAdded: NonNegativeIntegerSchema.optional().describe('Lines added'),
572
+ linesRemoved: NonNegativeIntegerSchema.optional().describe('Lines removed'),
573
+ hunksCount: NonNegativeIntegerSchema.optional().describe('Number of diff hunks'),
583
574
  truncated: z.boolean().optional().describe('Diff content truncated?'),
584
575
  resourceUri: z.string().optional().describe('Full diff content URI'),
585
- error: ErrorSchema.optional(),
586
576
  });
587
577
  export const ApplyPatchInputSchema = z.strictObject({
588
578
  path: RequiredPathSchema.describe('Path to file to patch'),
@@ -614,21 +604,20 @@ export const ApplyPatchOutputSchema = z.strictObject({
614
604
  ok: z.boolean(),
615
605
  path: z.string().optional(),
616
606
  applied: z.boolean().optional(),
617
- hunksApplied: z.number().optional().describe('Hunks applied'),
618
- linesAdded: z.number().optional().describe('Lines added'),
619
- linesRemoved: z.number().optional().describe('Lines removed'),
607
+ hunksApplied: NonNegativeIntegerSchema.optional().describe('Hunks applied'),
608
+ linesAdded: NonNegativeIntegerSchema.optional().describe('Lines added'),
609
+ linesRemoved: NonNegativeIntegerSchema.optional().describe('Lines removed'),
620
610
  results: z
621
611
  .array(z.strictObject({
622
612
  path: z.string().describe('File path'),
623
613
  applied: z.boolean().describe('Patch applied successfully'),
624
- hunksApplied: z.number().optional().describe('Hunks applied'),
625
- linesAdded: z.number().optional().describe('Lines added'),
626
- linesRemoved: z.number().optional().describe('Lines removed'),
627
- error: z.string().optional().describe('Error message'),
614
+ hunksApplied: NonNegativeIntegerSchema.optional().describe('Hunks applied'),
615
+ linesAdded: NonNegativeIntegerSchema.optional().describe('Lines added'),
616
+ linesRemoved: NonNegativeIntegerSchema.optional().describe('Lines removed'),
617
+ error: ErrorSchema.optional().describe('Structured error details'),
628
618
  }))
629
619
  .optional()
630
620
  .describe('Per-file results for multi-file patches'),
631
- error: ErrorSchema.optional(),
632
621
  });
633
622
  export const SearchAndReplaceInputSchema = z.strictObject({
634
623
  path: OptionalPathSchema.describe(DESC_PATH_ROOT),
@@ -675,22 +664,22 @@ export const SearchAndReplaceInputSchema = z.strictObject({
675
664
  .describe('Max files to process before stopping'),
676
665
  });
677
666
  export const SearchAndReplaceOutputSchema = z.strictObject({
678
- ok: z.boolean(),
679
- matches: z.number().optional().describe('Total matches found'),
680
- filesChanged: z.number().optional().describe('Files modified'),
681
- processedFiles: z.number().optional().describe('Files processed'),
682
- failedFiles: z.number().optional().describe('Files skipped due to errors'),
667
+ ok: SuccessFlagSchema,
668
+ matches: NonNegativeIntegerSchema.optional().describe('Total matches found'),
669
+ filesChanged: NonNegativeIntegerSchema.optional().describe('Files modified'),
670
+ processedFiles: NonNegativeIntegerSchema.optional().describe('Files processed'),
671
+ failedFiles: NonNegativeIntegerSchema.optional().describe('Files skipped due to errors'),
683
672
  failures: z
684
673
  .array(z.strictObject({
685
674
  path: z.string().describe('File path'),
686
- error: z.string().describe('Error message'),
675
+ error: ErrorSchema.describe('Structured error details'),
687
676
  }))
688
677
  .optional()
689
678
  .describe('Sample of per-file errors'),
690
679
  changedFiles: z
691
680
  .array(z.strictObject({
692
681
  path: z.string().describe('File path'),
693
- matches: z.number().describe('Matches in file'),
682
+ matches: PositiveIntegerSchema.describe('Matches in file'),
694
683
  }))
695
684
  .optional()
696
685
  .describe('Sample of changed files'),
@@ -707,6 +696,4 @@ export const SearchAndReplaceOutputSchema = z.strictObject({
707
696
  .enum(['maxFiles'])
708
697
  .optional()
709
698
  .describe('Why processing stopped early'),
710
- dryRun: z.boolean().optional(),
711
- error: ErrorSchema.optional(),
712
699
  });
@@ -5,20 +5,10 @@ export interface ServerOptions {
5
5
  allowCwd?: boolean;
6
6
  cliAllowedDirs?: string[];
7
7
  }
8
- interface CapabilityOptions {
9
- enablePromptListChanged?: boolean;
10
- enableTaskToolRequests?: boolean;
11
- }
12
- type ServerCapabilities = NonNullable<ConstructorParameters<typeof McpServer>[1]>['capabilities'];
13
- type NonOptionalServerCapabilities = NonNullable<ServerCapabilities>;
14
- export declare function buildServerCapabilities(options?: CapabilityOptions): NonOptionalServerCapabilities;
15
- export declare function supportsTaskToolRequests(): boolean;
16
8
  export interface LoggingState {
17
9
  minimumLevel: LoggingLevel;
18
10
  }
19
- export declare function createLoggingState(minimumLevel?: LoggingLevel): LoggingState;
20
11
  export declare function logToMcp(server: McpServer | undefined, level: LoggingLevel, data: string, minLevel?: LoggingLevel): void;
21
12
  export declare function createServer(options?: ServerOptions): Promise<McpServer>;
22
13
  export declare function startServer(server: McpServer): Promise<void>;
23
14
  export declare function startHttpServer(port: number, options: ServerOptions): Promise<http.Server>;
24
- export {};
@@ -41,7 +41,7 @@ function detectTaskToolSupport() {
41
41
  }
42
42
  return cachedTaskToolSupport;
43
43
  }
44
- export function buildServerCapabilities(options = {}) {
44
+ function buildServerCapabilities(options = {}) {
45
45
  const capabilities = {
46
46
  logging: {},
47
47
  resources: {},
@@ -61,7 +61,7 @@ export function buildServerCapabilities(options = {}) {
61
61
  }
62
62
  return capabilities;
63
63
  }
64
- export function supportsTaskToolRequests() {
64
+ function supportsTaskToolRequests() {
65
65
  return detectTaskToolSupport();
66
66
  }
67
67
  const MCP_LOGGER_NAME = 'filesystem-mcp';
@@ -75,7 +75,7 @@ const LOG_LEVEL_ORDER = {
75
75
  alert: 6,
76
76
  emergency: 7,
77
77
  };
78
- export function createLoggingState(minimumLevel = 'debug') {
78
+ function createLoggingState(minimumLevel = 'debug') {
79
79
  return { minimumLevel };
80
80
  }
81
81
  function canSendMcpLogs(server) {