@j0hanz/filesystem-context-mcp 1.1.0 → 1.2.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 (285) hide show
  1. package/README.md +106 -29
  2. package/dist/__tests__/lib/errors.test.js +3 -23
  3. package/dist/__tests__/lib/errors.test.js.map +1 -1
  4. package/dist/__tests__/lib/file-operations.test.js +34 -0
  5. package/dist/__tests__/lib/file-operations.test.js.map +1 -1
  6. package/dist/__tests__/lib/path-validation.test.js +8 -0
  7. package/dist/__tests__/lib/path-validation.test.js.map +1 -1
  8. package/dist/__tests__/schemas/validators.test.js +101 -122
  9. package/dist/__tests__/schemas/validators.test.js.map +1 -1
  10. package/dist/__tests__/security/filesystem-boundary.test.js +1 -1
  11. package/dist/__tests__/security/filesystem-boundary.test.js.map +1 -1
  12. package/dist/config/types.d.ts +1 -85
  13. package/dist/config/types.d.ts.map +1 -1
  14. package/dist/config/types.js +0 -2
  15. package/dist/config/types.js.map +1 -1
  16. package/dist/index.js +3 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/instructions.md +1 -2
  19. package/dist/lib/constants.d.ts +3 -1
  20. package/dist/lib/constants.d.ts.map +1 -1
  21. package/dist/lib/constants.js +66 -258
  22. package/dist/lib/constants.js.map +1 -1
  23. package/dist/lib/errors.d.ts +25 -3
  24. package/dist/lib/errors.d.ts.map +1 -1
  25. package/dist/lib/errors.js +4 -94
  26. package/dist/lib/errors.js.map +1 -1
  27. package/dist/lib/file-operations/analyze-directory.d.ts +8 -0
  28. package/dist/lib/file-operations/analyze-directory.d.ts.map +1 -0
  29. package/dist/lib/file-operations/analyze-directory.js +117 -0
  30. package/dist/lib/file-operations/analyze-directory.js.map +1 -0
  31. package/dist/lib/file-operations/directory-items.d.ts +20 -0
  32. package/dist/lib/file-operations/directory-items.d.ts.map +1 -0
  33. package/dist/lib/file-operations/directory-items.js +85 -0
  34. package/dist/lib/file-operations/directory-items.js.map +1 -0
  35. package/dist/lib/file-operations/directory-iteration.d.ts +17 -0
  36. package/dist/lib/file-operations/directory-iteration.d.ts.map +1 -0
  37. package/dist/lib/file-operations/directory-iteration.js +55 -0
  38. package/dist/lib/file-operations/directory-iteration.js.map +1 -0
  39. package/dist/lib/file-operations/directory-tree.d.ts +9 -0
  40. package/dist/lib/file-operations/directory-tree.d.ts.map +1 -0
  41. package/dist/lib/file-operations/directory-tree.js +175 -0
  42. package/dist/lib/file-operations/directory-tree.js.map +1 -0
  43. package/dist/lib/file-operations/file-info.d.ts +3 -0
  44. package/dist/lib/file-operations/file-info.d.ts.map +1 -0
  45. package/dist/lib/file-operations/file-info.js +56 -0
  46. package/dist/lib/file-operations/file-info.js.map +1 -0
  47. package/dist/lib/file-operations/list-directory.d.ts +10 -0
  48. package/dist/lib/file-operations/list-directory.d.ts.map +1 -0
  49. package/dist/lib/file-operations/list-directory.js +189 -0
  50. package/dist/lib/file-operations/list-directory.js.map +1 -0
  51. package/dist/lib/file-operations/read-media-file.d.ts +5 -0
  52. package/dist/lib/file-operations/read-media-file.d.ts.map +1 -0
  53. package/dist/lib/file-operations/read-media-file.js +31 -0
  54. package/dist/lib/file-operations/read-media-file.js.map +1 -0
  55. package/dist/lib/file-operations/read-multiple-files.d.ts +16 -0
  56. package/dist/lib/file-operations/read-multiple-files.d.ts.map +1 -0
  57. package/dist/lib/file-operations/read-multiple-files.js +98 -0
  58. package/dist/lib/file-operations/read-multiple-files.js.map +1 -0
  59. package/dist/lib/file-operations/search-content.d.ts +16 -0
  60. package/dist/lib/file-operations/search-content.d.ts.map +1 -0
  61. package/dist/lib/file-operations/search-content.js +431 -0
  62. package/dist/lib/file-operations/search-content.js.map +1 -0
  63. package/dist/lib/file-operations/search-files.d.ts +9 -0
  64. package/dist/lib/file-operations/search-files.d.ts.map +1 -0
  65. package/dist/lib/file-operations/search-files.js +139 -0
  66. package/dist/lib/file-operations/search-files.js.map +1 -0
  67. package/dist/lib/file-operations/sorting.d.ts +12 -0
  68. package/dist/lib/file-operations/sorting.d.ts.map +1 -0
  69. package/dist/lib/file-operations/sorting.js +24 -0
  70. package/dist/lib/file-operations/sorting.js.map +1 -0
  71. package/dist/lib/file-operations.d.ts +9 -57
  72. package/dist/lib/file-operations.d.ts.map +1 -1
  73. package/dist/lib/file-operations.js +9 -773
  74. package/dist/lib/file-operations.js.map +1 -1
  75. package/dist/lib/fs-helpers/binary-detect.d.ts +3 -0
  76. package/dist/lib/fs-helpers/binary-detect.d.ts.map +1 -0
  77. package/dist/lib/fs-helpers/binary-detect.js +54 -0
  78. package/dist/lib/fs-helpers/binary-detect.js.map +1 -0
  79. package/dist/lib/fs-helpers/concurrency.d.ts +11 -0
  80. package/dist/lib/fs-helpers/concurrency.d.ts.map +1 -0
  81. package/dist/lib/fs-helpers/concurrency.js +95 -0
  82. package/dist/lib/fs-helpers/concurrency.js.map +1 -0
  83. package/dist/lib/fs-helpers/fs-utils.d.ts +5 -0
  84. package/dist/lib/fs-helpers/fs-utils.d.ts.map +1 -0
  85. package/dist/lib/fs-helpers/fs-utils.js +13 -0
  86. package/dist/lib/fs-helpers/fs-utils.js.map +1 -0
  87. package/dist/lib/fs-helpers/readers/head-file.d.ts +2 -0
  88. package/dist/lib/fs-helpers/readers/head-file.d.ts.map +1 -0
  89. package/dist/lib/fs-helpers/readers/head-file.js +73 -0
  90. package/dist/lib/fs-helpers/readers/head-file.js.map +1 -0
  91. package/dist/lib/fs-helpers/readers/line-range.d.ts +7 -0
  92. package/dist/lib/fs-helpers/readers/line-range.d.ts.map +1 -0
  93. package/dist/lib/fs-helpers/readers/line-range.js +46 -0
  94. package/dist/lib/fs-helpers/readers/line-range.js.map +1 -0
  95. package/dist/lib/fs-helpers/readers/read-file.d.ts +16 -0
  96. package/dist/lib/fs-helpers/readers/read-file.d.ts.map +1 -0
  97. package/dist/lib/fs-helpers/readers/read-file.js +87 -0
  98. package/dist/lib/fs-helpers/readers/read-file.js.map +1 -0
  99. package/dist/lib/fs-helpers/readers/tail-file.d.ts +2 -0
  100. package/dist/lib/fs-helpers/readers/tail-file.d.ts.map +1 -0
  101. package/dist/lib/fs-helpers/readers/tail-file.js +98 -0
  102. package/dist/lib/fs-helpers/readers/tail-file.js.map +1 -0
  103. package/dist/lib/fs-helpers/readers/utf8.d.ts +3 -0
  104. package/dist/lib/fs-helpers/readers/utf8.d.ts.map +1 -0
  105. package/dist/lib/fs-helpers/readers/utf8.js +22 -0
  106. package/dist/lib/fs-helpers/readers/utf8.js.map +1 -0
  107. package/dist/lib/fs-helpers/readers.d.ts +4 -0
  108. package/dist/lib/fs-helpers/readers.d.ts.map +1 -0
  109. package/dist/lib/fs-helpers/readers.js +4 -0
  110. package/dist/lib/fs-helpers/readers.js.map +1 -0
  111. package/dist/lib/fs-helpers.d.ts +4 -25
  112. package/dist/lib/fs-helpers.d.ts.map +1 -1
  113. package/dist/lib/fs-helpers.js +4 -327
  114. package/dist/lib/fs-helpers.js.map +1 -1
  115. package/dist/lib/path-validation/allowed-directories.d.ts +9 -0
  116. package/dist/lib/path-validation/allowed-directories.d.ts.map +1 -0
  117. package/dist/lib/path-validation/allowed-directories.js +94 -0
  118. package/dist/lib/path-validation/allowed-directories.js.map +1 -0
  119. package/dist/lib/path-validation/errors.d.ts +5 -0
  120. package/dist/lib/path-validation/errors.d.ts.map +1 -0
  121. package/dist/lib/path-validation/errors.js +33 -0
  122. package/dist/lib/path-validation/errors.js.map +1 -0
  123. package/dist/lib/path-validation/roots.d.ts +3 -0
  124. package/dist/lib/path-validation/roots.d.ts.map +1 -0
  125. package/dist/lib/path-validation/roots.js +49 -0
  126. package/dist/lib/path-validation/roots.js.map +1 -0
  127. package/dist/lib/path-validation/validators.d.ts +9 -0
  128. package/dist/lib/path-validation/validators.d.ts.map +1 -0
  129. package/dist/lib/path-validation/validators.js +70 -0
  130. package/dist/lib/path-validation/validators.js.map +1 -0
  131. package/dist/lib/path-validation.d.ts +3 -7
  132. package/dist/lib/path-validation.d.ts.map +1 -1
  133. package/dist/lib/path-validation.js +3 -141
  134. package/dist/lib/path-validation.js.map +1 -1
  135. package/dist/schemas/input-helpers.d.ts +8 -0
  136. package/dist/schemas/input-helpers.d.ts.map +1 -0
  137. package/dist/schemas/input-helpers.js +44 -0
  138. package/dist/schemas/input-helpers.js.map +1 -0
  139. package/dist/schemas/inputs.d.ts +8 -5
  140. package/dist/schemas/inputs.d.ts.map +1 -1
  141. package/dist/schemas/inputs.js +41 -64
  142. package/dist/schemas/inputs.js.map +1 -1
  143. package/dist/schemas/output-helpers.d.ts +24 -0
  144. package/dist/schemas/output-helpers.d.ts.map +1 -0
  145. package/dist/schemas/output-helpers.js +13 -0
  146. package/dist/schemas/output-helpers.js.map +1 -0
  147. package/dist/schemas/outputs.d.ts +476 -42
  148. package/dist/schemas/outputs.d.ts.map +1 -1
  149. package/dist/schemas/outputs.js +26 -41
  150. package/dist/schemas/outputs.js.map +1 -1
  151. package/dist/server.d.ts +9 -1
  152. package/dist/server.d.ts.map +1 -1
  153. package/dist/server.js +28 -42
  154. package/dist/server.js.map +1 -1
  155. package/dist/tools/analyze-directory.d.ts.map +1 -1
  156. package/dist/tools/analyze-directory.js +115 -53
  157. package/dist/tools/analyze-directory.js.map +1 -1
  158. package/dist/tools/directory-tree.d.ts.map +1 -1
  159. package/dist/tools/directory-tree.js +86 -49
  160. package/dist/tools/directory-tree.js.map +1 -1
  161. package/dist/tools/get-file-info.d.ts.map +1 -1
  162. package/dist/tools/get-file-info.js +71 -37
  163. package/dist/tools/get-file-info.js.map +1 -1
  164. package/dist/tools/list-allowed-dirs.d.ts.map +1 -1
  165. package/dist/tools/list-allowed-dirs.js +48 -35
  166. package/dist/tools/list-allowed-dirs.js.map +1 -1
  167. package/dist/tools/list-directory.d.ts.map +1 -1
  168. package/dist/tools/list-directory.js +129 -58
  169. package/dist/tools/list-directory.js.map +1 -1
  170. package/dist/tools/read-file.d.ts.map +1 -1
  171. package/dist/tools/read-file.js +70 -56
  172. package/dist/tools/read-file.js.map +1 -1
  173. package/dist/tools/read-media-file.d.ts.map +1 -1
  174. package/dist/tools/read-media-file.js +39 -41
  175. package/dist/tools/read-media-file.js.map +1 -1
  176. package/dist/tools/read-multiple-files.d.ts.map +1 -1
  177. package/dist/tools/read-multiple-files.js +58 -50
  178. package/dist/tools/read-multiple-files.js.map +1 -1
  179. package/dist/tools/search-content.d.ts.map +1 -1
  180. package/dist/tools/search-content.js +146 -89
  181. package/dist/tools/search-content.js.map +1 -1
  182. package/dist/tools/search-files.d.ts.map +1 -1
  183. package/dist/tools/search-files.js +121 -50
  184. package/dist/tools/search-files.js.map +1 -1
  185. package/dist/tools/tool-response.d.ts +9 -0
  186. package/dist/tools/tool-response.d.ts.map +1 -0
  187. package/dist/tools/tool-response.js +7 -0
  188. package/dist/tools/tool-response.js.map +1 -0
  189. package/package.json +2 -1
  190. package/dist/__tests__/errors.test.d.ts +0 -2
  191. package/dist/__tests__/errors.test.d.ts.map +0 -1
  192. package/dist/__tests__/errors.test.js +0 -88
  193. package/dist/__tests__/errors.test.js.map +0 -1
  194. package/dist/__tests__/file-operations.test.d.ts +0 -2
  195. package/dist/__tests__/file-operations.test.d.ts.map +0 -1
  196. package/dist/__tests__/file-operations.test.js +0 -230
  197. package/dist/__tests__/file-operations.test.js.map +0 -1
  198. package/dist/__tests__/lib/formatters.test.d.ts +0 -2
  199. package/dist/__tests__/lib/formatters.test.d.ts.map +0 -1
  200. package/dist/__tests__/lib/formatters.test.js +0 -248
  201. package/dist/__tests__/lib/formatters.test.js.map +0 -1
  202. package/dist/__tests__/lib/image-parsing.test.d.ts +0 -2
  203. package/dist/__tests__/lib/image-parsing.test.d.ts.map +0 -1
  204. package/dist/__tests__/lib/image-parsing.test.js +0 -262
  205. package/dist/__tests__/lib/image-parsing.test.js.map +0 -1
  206. package/dist/__tests__/path-validation.test.d.ts +0 -2
  207. package/dist/__tests__/path-validation.test.d.ts.map +0 -1
  208. package/dist/__tests__/path-validation.test.js +0 -92
  209. package/dist/__tests__/path-validation.test.js.map +0 -1
  210. package/dist/lib/directory-helpers.d.ts +0 -4
  211. package/dist/lib/directory-helpers.d.ts.map +0 -1
  212. package/dist/lib/directory-helpers.js +0 -36
  213. package/dist/lib/directory-helpers.js.map +0 -1
  214. package/dist/lib/formatters.d.ts +0 -10
  215. package/dist/lib/formatters.d.ts.map +0 -1
  216. package/dist/lib/formatters.js +0 -202
  217. package/dist/lib/formatters.js.map +0 -1
  218. package/dist/lib/image-parsing.d.ts +0 -4
  219. package/dist/lib/image-parsing.d.ts.map +0 -1
  220. package/dist/lib/image-parsing.js +0 -124
  221. package/dist/lib/image-parsing.js.map +0 -1
  222. package/dist/lib/mcp-logger.d.ts +0 -11
  223. package/dist/lib/mcp-logger.d.ts.map +0 -1
  224. package/dist/lib/mcp-logger.js +0 -49
  225. package/dist/lib/mcp-logger.js.map +0 -1
  226. package/dist/lib/roots-utils.d.ts +0 -7
  227. package/dist/lib/roots-utils.d.ts.map +0 -1
  228. package/dist/lib/roots-utils.js +0 -39
  229. package/dist/lib/roots-utils.js.map +0 -1
  230. package/dist/lib/search-helpers.d.ts +0 -16
  231. package/dist/lib/search-helpers.d.ts.map +0 -1
  232. package/dist/lib/search-helpers.js +0 -169
  233. package/dist/lib/search-helpers.js.map +0 -1
  234. package/dist/lib/sorting.d.ts +0 -12
  235. package/dist/lib/sorting.d.ts.map +0 -1
  236. package/dist/lib/sorting.js +0 -41
  237. package/dist/lib/sorting.js.map +0 -1
  238. package/dist/lib/types.d.ts +0 -6
  239. package/dist/lib/types.d.ts.map +0 -1
  240. package/dist/lib/types.js +0 -2
  241. package/dist/lib/types.js.map +0 -1
  242. package/dist/prompts/analyze-codebase.d.ts +0 -3
  243. package/dist/prompts/analyze-codebase.d.ts.map +0 -1
  244. package/dist/prompts/analyze-codebase.js +0 -144
  245. package/dist/prompts/analyze-codebase.js.map +0 -1
  246. package/dist/prompts/filesystem-query.d.ts +0 -3
  247. package/dist/prompts/filesystem-query.d.ts.map +0 -1
  248. package/dist/prompts/filesystem-query.js +0 -168
  249. package/dist/prompts/filesystem-query.js.map +0 -1
  250. package/dist/prompts/find-duplicates.d.ts +0 -3
  251. package/dist/prompts/find-duplicates.d.ts.map +0 -1
  252. package/dist/prompts/find-duplicates.js +0 -77
  253. package/dist/prompts/find-duplicates.js.map +0 -1
  254. package/dist/prompts/index.d.ts +0 -3
  255. package/dist/prompts/index.d.ts.map +0 -1
  256. package/dist/prompts/index.js +0 -13
  257. package/dist/prompts/index.js.map +0 -1
  258. package/dist/prompts/project-overview.d.ts +0 -3
  259. package/dist/prompts/project-overview.d.ts.map +0 -1
  260. package/dist/prompts/project-overview.js +0 -122
  261. package/dist/prompts/project-overview.js.map +0 -1
  262. package/dist/prompts/search-and-replace.d.ts +0 -3
  263. package/dist/prompts/search-and-replace.d.ts.map +0 -1
  264. package/dist/prompts/search-and-replace.js +0 -130
  265. package/dist/prompts/search-and-replace.js.map +0 -1
  266. package/dist/prompts/shared.d.ts +0 -11
  267. package/dist/prompts/shared.d.ts.map +0 -1
  268. package/dist/prompts/shared.js +0 -32
  269. package/dist/prompts/shared.js.map +0 -1
  270. package/dist/resources/index.d.ts +0 -3
  271. package/dist/resources/index.d.ts.map +0 -1
  272. package/dist/resources/index.js +0 -54
  273. package/dist/resources/index.js.map +0 -1
  274. package/dist/schemas/validators.d.ts +0 -12
  275. package/dist/schemas/validators.d.ts.map +0 -1
  276. package/dist/schemas/validators.js +0 -35
  277. package/dist/schemas/validators.js.map +0 -1
  278. package/dist/utils/index.d.ts +0 -2
  279. package/dist/utils/index.d.ts.map +0 -1
  280. package/dist/utils/index.js +0 -2
  281. package/dist/utils/index.js.map +0 -1
  282. package/dist/utils/response-helpers.d.ts +0 -22
  283. package/dist/utils/response-helpers.d.ts.map +0 -1
  284. package/dist/utils/response-helpers.js +0 -24
  285. package/dist/utils/response-helpers.js.map +0 -1
@@ -1,774 +1,10 @@
1
- import * as fs from 'node:fs/promises';
2
- import * as path from 'node:path';
3
- import fg from 'fast-glob';
4
- import { Minimatch } from 'minimatch';
5
- import safeRegex from 'safe-regex2';
6
- import { DEFAULT_MAX_DEPTH, DEFAULT_MAX_RESULTS, DEFAULT_TOP_N, DIR_TRAVERSAL_CONCURRENCY, getMimeType, MAX_MEDIA_FILE_SIZE, MAX_SEARCHABLE_FILE_SIZE, MAX_TEXT_FILE_SIZE, PARALLEL_CONCURRENCY, } from './constants.js';
7
- import { ErrorCode, McpError } from './errors.js';
8
- import { getFileType, isHidden, isProbablyBinary, processInParallel, readFile, runWorkQueue, } from './fs-helpers.js';
9
- import { parseImageDimensions } from './image-parsing.js';
10
- import { validateExistingPath, validateExistingPathDetailed, } from './path-validation.js';
11
- import { isSimpleSafePattern, prepareSearchPattern, scanFileForContent, } from './search-helpers.js';
12
- function createExcludeMatcher(excludePatterns) {
13
- if (excludePatterns.length === 0) {
14
- return () => false;
15
- }
16
- const matchers = excludePatterns.map((pattern) => new Minimatch(pattern));
17
- return (name, relativePath) => matchers.some((m) => m.match(name) || m.match(relativePath));
18
- }
19
- function classifyAccessError(error) {
20
- if (error instanceof McpError &&
21
- (error.code === ErrorCode.E_ACCESS_DENIED ||
22
- error.code === ErrorCode.E_SYMLINK_NOT_ALLOWED)) {
23
- return 'symlink';
24
- }
25
- return 'inaccessible';
26
- }
27
- function insertSorted(arr, item, compare, maxLen) {
28
- if (maxLen <= 0)
29
- return;
30
- const idx = arr.findIndex((el) => compare(item, el) < 0);
31
- if (idx === -1) {
32
- if (arr.length < maxLen)
33
- arr.push(item);
34
- }
35
- else {
36
- arr.splice(idx, 0, item);
37
- if (arr.length > maxLen)
38
- arr.pop();
39
- }
40
- }
41
- const SORT_COMPARATORS = {
42
- size: (a, b) => (b.size ?? 0) - (a.size ?? 0),
43
- modified: (a, b) => (b.modified?.getTime() ?? 0) - (a.modified?.getTime() ?? 0),
44
- type: (a, b) => {
45
- if (a.type !== b.type)
46
- return a.type === 'directory' ? -1 : 1;
47
- return (a.name ?? '').localeCompare(b.name ?? '');
48
- },
49
- path: (a, b) => (a.path ?? '').localeCompare(b.path ?? ''),
50
- name: (a, b) => (a.name ?? '').localeCompare(b.name ?? ''),
51
- };
52
- function sortByField(items, sortBy) {
53
- const comparator = SORT_COMPARATORS[sortBy];
54
- items.sort(comparator);
55
- }
56
- function sortSearchResults(results, sortBy) {
57
- if (sortBy === 'name') {
58
- results.sort((a, b) => path.basename(a.path ?? '').localeCompare(path.basename(b.path ?? '')));
59
- }
60
- else {
61
- sortByField(results, sortBy);
62
- }
63
- }
64
- function getPermissions(mode) {
65
- const PERM_STRINGS = [
66
- '---',
67
- '--x',
68
- '-w-',
69
- '-wx',
70
- 'r--',
71
- 'r-x',
72
- 'rw-',
73
- 'rwx',
74
- ];
75
- const ownerIndex = (mode >> 6) & 0b111;
76
- const groupIndex = (mode >> 3) & 0b111;
77
- const otherIndex = mode & 0b111;
78
- const owner = PERM_STRINGS[ownerIndex] ?? '---';
79
- const group = PERM_STRINGS[groupIndex] ?? '---';
80
- const other = PERM_STRINGS[otherIndex] ?? '---';
81
- return `${owner}${group}${other}`;
82
- }
83
- export async function getFileInfo(filePath) {
84
- const { requestedPath, resolvedPath, isSymlink } = await validateExistingPathDetailed(filePath);
85
- const name = path.basename(requestedPath);
86
- const ext = path.extname(name).toLowerCase();
87
- const mimeType = ext ? getMimeType(ext) : undefined;
88
- let symlinkTarget;
89
- if (isSymlink) {
90
- try {
91
- symlinkTarget = await fs.readlink(requestedPath);
92
- }
93
- catch {
94
- // Symlink target unreadable
95
- }
96
- }
97
- const stats = await fs.stat(resolvedPath);
98
- return {
99
- name,
100
- path: requestedPath,
101
- type: isSymlink ? 'symlink' : getFileType(stats),
102
- size: stats.size,
103
- created: stats.birthtime,
104
- modified: stats.mtime,
105
- accessed: stats.atime,
106
- permissions: getPermissions(stats.mode),
107
- isHidden: isHidden(name),
108
- mimeType,
109
- symlinkTarget,
110
- };
111
- }
112
- export async function listDirectory(dirPath, options = {}) {
113
- const { recursive = false, includeHidden = false, maxDepth = DEFAULT_MAX_DEPTH, maxEntries, sortBy = 'name', includeSymlinkTargets = false, } = options;
114
- const validPath = await validateExistingPath(dirPath);
115
- const entries = [];
116
- let totalFiles = 0;
117
- let totalDirectories = 0;
118
- let maxDepthReached = 0;
119
- let truncated = false;
120
- let skippedInaccessible = 0;
121
- let symlinksNotFollowed = 0;
122
- const stopIfNeeded = () => {
123
- if (maxEntries !== undefined && entries.length >= maxEntries) {
124
- truncated = true;
125
- return true;
126
- }
127
- return false;
128
- };
129
- await runWorkQueue([{ currentPath: validPath, depth: 0 }], async ({ currentPath, depth }, enqueue) => {
130
- if (depth > maxDepth)
131
- return;
132
- if (stopIfNeeded())
133
- return;
134
- maxDepthReached = Math.max(maxDepthReached, depth);
135
- let items;
136
- try {
137
- items = await fs.readdir(currentPath, { withFileTypes: true });
138
- }
139
- catch {
140
- skippedInaccessible++;
141
- return;
142
- }
143
- const visibleItems = includeHidden
144
- ? items
145
- : items.filter((item) => !isHidden(item.name));
146
- const { results: processedEntries, errors: processingErrors } = await processInParallel(visibleItems, async (item) => {
147
- const fullPath = path.join(currentPath, item.name);
148
- const relativePath = path.relative(validPath, fullPath) || item.name;
149
- try {
150
- if (item.isSymbolicLink()) {
151
- symlinksNotFollowed++;
152
- const stats = await fs.lstat(fullPath);
153
- let symlinkTarget;
154
- if (includeSymlinkTargets) {
155
- try {
156
- symlinkTarget = await fs.readlink(fullPath);
157
- }
158
- catch {
159
- // Symlink target unreadable
160
- }
161
- }
162
- const entry = {
163
- name: item.name,
164
- path: fullPath,
165
- relativePath,
166
- type: 'symlink',
167
- size: stats.size,
168
- modified: stats.mtime,
169
- symlinkTarget,
170
- };
171
- return { entry };
172
- }
173
- const stats = await fs.stat(fullPath);
174
- const isDir = item.isDirectory();
175
- const type = isDir
176
- ? 'directory'
177
- : item.isFile()
178
- ? 'file'
179
- : getFileType(stats);
180
- const entry = {
181
- name: item.name,
182
- path: fullPath,
183
- relativePath,
184
- type,
185
- size: type === 'file' ? stats.size : undefined,
186
- modified: stats.mtime,
187
- };
188
- const enqueueDir = recursive && isDir && depth + 1 <= maxDepth
189
- ? {
190
- currentPath: await validateExistingPath(fullPath),
191
- depth: depth + 1,
192
- }
193
- : undefined;
194
- return { entry, enqueueDir };
195
- }
196
- catch {
197
- skippedInaccessible++;
198
- const entry = {
199
- name: item.name,
200
- path: fullPath,
201
- relativePath,
202
- type: item.isDirectory()
203
- ? 'directory'
204
- : item.isFile()
205
- ? 'file'
206
- : 'other',
207
- };
208
- return { entry };
209
- }
210
- });
211
- skippedInaccessible += processingErrors.length;
212
- for (const { entry, enqueueDir } of processedEntries) {
213
- if (stopIfNeeded())
214
- break;
215
- entries.push(entry);
216
- if (entry.type === 'directory')
217
- totalDirectories++;
218
- if (entry.type === 'file')
219
- totalFiles++;
220
- if (enqueueDir)
221
- enqueue(enqueueDir);
222
- }
223
- }, DIR_TRAVERSAL_CONCURRENCY);
224
- sortByField(entries, sortBy);
225
- return {
226
- path: validPath,
227
- entries,
228
- summary: {
229
- totalEntries: entries.length,
230
- totalFiles,
231
- totalDirectories,
232
- maxDepthReached,
233
- truncated,
234
- skippedInaccessible,
235
- symlinksNotFollowed,
236
- },
237
- };
238
- }
239
- export async function searchFiles(basePath, pattern, excludePatterns = [], options = {}) {
240
- const validPath = await validateExistingPath(basePath);
241
- const { maxResults, sortBy = 'path', maxDepth } = options;
242
- const results = [];
243
- let skippedInaccessible = 0;
244
- let truncated = false;
245
- let filesScanned = 0;
246
- const batch = [];
247
- const flushBatch = async () => {
248
- if (batch.length === 0)
249
- return;
250
- const toProcess = batch.splice(0, batch.length);
251
- const settled = await Promise.allSettled(toProcess.map(async (match) => {
252
- const stats = await fs.stat(match);
253
- const { size, mtime: modified } = stats;
254
- return {
255
- path: match,
256
- type: getFileType(stats),
257
- size: stats.isFile() ? size : undefined,
258
- modified,
259
- };
260
- }));
261
- for (const r of settled) {
262
- if (r.status === 'fulfilled') {
263
- if (maxResults !== undefined && results.length >= maxResults) {
264
- truncated = true;
265
- break;
266
- }
267
- results.push(r.value);
268
- }
269
- else {
270
- skippedInaccessible++;
271
- }
272
- }
273
- };
274
- const stream = fg.stream(pattern, {
275
- cwd: validPath,
276
- absolute: true,
277
- onlyFiles: false,
278
- dot: true,
279
- ignore: excludePatterns,
280
- suppressErrors: true,
281
- followSymbolicLinks: false,
282
- deep: maxDepth,
283
- });
284
- for await (const entry of stream) {
285
- const matchPath = typeof entry === 'string' ? entry : String(entry);
286
- filesScanned++;
287
- if (maxResults !== undefined && results.length >= maxResults) {
288
- truncated = true;
289
- break;
290
- }
291
- batch.push(matchPath);
292
- if (batch.length >= PARALLEL_CONCURRENCY) {
293
- await flushBatch();
294
- if (maxResults !== undefined && results.length >= maxResults) {
295
- truncated = true;
296
- break;
297
- }
298
- }
299
- }
300
- await flushBatch();
301
- sortSearchResults(results, sortBy);
302
- return {
303
- basePath: validPath,
304
- pattern,
305
- results,
306
- summary: {
307
- matched: results.length,
308
- truncated,
309
- skippedInaccessible,
310
- filesScanned,
311
- },
312
- };
313
- }
314
- export { readFile };
315
- export async function readMultipleFiles(filePaths, options = {}) {
316
- const { encoding = 'utf-8', maxSize = MAX_TEXT_FILE_SIZE, maxTotalSize = 100 * 1024 * 1024, head, tail, } = options;
317
- if (filePaths.length === 0)
318
- return [];
319
- const output = filePaths.map((filePath) => ({ path: filePath }));
320
- let totalSize = 0;
321
- const fileSizes = new Map();
322
- for (const filePath of filePaths) {
323
- try {
324
- const validPath = await validateExistingPath(filePath);
325
- const stats = await fs.stat(validPath);
326
- fileSizes.set(filePath, stats.size);
327
- totalSize += stats.size;
328
- }
329
- catch {
330
- fileSizes.set(filePath, 0);
331
- }
332
- }
333
- if (totalSize > maxTotalSize) {
334
- throw new McpError(ErrorCode.E_TOO_LARGE, `Total size of all files (${totalSize} bytes) exceeds limit (${maxTotalSize} bytes)`, undefined, { totalSize, maxTotalSize, fileCount: filePaths.length });
335
- }
336
- const { results, errors } = await processInParallel(filePaths.map((filePath, index) => ({ filePath, index })), async ({ filePath, index }) => {
337
- const result = await readFile(filePath, {
338
- encoding,
339
- maxSize,
340
- head,
341
- tail,
342
- });
343
- return {
344
- index,
345
- value: { path: result.path, content: result.content },
346
- };
347
- }, PARALLEL_CONCURRENCY);
348
- for (const r of results) {
349
- output[r.index] = r.value;
350
- }
351
- for (const e of errors) {
352
- const filePath = filePaths[e.index] ?? '(unknown)';
353
- output[e.index] = {
354
- path: filePath,
355
- error: e.error.message,
356
- };
357
- }
358
- return output;
359
- }
360
- export async function searchContent(basePath, searchPattern, options = {}) {
361
- const { filePattern = '**/*', excludePatterns = [], caseSensitive = false, maxResults = DEFAULT_MAX_RESULTS, maxFileSize = MAX_SEARCHABLE_FILE_SIZE, maxFilesScanned, timeoutMs, skipBinary = true, contextLines = 0, wholeWord = false, isLiteral = false, } = options;
362
- const validPath = await validateExistingPath(basePath);
363
- const deadlineMs = timeoutMs !== undefined ? Date.now() + timeoutMs : undefined;
364
- // Prepare the search pattern with optional literal escaping and word boundaries
365
- const finalPattern = prepareSearchPattern(searchPattern, {
366
- isLiteral,
367
- wholeWord,
368
- });
369
- const needsReDoSCheck = !isLiteral && !isSimpleSafePattern(finalPattern);
370
- if (needsReDoSCheck && !safeRegex(finalPattern)) {
371
- throw new McpError(ErrorCode.E_INVALID_PATTERN, `Potentially unsafe regular expression (ReDoS risk): ${searchPattern}. ` +
372
- 'Avoid patterns with nested quantifiers, overlapping alternations, or exponential backtracking.', basePath, { reason: 'ReDoS risk detected' });
373
- }
374
- let regex;
375
- try {
376
- regex = new RegExp(finalPattern, caseSensitive ? 'g' : 'gi');
377
- }
378
- catch (error) {
379
- const message = error instanceof Error ? error.message : String(error);
380
- throw new McpError(ErrorCode.E_INVALID_PATTERN, `Invalid regular expression: ${finalPattern} (${message})`, basePath, { searchPattern: finalPattern });
381
- }
382
- const matches = [];
383
- let filesScanned = 0;
384
- let filesMatched = 0;
385
- let skippedTooLarge = 0;
386
- let skippedBinary = 0;
387
- let skippedInaccessible = 0;
388
- let linesSkippedDueToRegexTimeout = 0;
389
- let truncated = false;
390
- let stoppedReason;
391
- let firstPathValidated = false;
392
- const stopNow = (reason) => {
393
- truncated = true;
394
- stoppedReason = reason;
395
- return true;
396
- };
397
- const stream = fg.stream(filePattern, {
398
- cwd: validPath,
399
- absolute: true,
400
- onlyFiles: true,
401
- dot: false,
402
- ignore: excludePatterns,
403
- suppressErrors: true,
404
- followSymbolicLinks: false,
405
- });
406
- try {
407
- for await (const entry of stream) {
408
- const file = typeof entry === 'string' ? entry : String(entry);
409
- if (!firstPathValidated) {
410
- try {
411
- await validateExistingPath(file);
412
- firstPathValidated = true;
413
- }
414
- catch {
415
- console.error('[SECURITY] fast-glob returned invalid path:', file);
416
- stopNow('maxFiles');
417
- break;
418
- }
419
- }
420
- if (deadlineMs !== undefined && Date.now() > deadlineMs) {
421
- stopNow('timeout');
422
- break;
423
- }
424
- if (maxFilesScanned !== undefined && filesScanned >= maxFilesScanned) {
425
- stopNow('maxFiles');
426
- break;
427
- }
428
- if (matches.length >= maxResults) {
429
- stopNow('maxResults');
430
- break;
431
- }
432
- try {
433
- const handle = await fs.open(file, 'r');
434
- let shouldScan = true;
435
- try {
436
- const stats = await handle.stat();
437
- filesScanned++;
438
- if (stats.size > maxFileSize) {
439
- skippedTooLarge++;
440
- shouldScan = false;
441
- }
442
- else if (skipBinary) {
443
- const binary = await isProbablyBinary(file, handle);
444
- if (binary) {
445
- skippedBinary++;
446
- shouldScan = false;
447
- }
448
- }
449
- }
450
- finally {
451
- await handle.close().catch(() => { });
452
- }
453
- if (!shouldScan)
454
- continue;
455
- const scanResult = await scanFileForContent(file, regex, {
456
- maxResults,
457
- contextLines,
458
- deadlineMs,
459
- currentMatchCount: matches.length,
460
- isLiteral,
461
- searchString: isLiteral ? searchPattern : undefined,
462
- caseSensitive,
463
- });
464
- matches.push(...scanResult.matches);
465
- linesSkippedDueToRegexTimeout +=
466
- scanResult.linesSkippedDueToRegexTimeout;
467
- if (scanResult.fileHadMatches)
468
- filesMatched++;
469
- if (deadlineMs !== undefined && Date.now() > deadlineMs) {
470
- stopNow('timeout');
471
- break;
472
- }
473
- if (matches.length >= maxResults) {
474
- stopNow('maxResults');
475
- break;
476
- }
477
- if (stoppedReason !== undefined)
478
- break;
479
- }
480
- catch {
481
- skippedInaccessible++;
482
- }
483
- }
484
- }
485
- finally {
486
- // Ensure the stream is closed
487
- }
488
- return {
489
- basePath: validPath,
490
- pattern: searchPattern,
491
- filePattern,
492
- matches,
493
- summary: {
494
- filesScanned,
495
- filesMatched,
496
- matches: matches.length,
497
- truncated,
498
- skippedTooLarge,
499
- skippedBinary,
500
- skippedInaccessible,
501
- linesSkippedDueToRegexTimeout,
502
- stoppedReason,
503
- },
504
- };
505
- }
506
- export async function analyzeDirectory(dirPath, options = {}) {
507
- const { maxDepth = DEFAULT_MAX_DEPTH, topN = DEFAULT_TOP_N, excludePatterns = [], includeHidden = false, } = options;
508
- const validPath = await validateExistingPath(dirPath);
509
- let totalFiles = 0;
510
- let totalDirectories = 0;
511
- let totalSize = 0;
512
- let currentMaxDepth = 0;
513
- let skippedInaccessible = 0;
514
- let symlinksNotFollowed = 0;
515
- const fileTypes = {};
516
- const largestFiles = [];
517
- const recentlyModified = [];
518
- const shouldExclude = createExcludeMatcher(excludePatterns);
519
- await runWorkQueue([{ currentPath: validPath, depth: 0 }], async ({ currentPath, depth }, enqueue) => {
520
- if (depth > maxDepth)
521
- return;
522
- currentMaxDepth = Math.max(currentMaxDepth, depth);
523
- let items;
524
- try {
525
- items = await fs.readdir(currentPath, { withFileTypes: true });
526
- }
527
- catch {
528
- skippedInaccessible++;
529
- return;
530
- }
531
- for (const item of items) {
532
- const fullPath = path.join(currentPath, item.name);
533
- const relativePath = path.relative(validPath, fullPath);
534
- if (!includeHidden && isHidden(item.name)) {
535
- continue;
536
- }
537
- if (shouldExclude(item.name, relativePath)) {
538
- continue;
539
- }
540
- try {
541
- const validated = await validateExistingPathDetailed(fullPath);
542
- if (validated.isSymlink || item.isSymbolicLink()) {
543
- symlinksNotFollowed++;
544
- continue;
545
- }
546
- const stats = await fs.stat(validated.resolvedPath);
547
- if (stats.isDirectory()) {
548
- totalDirectories++;
549
- if (depth + 1 <= maxDepth) {
550
- enqueue({
551
- currentPath: validated.resolvedPath,
552
- depth: depth + 1,
553
- });
554
- }
555
- }
556
- else if (stats.isFile()) {
557
- totalFiles++;
558
- totalSize += stats.size;
559
- const ext = path.extname(item.name).toLowerCase() || '(no extension)';
560
- fileTypes[ext] = (fileTypes[ext] ?? 0) + 1;
561
- insertSorted(largestFiles, { path: validated.resolvedPath, size: stats.size }, (a, b) => b.size - a.size, topN);
562
- insertSorted(recentlyModified, { path: validated.resolvedPath, modified: stats.mtime }, (a, b) => b.modified.getTime() - a.modified.getTime(), topN);
563
- }
564
- }
565
- catch (error) {
566
- if (classifyAccessError(error) === 'symlink') {
567
- symlinksNotFollowed++;
568
- }
569
- else {
570
- skippedInaccessible++;
571
- }
572
- }
573
- }
574
- }, DIR_TRAVERSAL_CONCURRENCY);
575
- const analysis = {
576
- path: validPath,
577
- totalFiles,
578
- totalDirectories,
579
- totalSize,
580
- fileTypes,
581
- largestFiles,
582
- recentlyModified,
583
- maxDepth: currentMaxDepth,
584
- };
585
- return {
586
- analysis,
587
- summary: {
588
- truncated: false,
589
- skippedInaccessible,
590
- symlinksNotFollowed,
591
- },
592
- };
593
- }
594
- export async function getDirectoryTree(dirPath, options = {}) {
595
- const { maxDepth = DEFAULT_MAX_DEPTH, excludePatterns = [], includeHidden = false, includeSize = false, maxFiles, } = options;
596
- const validPath = await validateExistingPath(dirPath);
597
- const rootStats = await fs.stat(validPath);
598
- if (!rootStats.isDirectory()) {
599
- throw new McpError(ErrorCode.E_NOT_DIRECTORY, `Not a directory: ${dirPath}`, dirPath);
600
- }
601
- let totalFiles = 0;
602
- let totalDirectories = 0;
603
- let maxDepthReached = 0;
604
- let skippedInaccessible = 0;
605
- let symlinksNotFollowed = 0;
606
- let truncated = false;
607
- const shouldExclude = createExcludeMatcher(excludePatterns);
608
- const hitMaxFiles = () => {
609
- return maxFiles !== undefined && totalFiles >= maxFiles;
610
- };
611
- const collectedEntries = [];
612
- const directoriesFound = new Set([validPath]);
613
- await runWorkQueue([{ currentPath: validPath, depth: 0 }], async ({ currentPath, depth }, enqueue) => {
614
- if (hitMaxFiles()) {
615
- truncated = true;
616
- return;
617
- }
618
- if (depth > maxDepth) {
619
- truncated = true;
620
- return;
621
- }
622
- maxDepthReached = Math.max(maxDepthReached, depth);
623
- let items;
624
- try {
625
- items = await fs.readdir(currentPath, { withFileTypes: true });
626
- }
627
- catch {
628
- skippedInaccessible++;
629
- return;
630
- }
631
- for (const item of items) {
632
- if (hitMaxFiles()) {
633
- truncated = true;
634
- break;
635
- }
636
- const { name } = item;
637
- if (!includeHidden && name.startsWith('.')) {
638
- continue;
639
- }
640
- const fullPath = path.join(currentPath, name);
641
- const relativePath = path.relative(validPath, fullPath);
642
- if (shouldExclude(name, relativePath)) {
643
- continue;
644
- }
645
- if (item.isSymbolicLink()) {
646
- symlinksNotFollowed++;
647
- continue;
648
- }
649
- try {
650
- const { resolvedPath, isSymlink } = await validateExistingPathDetailed(fullPath);
651
- if (isSymlink) {
652
- symlinksNotFollowed++;
653
- continue;
654
- }
655
- const stats = await fs.stat(resolvedPath);
656
- if (stats.isFile()) {
657
- totalFiles++;
658
- collectedEntries.push({
659
- parentPath: currentPath,
660
- name,
661
- type: 'file',
662
- size: includeSize ? stats.size : undefined,
663
- depth,
664
- });
665
- }
666
- else if (stats.isDirectory()) {
667
- totalDirectories++;
668
- directoriesFound.add(resolvedPath);
669
- collectedEntries.push({
670
- parentPath: currentPath,
671
- name,
672
- type: 'directory',
673
- depth,
674
- });
675
- if (depth + 1 <= maxDepth) {
676
- enqueue({ currentPath: resolvedPath, depth: depth + 1 });
677
- }
678
- else {
679
- truncated = true;
680
- }
681
- }
682
- }
683
- catch (error) {
684
- if (classifyAccessError(error) === 'symlink') {
685
- symlinksNotFollowed++;
686
- }
687
- else {
688
- skippedInaccessible++;
689
- }
690
- }
691
- }
692
- }, DIR_TRAVERSAL_CONCURRENCY);
693
- const childrenByParent = new Map();
694
- for (const dirPath of directoriesFound) {
695
- childrenByParent.set(dirPath, []);
696
- }
697
- for (const entry of collectedEntries) {
698
- const treeEntry = {
699
- name: entry.name,
700
- type: entry.type,
701
- };
702
- if (entry.type === 'file' && entry.size !== undefined) {
703
- treeEntry.size = entry.size;
704
- }
705
- if (entry.type === 'directory') {
706
- const fullPath = path.join(entry.parentPath, entry.name);
707
- treeEntry.children = childrenByParent.get(fullPath) ?? [];
708
- }
709
- const siblings = childrenByParent.get(entry.parentPath);
710
- if (siblings) {
711
- siblings.push(treeEntry);
712
- }
713
- }
714
- const sortChildren = (entries) => {
715
- entries.sort((a, b) => {
716
- if (a.type !== b.type) {
717
- return a.type === 'directory' ? -1 : 1;
718
- }
719
- return a.name.localeCompare(b.name);
720
- });
721
- };
722
- for (const children of childrenByParent.values()) {
723
- sortChildren(children);
724
- }
725
- const rootName = path.basename(validPath);
726
- const tree = {
727
- name: rootName || validPath,
728
- type: 'directory',
729
- children: childrenByParent.get(validPath) ?? [],
730
- };
731
- return {
732
- tree,
733
- summary: {
734
- totalFiles,
735
- totalDirectories,
736
- maxDepthReached,
737
- truncated,
738
- skippedInaccessible,
739
- symlinksNotFollowed,
740
- },
741
- };
742
- }
743
- export async function readMediaFile(filePath, { maxSize = MAX_MEDIA_FILE_SIZE } = {}) {
744
- const validPath = await validateExistingPath(filePath);
745
- const stats = await fs.stat(validPath);
746
- const { size } = stats;
747
- if (!stats.isFile()) {
748
- throw new McpError(ErrorCode.E_NOT_FILE, `Not a file: ${filePath}`, filePath);
749
- }
750
- if (size > maxSize) {
751
- throw new McpError(ErrorCode.E_TOO_LARGE, `File too large: ${size} bytes (max: ${maxSize} bytes)`, filePath, { size, maxSize });
752
- }
753
- const ext = path.extname(validPath).toLowerCase();
754
- const mimeType = getMimeType(ext);
755
- const buffer = await fs.readFile(validPath);
756
- const data = buffer.toString('base64');
757
- let width;
758
- let height;
759
- if (mimeType.startsWith('image/')) {
760
- const dimensions = parseImageDimensions(buffer, ext);
761
- if (dimensions) {
762
- ({ width, height } = dimensions);
763
- }
764
- }
765
- return {
766
- path: validPath,
767
- mimeType,
768
- size,
769
- data,
770
- width,
771
- height,
772
- };
773
- }
1
+ export { analyzeDirectory } from './file-operations/analyze-directory.js';
2
+ export { getDirectoryTree } from './file-operations/directory-tree.js';
3
+ export { getFileInfo } from './file-operations/file-info.js';
4
+ export { listDirectory } from './file-operations/list-directory.js';
5
+ export { readMediaFile } from './file-operations/read-media-file.js';
6
+ export { readMultipleFiles } from './file-operations/read-multiple-files.js';
7
+ export { searchContent } from './file-operations/search-content.js';
8
+ export { searchFiles } from './file-operations/search-files.js';
9
+ export { readFile } from './fs-helpers.js';
774
10
  //# sourceMappingURL=file-operations.js.map