@j0hanz/filesystem-mcp 1.19.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/README.md +333 -838
  2. package/dist/cli-help.d.ts +3 -0
  3. package/dist/cli-help.d.ts.map +1 -0
  4. package/dist/cli-help.js +169 -0
  5. package/dist/cli-help.js.map +1 -0
  6. package/dist/cli.d.ts +28 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +168 -123
  9. package/dist/cli.js.map +1 -0
  10. package/dist/core/concurrency.d.ts +70 -0
  11. package/dist/core/concurrency.d.ts.map +1 -0
  12. package/dist/core/concurrency.js +173 -0
  13. package/dist/core/concurrency.js.map +1 -0
  14. package/dist/core/config.d.ts +19 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +11 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/cursor.d.ts +23 -0
  19. package/dist/core/cursor.d.ts.map +1 -0
  20. package/dist/core/cursor.js +62 -0
  21. package/dist/core/cursor.js.map +1 -0
  22. package/dist/core/errors.d.ts +96 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/core/errors.js +356 -0
  25. package/dist/core/errors.js.map +1 -0
  26. package/dist/core/file-uri.d.ts +24 -0
  27. package/dist/core/file-uri.d.ts.map +1 -0
  28. package/dist/core/file-uri.js +52 -0
  29. package/dist/core/file-uri.js.map +1 -0
  30. package/dist/core/fmt.d.ts +38 -0
  31. package/dist/core/fmt.d.ts.map +1 -0
  32. package/dist/core/fmt.js +109 -0
  33. package/dist/core/fmt.js.map +1 -0
  34. package/dist/core/fs.d.ts +108 -0
  35. package/dist/core/fs.d.ts.map +1 -0
  36. package/dist/core/fs.js +243 -0
  37. package/dist/core/fs.js.map +1 -0
  38. package/dist/core/glob.d.ts +41 -0
  39. package/dist/core/glob.d.ts.map +1 -0
  40. package/dist/core/glob.js +399 -0
  41. package/dist/core/glob.js.map +1 -0
  42. package/dist/core/input-required.d.ts +147 -0
  43. package/dist/core/input-required.d.ts.map +1 -0
  44. package/dist/core/input-required.js +261 -0
  45. package/dist/core/input-required.js.map +1 -0
  46. package/dist/core/mime.d.ts +15 -0
  47. package/dist/core/mime.d.ts.map +1 -0
  48. package/dist/core/mime.js +188 -0
  49. package/dist/core/mime.js.map +1 -0
  50. package/dist/core/observability.d.ts +10 -0
  51. package/dist/core/observability.d.ts.map +1 -0
  52. package/dist/core/observability.js +76 -0
  53. package/dist/core/observability.js.map +1 -0
  54. package/dist/core/page-store.d.ts +41 -0
  55. package/dist/core/page-store.d.ts.map +1 -0
  56. package/dist/core/page-store.js +74 -0
  57. package/dist/core/page-store.js.map +1 -0
  58. package/dist/core/path-completer.d.ts +10 -0
  59. package/dist/core/path-completer.d.ts.map +1 -0
  60. package/dist/core/path-completer.js +227 -0
  61. package/dist/core/path-completer.js.map +1 -0
  62. package/dist/core/path-discovery.d.ts +8 -0
  63. package/dist/core/path-discovery.d.ts.map +1 -0
  64. package/dist/core/path-discovery.js +105 -0
  65. package/dist/core/path-discovery.js.map +1 -0
  66. package/dist/core/path-utils.d.ts +9 -0
  67. package/dist/core/path-utils.d.ts.map +1 -0
  68. package/dist/core/path-utils.js +121 -0
  69. package/dist/core/path-utils.js.map +1 -0
  70. package/dist/core/path.d.ts +148 -0
  71. package/dist/core/path.d.ts.map +1 -0
  72. package/dist/core/path.js +674 -0
  73. package/dist/core/path.js.map +1 -0
  74. package/dist/core/primitives.d.ts +23 -0
  75. package/dist/core/primitives.d.ts.map +1 -0
  76. package/dist/core/primitives.js +54 -0
  77. package/dist/core/primitives.js.map +1 -0
  78. package/dist/core/read.d.ts +74 -0
  79. package/dist/core/read.d.ts.map +1 -0
  80. package/dist/core/read.js +562 -0
  81. package/dist/core/read.js.map +1 -0
  82. package/dist/core/schema.d.ts +139 -0
  83. package/dist/core/schema.d.ts.map +1 -0
  84. package/dist/core/schema.js +345 -0
  85. package/dist/core/schema.js.map +1 -0
  86. package/dist/core/search.d.ts +100 -0
  87. package/dist/core/search.d.ts.map +1 -0
  88. package/dist/core/search.js +248 -0
  89. package/dist/core/search.js.map +1 -0
  90. package/dist/core/sensitive.d.ts +6 -0
  91. package/dist/core/sensitive.d.ts.map +1 -0
  92. package/dist/core/sensitive.js +142 -0
  93. package/dist/core/sensitive.js.map +1 -0
  94. package/dist/core/store.d.ts +57 -0
  95. package/dist/core/store.d.ts.map +1 -0
  96. package/dist/core/store.js +162 -0
  97. package/dist/core/store.js.map +1 -0
  98. package/dist/core/util.d.ts +26 -0
  99. package/dist/core/util.d.ts.map +1 -0
  100. package/dist/core/util.js +66 -0
  101. package/dist/core/util.js.map +1 -0
  102. package/dist/core/watcher-registry.d.ts +68 -0
  103. package/dist/core/watcher-registry.d.ts.map +1 -0
  104. package/dist/core/watcher-registry.js +312 -0
  105. package/dist/core/watcher-registry.js.map +1 -0
  106. package/dist/http-policy.d.ts +121 -0
  107. package/dist/http-policy.d.ts.map +1 -0
  108. package/dist/http-policy.js +340 -0
  109. package/dist/http-policy.js.map +1 -0
  110. package/dist/index.d.ts +1 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +77 -41
  113. package/dist/index.js.map +1 -0
  114. package/dist/instructions.d.ts +12 -0
  115. package/dist/instructions.d.ts.map +1 -0
  116. package/dist/instructions.js +71 -0
  117. package/dist/instructions.js.map +1 -0
  118. package/dist/prompts.d.ts +8 -6
  119. package/dist/prompts.d.ts.map +1 -0
  120. package/dist/prompts.js +74 -168
  121. package/dist/prompts.js.map +1 -0
  122. package/dist/resources.d.ts +73 -9
  123. package/dist/resources.d.ts.map +1 -0
  124. package/dist/resources.js +392 -163
  125. package/dist/resources.js.map +1 -0
  126. package/dist/server.d.ts +62 -1
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +139 -1
  129. package/dist/server.js.map +1 -0
  130. package/dist/tools/batch.d.ts +89 -0
  131. package/dist/tools/batch.d.ts.map +1 -0
  132. package/dist/tools/batch.js +157 -0
  133. package/dist/tools/batch.js.map +1 -0
  134. package/dist/tools/create.d.ts +2 -0
  135. package/dist/tools/create.d.ts.map +1 -0
  136. package/dist/tools/create.js +142 -0
  137. package/dist/tools/create.js.map +1 -0
  138. package/dist/tools/define.d.ts +110 -0
  139. package/dist/tools/define.d.ts.map +1 -0
  140. package/dist/tools/define.js +394 -0
  141. package/dist/tools/define.js.map +1 -0
  142. package/dist/tools/delete-file.d.ts +2 -4
  143. package/dist/tools/delete-file.d.ts.map +1 -0
  144. package/dist/tools/delete-file.js +330 -87
  145. package/dist/tools/delete-file.js.map +1 -0
  146. package/dist/tools/diff.d.ts +2 -0
  147. package/dist/tools/diff.d.ts.map +1 -0
  148. package/dist/tools/diff.js +94 -0
  149. package/dist/tools/diff.js.map +1 -0
  150. package/dist/tools/edit.d.ts +2 -0
  151. package/dist/tools/edit.d.ts.map +1 -0
  152. package/dist/tools/edit.js +442 -0
  153. package/dist/tools/edit.js.map +1 -0
  154. package/dist/tools/index.d.ts +26 -0
  155. package/dist/tools/index.d.ts.map +1 -0
  156. package/dist/tools/index.js +52 -0
  157. package/dist/tools/index.js.map +1 -0
  158. package/dist/tools/list.d.ts +2 -0
  159. package/dist/tools/list.d.ts.map +1 -0
  160. package/dist/tools/list.js +291 -0
  161. package/dist/tools/list.js.map +1 -0
  162. package/dist/tools/move.d.ts +2 -0
  163. package/dist/tools/move.d.ts.map +1 -0
  164. package/dist/tools/move.js +321 -0
  165. package/dist/tools/move.js.map +1 -0
  166. package/dist/tools/patch.d.ts +2 -0
  167. package/dist/tools/patch.d.ts.map +1 -0
  168. package/dist/tools/patch.js +155 -0
  169. package/dist/tools/patch.js.map +1 -0
  170. package/dist/tools/progress.d.ts +52 -0
  171. package/dist/tools/progress.d.ts.map +1 -0
  172. package/dist/tools/progress.js +172 -0
  173. package/dist/tools/progress.js.map +1 -0
  174. package/dist/tools/read.d.ts +2 -4
  175. package/dist/tools/read.d.ts.map +1 -0
  176. package/dist/tools/read.js +398 -152
  177. package/dist/tools/read.js.map +1 -0
  178. package/dist/tools/replace-in-files.d.ts +2 -4
  179. package/dist/tools/replace-in-files.d.ts.map +1 -0
  180. package/dist/tools/replace-in-files.js +435 -264
  181. package/dist/tools/replace-in-files.js.map +1 -0
  182. package/dist/tools/roots.d.ts +2 -4
  183. package/dist/tools/roots.d.ts.map +1 -0
  184. package/dist/tools/roots.js +29 -52
  185. package/dist/tools/roots.js.map +1 -0
  186. package/dist/tools/search-content.d.ts +2 -4
  187. package/dist/tools/search-content.d.ts.map +1 -0
  188. package/dist/tools/search-content.js +262 -228
  189. package/dist/tools/search-content.js.map +1 -0
  190. package/dist/tools/search-files.d.ts +2 -4
  191. package/dist/tools/search-files.d.ts.map +1 -0
  192. package/dist/tools/search-files.js +162 -161
  193. package/dist/tools/search-files.js.map +1 -0
  194. package/dist/tools/stat.d.ts +2 -4
  195. package/dist/tools/stat.d.ts.map +1 -0
  196. package/dist/tools/stat.js +202 -62
  197. package/dist/tools/stat.js.map +1 -0
  198. package/dist/transport/http.d.ts +5 -0
  199. package/dist/transport/http.d.ts.map +1 -0
  200. package/dist/transport/http.js +276 -0
  201. package/dist/transport/http.js.map +1 -0
  202. package/dist/transport/shared.d.ts +45 -0
  203. package/dist/transport/shared.d.ts.map +1 -0
  204. package/dist/transport/shared.js +65 -0
  205. package/dist/transport/shared.js.map +1 -0
  206. package/dist/transport/stdio.d.ts +32 -0
  207. package/dist/transport/stdio.d.ts.map +1 -0
  208. package/dist/transport/stdio.js +279 -0
  209. package/dist/transport/stdio.js.map +1 -0
  210. package/dist/transport.d.ts +5 -0
  211. package/dist/transport.d.ts.map +1 -0
  212. package/dist/transport.js +4 -0
  213. package/dist/transport.js.map +1 -0
  214. package/package.json +37 -46
  215. package/dist/assets/logo.svg +0 -35
  216. package/dist/completions.d.ts +0 -2
  217. package/dist/completions.js +0 -572
  218. package/dist/config.d.ts +0 -119
  219. package/dist/config.js +0 -31
  220. package/dist/lib/abort.d.ts +0 -7
  221. package/dist/lib/abort.js +0 -81
  222. package/dist/lib/constants.d.ts +0 -42
  223. package/dist/lib/constants.js +0 -313
  224. package/dist/lib/errors.d.ts +0 -28
  225. package/dist/lib/errors.js +0 -301
  226. package/dist/lib/file-operations/core.d.ts +0 -53
  227. package/dist/lib/file-operations/core.js +0 -144
  228. package/dist/lib/file-operations/metadata.d.ts +0 -77
  229. package/dist/lib/file-operations/metadata.js +0 -878
  230. package/dist/lib/file-operations/search.d.ts +0 -46
  231. package/dist/lib/file-operations/search.js +0 -1194
  232. package/dist/lib/file-operations/traversal.d.ts +0 -40
  233. package/dist/lib/file-operations/traversal.js +0 -310
  234. package/dist/lib/fs-helpers.d.ts +0 -46
  235. package/dist/lib/fs-helpers.js +0 -606
  236. package/dist/lib/globs.d.ts +0 -2
  237. package/dist/lib/globs.js +0 -19
  238. package/dist/lib/logger.d.ts +0 -27
  239. package/dist/lib/logger.js +0 -91
  240. package/dist/lib/observability.d.ts +0 -33
  241. package/dist/lib/observability.js +0 -373
  242. package/dist/lib/paths.d.ts +0 -35
  243. package/dist/lib/paths.js +0 -634
  244. package/dist/lib/resource-store.d.ts +0 -28
  245. package/dist/lib/resource-store.js +0 -195
  246. package/dist/lib/utils.d.ts +0 -7
  247. package/dist/lib/utils.js +0 -37
  248. package/dist/lib/zod-codecs.d.ts +0 -2
  249. package/dist/lib/zod-codecs.js +0 -18
  250. package/dist/pkg-info.d.ts +0 -7
  251. package/dist/pkg-info.js +0 -9
  252. package/dist/resources/generated-instructions.d.ts +0 -1
  253. package/dist/resources/generated-instructions.js +0 -105
  254. package/dist/resources/tool-catalog.d.ts +0 -2
  255. package/dist/resources/tool-catalog.js +0 -78
  256. package/dist/resources/tool-info.d.ts +0 -9
  257. package/dist/resources/tool-info.js +0 -206
  258. package/dist/resources/workflows.d.ts +0 -1
  259. package/dist/resources/workflows.js +0 -72
  260. package/dist/schemas.d.ts +0 -505
  261. package/dist/schemas.js +0 -676
  262. package/dist/server/bootstrap.d.ts +0 -6
  263. package/dist/server/bootstrap.js +0 -585
  264. package/dist/server/event-store.d.ts +0 -18
  265. package/dist/server/event-store.js +0 -71
  266. package/dist/server/roots-manager.d.ts +0 -28
  267. package/dist/server/roots-manager.js +0 -227
  268. package/dist/server/task-store.d.ts +0 -10
  269. package/dist/server/task-store.js +0 -91
  270. package/dist/tools/apply-patch.d.ts +0 -4
  271. package/dist/tools/apply-patch.js +0 -235
  272. package/dist/tools/calculate-hash.d.ts +0 -4
  273. package/dist/tools/calculate-hash.js +0 -187
  274. package/dist/tools/contract.d.ts +0 -50
  275. package/dist/tools/contract.js +0 -1
  276. package/dist/tools/create-directory.d.ts +0 -4
  277. package/dist/tools/create-directory.js +0 -68
  278. package/dist/tools/diff-files.d.ts +0 -4
  279. package/dist/tools/diff-files.js +0 -136
  280. package/dist/tools/edit-file.d.ts +0 -4
  281. package/dist/tools/edit-file.js +0 -256
  282. package/dist/tools/icons.d.ts +0 -15
  283. package/dist/tools/icons.js +0 -24
  284. package/dist/tools/list-directory.d.ts +0 -4
  285. package/dist/tools/list-directory.js +0 -218
  286. package/dist/tools/move-file.d.ts +0 -4
  287. package/dist/tools/move-file.js +0 -178
  288. package/dist/tools/read-multiple.d.ts +0 -4
  289. package/dist/tools/read-multiple.js +0 -207
  290. package/dist/tools/shared.d.ts +0 -172
  291. package/dist/tools/shared.js +0 -599
  292. package/dist/tools/stat-many.d.ts +0 -4
  293. package/dist/tools/stat-many.js +0 -104
  294. package/dist/tools/task-support.d.ts +0 -29
  295. package/dist/tools/task-support.js +0 -473
  296. package/dist/tools/tree.d.ts +0 -4
  297. package/dist/tools/tree.js +0 -102
  298. package/dist/tools/write-file.d.ts +0 -4
  299. package/dist/tools/write-file.js +0 -60
  300. package/dist/tools.d.ts +0 -5
  301. package/dist/tools.js +0 -54
@@ -1,256 +0,0 @@
1
- import { stat } from 'node:fs/promises';
2
- import { basename } from 'node:path';
3
- import { createTwoFilesPatch, diffLines } from 'diff';
4
- import RE2 from 're2';
5
- import { withAbort } from '../lib/abort.js';
6
- import { MAX_TEXT_FILE_SIZE } from '../lib/constants.js';
7
- import { ErrorCode, McpError } from '../lib/errors.js';
8
- import { atomicWriteFile, readFileWithStats } from '../lib/fs-helpers.js';
9
- import { Logger } from '../lib/logger.js';
10
- import { assertAllowedFileAccess, validateExistingPath } from '../lib/paths.js';
11
- import { EditFileInputSchema, EditFileOutputSchema } from '../schemas.js';
12
- import { FILE_EDIT_ICONS } from './icons.js';
13
- import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, } from './shared.js';
14
- import { registerStandardTool } from './task-support.js';
15
- export const EDIT_FILE_TOOL = {
16
- name: 'edit',
17
- title: 'Edit File',
18
- description: 'Apply sequential literal string replacements to a file (first occurrence per edit). ' +
19
- '`oldText` must match exactly — include 3–5 lines of context for unique targeting. ' +
20
- 'Use `dryRun:true` to preview.',
21
- inputSchema: EditFileInputSchema,
22
- outputSchema: EditFileOutputSchema,
23
- annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
24
- icons: FILE_EDIT_ICONS,
25
- nuances: ['Each edit applies to the output of the previous edit.'],
26
- taskSupport: 'forbidden',
27
- };
28
- function escapeRegExp(string) {
29
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
- }
31
- function getLineNumberAtIndex(str, maxIndex = str.length) {
32
- let count = 1;
33
- let pos = 0;
34
- while (pos < maxIndex) {
35
- pos = str.indexOf('\n', pos);
36
- if (pos === -1 || pos >= maxIndex)
37
- break;
38
- count++;
39
- pos++;
40
- }
41
- return count;
42
- }
43
- function countLines(str) {
44
- return getLineNumberAtIndex(str);
45
- }
46
- async function computeDiffStats(original, modified) {
47
- return new Promise((resolve) => {
48
- diffLines(original, modified, {
49
- callback: (changes) => {
50
- let linesAdded = 0;
51
- let linesRemoved = 0;
52
- for (const part of changes) {
53
- if (part.added) {
54
- linesAdded += part.count;
55
- }
56
- else if (part.removed) {
57
- linesRemoved += part.count;
58
- }
59
- }
60
- resolve({ linesAdded, linesRemoved });
61
- },
62
- });
63
- });
64
- }
65
- function findEditMatch(content, oldText, ignoreWhitespace, regexCache) {
66
- if (ignoreWhitespace) {
67
- const pattern = escapeRegExp(oldText).replace(/\s+/g, '\\s+');
68
- let regex = regexCache?.get(pattern);
69
- if (!regex) {
70
- regex = new RE2(pattern);
71
- if (regexCache)
72
- regexCache.set(pattern, regex);
73
- }
74
- const match = regex.exec(content);
75
- if (!match) {
76
- return undefined;
77
- }
78
- return {
79
- startIndex: match.index,
80
- length: match[0].length,
81
- };
82
- }
83
- const index = content.indexOf(oldText);
84
- if (index === -1) {
85
- return undefined;
86
- }
87
- return {
88
- startIndex: index,
89
- length: oldText.length,
90
- };
91
- }
92
- function replaceEditMatch(content, match, newText) {
93
- return (content.slice(0, match.startIndex) +
94
- newText +
95
- content.slice(match.startIndex + match.length));
96
- }
97
- function mergeLineRange(currentRange, content, matchStartIndex, newText) {
98
- const startLine = getLineNumberAtIndex(content, matchStartIndex);
99
- const endLine = startLine + countLines(newText) - 1;
100
- if (!currentRange) {
101
- return [startLine, endLine];
102
- }
103
- return [
104
- Math.min(currentRange[0], startLine),
105
- Math.max(currentRange[1], endLine),
106
- ];
107
- }
108
- function buildStructuredEditOutput(validPath, result) {
109
- return {
110
- ok: result.appliedEdits > 0 || result.unmatchedEdits.length === 0,
111
- path: validPath,
112
- appliedEdits: result.appliedEdits,
113
- ...(result.appliedEdits > 0
114
- ? {
115
- linesAdded: result.linesAdded,
116
- linesRemoved: result.linesRemoved,
117
- }
118
- : {}),
119
- ...(result.unmatchedEdits.length > 0
120
- ? { unmatchedEdits: result.unmatchedEdits }
121
- : {}),
122
- ...(result.lineRange ? { lineRange: result.lineRange } : {}),
123
- };
124
- }
125
- async function finalizeEditResult(originalContent, updatedContent, appliedEdits, unmatchedEdits, lineRange) {
126
- const { linesAdded, linesRemoved } = appliedEdits > 0
127
- ? await computeDiffStats(originalContent, updatedContent)
128
- : { linesAdded: 0, linesRemoved: 0 };
129
- return {
130
- content: updatedContent,
131
- appliedEdits,
132
- unmatchedEdits,
133
- linesAdded,
134
- linesRemoved,
135
- ...(lineRange ? { lineRange } : {}),
136
- };
137
- }
138
- async function buildDiff(validPath, original, modified) {
139
- const fileName = basename(validPath);
140
- return new Promise((resolve) => {
141
- createTwoFilesPatch(fileName, fileName, original, modified, 'Original', 'Modified', {
142
- callback: (res) => {
143
- resolve(res ?? '');
144
- },
145
- });
146
- });
147
- }
148
- function formatUnmatchedEditsNote(unmatchedEdits) {
149
- if (unmatchedEdits.length === 0) {
150
- return '';
151
- }
152
- return ` — ${unmatchedEdits.length} unmatched: [${unmatchedEdits
153
- .map((text) => JSON.stringify(text.length > 40 ? `${text.slice(0, 40)}…` : text))
154
- .join(', ')}]`;
155
- }
156
- function buildEditMessage(requestedPath, result) {
157
- const unmatchedNote = formatUnmatchedEditsNote(result.unmatchedEdits);
158
- if (result.appliedEdits === 0) {
159
- return `No edits applied to ${requestedPath}${unmatchedNote}`;
160
- }
161
- return `Successfully applied ${result.appliedEdits} edits to ${requestedPath}${unmatchedNote}`;
162
- }
163
- async function loadEditableFile(requestedPath, signal) {
164
- const validPath = await validateExistingPath(requestedPath, signal);
165
- assertAllowedFileAccess(requestedPath, validPath);
166
- const stats = await withAbort(stat(validPath), signal);
167
- if (stats.size > MAX_TEXT_FILE_SIZE) {
168
- throw new McpError(ErrorCode.TOO_LARGE, `File too large for edit (${stats.size} bytes > ${MAX_TEXT_FILE_SIZE} bytes)`, requestedPath, { size: stats.size, maxFileSize: MAX_TEXT_FILE_SIZE });
169
- }
170
- const { content } = await readFileWithStats(requestedPath, validPath, stats, {
171
- encoding: 'utf-8',
172
- maxSize: MAX_TEXT_FILE_SIZE,
173
- skipBinary: true,
174
- ...(signal ? { signal } : {}),
175
- });
176
- return { validPath, content };
177
- }
178
- function buildEditProgressMessage(args) {
179
- const name = basename(args.path);
180
- const tag = args.dryRun ? ' [dry run]' : '';
181
- return `${EDIT_FILE_TOOL.title}: ${name}${tag}`;
182
- }
183
- function buildEditCompletionMessage(args, result) {
184
- const name = basename(args.path);
185
- if (result.isError)
186
- return `${EDIT_FILE_TOOL.title}: ${name} • ${result.errorCode}`;
187
- const { structuredContent } = result;
188
- if (!structuredContent.ok)
189
- return `${EDIT_FILE_TOOL.title}: ${name} • failed`;
190
- const applied = structuredContent.appliedEdits ?? 0;
191
- if (applied === 0)
192
- return `${EDIT_FILE_TOOL.title}: ${name} • no changes`;
193
- const added = structuredContent.linesAdded ?? 0;
194
- const removed = structuredContent.linesRemoved ?? 0;
195
- const dry = args.dryRun ? 'dry run ' : '';
196
- return `${EDIT_FILE_TOOL.title}: ${name} • ${dry} +${added} -${removed}`;
197
- }
198
- async function applyEdits(content, edits, ignoreWhitespace) {
199
- let newContent = content;
200
- let appliedEdits = 0;
201
- const unmatchedEdits = [];
202
- let lineRange;
203
- const regexCache = ignoreWhitespace ? new Map() : undefined;
204
- for (const edit of edits) {
205
- const match = findEditMatch(newContent, edit.oldText, ignoreWhitespace, regexCache);
206
- if (!match) {
207
- unmatchedEdits.push(edit.oldText);
208
- continue;
209
- }
210
- lineRange = mergeLineRange(lineRange, newContent, match.startIndex, edit.newText);
211
- newContent = replaceEditMatch(newContent, match, edit.newText);
212
- appliedEdits += 1;
213
- }
214
- return finalizeEditResult(content, newContent, appliedEdits, unmatchedEdits, lineRange);
215
- }
216
- async function handleEditFile(args, signal) {
217
- const { validPath, content } = await loadEditableFile(args.path, signal);
218
- const editResult = await applyEdits(content, args.edits, args.ignoreWhitespace);
219
- const structured = buildStructuredEditOutput(validPath, editResult);
220
- if (args.dryRun) {
221
- if (editResult.appliedEdits > 0) {
222
- structured.diff = await buildDiff(validPath, content, editResult.content);
223
- }
224
- return buildToolResponse(`Dry run complete. ${editResult.appliedEdits} edits would be applied.`, structured);
225
- }
226
- if (editResult.appliedEdits === 0 && editResult.unmatchedEdits.length > 0) {
227
- throw new McpError(ErrorCode.INVALID_INPUT, `All ${editResult.unmatchedEdits.length} edits failed. Verify oldText matches exact file content.`, args.path);
228
- }
229
- if (editResult.appliedEdits > 0) {
230
- await atomicWriteFile(validPath, editResult.content, {
231
- encoding: 'utf-8',
232
- signal,
233
- });
234
- Logger.info(`edit: ${args.path} (${editResult.appliedEdits} edits, +${editResult.linesAdded}/-${editResult.linesRemoved})`);
235
- }
236
- return buildToolResponse(buildEditMessage(args.path, editResult), structured);
237
- }
238
- export function registerEditFileTool(server, options = {}) {
239
- const handler = (args, ctx) => executeToolWithDiagnostics({
240
- toolName: 'edit',
241
- ctx,
242
- outputSchema: EditFileOutputSchema,
243
- timedSignal: {},
244
- context: { path: args.path },
245
- run: async (signal) => {
246
- const result = await handleEditFile(args, signal);
247
- void ctx.log?.('info', `edit: ${args.path} (${String(result.structuredContent.appliedEdits ?? 0)} edits)`);
248
- return result;
249
- },
250
- onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path),
251
- });
252
- registerStandardTool(server, EDIT_FILE_TOOL, handler, options, {
253
- progressMessage: buildEditProgressMessage,
254
- completionMessage: buildEditCompletionMessage,
255
- });
256
- }
@@ -1,15 +0,0 @@
1
- import type { Icon } from '@modelcontextprotocol/server';
2
- /** Document with lines — read, read_many, stat, stat_many, calculate_hash, diff_files */
3
- export declare const FILE_READ_ICONS: Icon[];
4
- /** Folder — ls, tree, find, roots */
5
- export declare const DIRECTORY_ICONS: Icon[];
6
- /** Magnifying glass — grep */
7
- export declare const SEARCH_ICONS: Icon[];
8
- /** Pencil — write, edit, apply_patch, search_and_replace */
9
- export declare const FILE_EDIT_ICONS: Icon[];
10
- /** Arrow — mv */
11
- export declare const FILE_MOVE_ICONS: Icon[];
12
- /** Trash — rm */
13
- export declare const FILE_DELETE_ICONS: Icon[];
14
- /** Folder with plus — mkdir */
15
- export declare const DIR_CREATE_ICONS: Icon[];
@@ -1,24 +0,0 @@
1
- const MIME = 'image/svg+xml';
2
- function svgIcon(paths) {
3
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
4
- return [
5
- {
6
- src: `data:${MIME};base64,${Buffer.from(svg).toString('base64')}`,
7
- mimeType: MIME,
8
- },
9
- ];
10
- }
11
- /** Document with lines — read, read_many, stat, stat_many, calculate_hash, diff_files */
12
- export const FILE_READ_ICONS = svgIcon('<path d="M4 1h5l4 4v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z"/><path d="M9 1v4h4"/><path d="M6 8h4M6 11h3"/>');
13
- /** Folder — ls, tree, find, roots */
14
- export const DIRECTORY_ICONS = svgIcon('<path d="M2 4h4l2 2h6v7a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z"/>');
15
- /** Magnifying glass — grep */
16
- export const SEARCH_ICONS = svgIcon('<circle cx="6.5" cy="6.5" r="4"/><path d="M10 10l4 4"/>');
17
- /** Pencil — write, edit, apply_patch, search_and_replace */
18
- export const FILE_EDIT_ICONS = svgIcon('<path d="M11 2l3 3-9 9H2v-3z"/>');
19
- /** Arrow — mv */
20
- export const FILE_MOVE_ICONS = svgIcon('<path d="M2 8h12M10 4l4 4-4 4"/>');
21
- /** Trash — rm */
22
- export const FILE_DELETE_ICONS = svgIcon('<path d="M3 4h10M5.5 4V3h5v1"/><path d="M4 4l.7 9.5h6.6L12 4"/>');
23
- /** Folder with plus — mkdir */
24
- export const DIR_CREATE_ICONS = svgIcon('<path d="M2 4h4l2 2h6v7a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z"/><path d="M8 9v3M6.5 10.5h3"/>');
@@ -1,4 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
- export declare const LIST_DIRECTORY_TOOL: ToolContract;
4
- export declare function registerListDirectoryTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -1,218 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { basename } from 'node:path';
3
- import { z } from 'zod';
4
- import { DEFAULT_EXCLUDE_PATTERNS, MAX_LIST_ENTRIES, } from '../lib/constants.js';
5
- import { ErrorCode, McpError } from '../lib/errors.js';
6
- import { listDirectory } from '../lib/file-operations/metadata.js';
7
- import { createBase64JsonCodec } from '../lib/zod-codecs.js';
8
- import { formatOperationSummary, joinLines } from '../config.js';
9
- import { ListDirectoryInputSchema, ListDirectoryOutputSchema, } from '../schemas.js';
10
- import { DIRECTORY_ICONS } from './icons.js';
11
- import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, } from './shared.js';
12
- import { registerStandardTool } from './task-support.js';
13
- export const LIST_DIRECTORY_TOOL = {
14
- name: 'ls',
15
- title: 'List Directory',
16
- description: 'List immediate directory contents (non-recursive): name, path, type, size, modified date. ' +
17
- 'Omit path for workspace root. `includeIgnored=true` for node_modules etc. ' +
18
- 'For recursive search, use `find`.',
19
- inputSchema: ListDirectoryInputSchema,
20
- outputSchema: ListDirectoryOutputSchema,
21
- annotations: READ_ONLY_TOOL_ANNOTATIONS,
22
- icons: DIRECTORY_ICONS,
23
- taskSupport: 'optional',
24
- nuances: ['`pattern` enables filtered recursive traversal up to `maxDepth`.'],
25
- };
26
- const LIST_CURSOR_TTL_MS = parseInt(process.env['FS_CONTEXT_LIST_CURSOR_TTL_MS'] ?? '', 10) ||
27
- 5 * 60 * 1000;
28
- const listSnapshots = new Map();
29
- const listSnapshotTimers = new Map();
30
- const ListCursorPayloadSchema = z.strictObject({
31
- snapshotId: z.string().min(1),
32
- offset: z.int().min(0),
33
- });
34
- const ListCursorCodec = createBase64JsonCodec(ListCursorPayloadSchema);
35
- function buildListFingerprint(args) {
36
- return JSON.stringify({
37
- path: resolvePathOrRoot(args.path),
38
- includeHidden: args.includeHidden,
39
- includeIgnored: args.includeIgnored,
40
- maxDepth: args.maxDepth,
41
- sortBy: args.sortBy,
42
- pattern: args.pattern,
43
- includeSymlinkTargets: args.includeSymlinkTargets,
44
- });
45
- }
46
- function deleteListSnapshot(snapshotId) {
47
- listSnapshots.delete(snapshotId);
48
- const timer = listSnapshotTimers.get(snapshotId);
49
- if (timer) {
50
- clearTimeout(timer);
51
- listSnapshotTimers.delete(snapshotId);
52
- }
53
- }
54
- function storeListSnapshot(snapshot) {
55
- const snapshotId = randomUUID();
56
- listSnapshots.set(snapshotId, snapshot);
57
- const timer = setTimeout(() => {
58
- deleteListSnapshot(snapshotId);
59
- }, LIST_CURSOR_TTL_MS);
60
- if (typeof timer.unref === 'function') {
61
- timer.unref();
62
- }
63
- listSnapshotTimers.set(snapshotId, timer);
64
- return snapshotId;
65
- }
66
- function encodeListCursor(payload) {
67
- return z.encode(ListCursorCodec, payload);
68
- }
69
- function decodeListCursor(cursor) {
70
- try {
71
- return ListCursorCodec.parse(cursor);
72
- }
73
- catch {
74
- // fall through to throw
75
- }
76
- throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid or expired cursor.');
77
- }
78
- function resolveNextListCursor(snapshotId, offset, pageSize, totalEntries) {
79
- if (!snapshotId)
80
- return undefined;
81
- const nextOffset = offset + pageSize;
82
- if (nextOffset >= totalEntries) {
83
- deleteListSnapshot(snapshotId);
84
- return undefined;
85
- }
86
- return encodeListCursor({ snapshotId, offset: nextOffset });
87
- }
88
- function buildListTextResult(result, nextCursor) {
89
- const { entries, summary, path } = result;
90
- if (entries.length === 0) {
91
- if (!summary.entriesScanned || summary.entriesScanned === 0) {
92
- return `${path} (empty)`;
93
- }
94
- return `${path} (no matches)`;
95
- }
96
- const lines = [path];
97
- for (const entry of entries) {
98
- const suffix = entry.type === 'directory' ? '/' : '';
99
- lines.push(` ${entry.relativePath}${suffix}`);
100
- }
101
- let truncatedReason;
102
- if (summary.truncated) {
103
- if (summary.stoppedReason === 'maxEntries') {
104
- truncatedReason = `max entries (${summary.totalEntries})`;
105
- }
106
- else {
107
- truncatedReason = 'aborted';
108
- }
109
- }
110
- const summaryOptions = {
111
- truncated: summary.truncated,
112
- ...(truncatedReason ? { truncatedReason } : {}),
113
- };
114
- let text = joinLines(lines) + formatOperationSummary(summaryOptions);
115
- if (nextCursor) {
116
- text += `\n[Next page available. Use cursor: "${nextCursor}"]`;
117
- }
118
- return text;
119
- }
120
- function buildStructuredListEntry(entry) {
121
- return {
122
- name: entry.name,
123
- relativePath: entry.relativePath,
124
- type: entry.type,
125
- size: entry.size,
126
- modified: entry.modified?.toISOString(),
127
- };
128
- }
129
- function buildStructuredListResult(result, nextCursor) {
130
- const { entries, summary, path: resultPath } = result;
131
- const structuredEntries = [];
132
- for (const entry of entries) {
133
- structuredEntries.push(buildStructuredListEntry(entry));
134
- }
135
- return {
136
- ok: true,
137
- path: resultPath,
138
- entries: structuredEntries,
139
- totalEntries: summary.totalEntries,
140
- ...(summary.truncated ? { truncated: summary.truncated } : {}),
141
- totalFiles: summary.totalFiles,
142
- totalDirectories: summary.totalDirectories,
143
- ...(summary.stoppedReason ? { stoppedReason: summary.stoppedReason } : {}),
144
- ...(summary.skippedInaccessible
145
- ? { skippedInaccessible: summary.skippedInaccessible }
146
- : {}),
147
- ...(nextCursor !== undefined ? { nextCursor } : {}),
148
- };
149
- }
150
- async function handleListDirectory(args, signal) {
151
- const dirPath = resolvePathOrRoot(args.path);
152
- const pageSize = args.maxEntries;
153
- const options = {
154
- includeHidden: args.includeHidden,
155
- excludePatterns: args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS,
156
- sortBy: args.sortBy,
157
- includeSymlinkTargets: args.includeSymlinkTargets,
158
- ...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),
159
- maxEntries: MAX_LIST_ENTRIES,
160
- ...(args.pattern !== undefined ? { pattern: args.pattern } : {}),
161
- ...(signal ? { signal } : {}),
162
- };
163
- const fingerprint = buildListFingerprint(args);
164
- let result;
165
- let cursorOffset = 0;
166
- let snapshotId;
167
- if (args.cursor) {
168
- const cursor = decodeListCursor(args.cursor);
169
- const snapshot = listSnapshots.get(cursor.snapshotId);
170
- if (snapshot?.fingerprint !== fingerprint) {
171
- throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid or expired cursor.');
172
- }
173
- const { offset, snapshotId: storedSnapshotId } = cursor;
174
- cursorOffset = offset;
175
- snapshotId = storedSnapshotId;
176
- result = {
177
- path: snapshot.path,
178
- entries: snapshot.entries,
179
- summary: snapshot.summary,
180
- };
181
- }
182
- else {
183
- result = await listDirectory(dirPath, options);
184
- }
185
- const displayEntries = result.entries.slice(cursorOffset, cursorOffset + pageSize);
186
- if (!args.cursor && displayEntries.length < result.entries.length) {
187
- snapshotId = storeListSnapshot({
188
- path: result.path,
189
- entries: result.entries,
190
- summary: result.summary,
191
- fingerprint,
192
- });
193
- }
194
- const nextCursor = resolveNextListCursor(snapshotId, cursorOffset, displayEntries.length, result.entries.length);
195
- const displayResult = { ...result, entries: displayEntries };
196
- return buildToolResponse(buildListTextResult(displayResult, nextCursor), buildStructuredListResult(displayResult, nextCursor));
197
- }
198
- export function registerListDirectoryTool(server, options = {}) {
199
- const handler = (args, ctx) => executeToolWithDiagnostics({
200
- toolName: 'ls',
201
- ctx,
202
- outputSchema: ListDirectoryOutputSchema,
203
- context: { path: args.path ?? '.' },
204
- run: (signal) => handleListDirectory(args, signal),
205
- onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_DIRECTORY, args.path ?? '.'),
206
- });
207
- registerStandardTool(server, LIST_DIRECTORY_TOOL, handler, options, {
208
- progressMessage: (args) => `${LIST_DIRECTORY_TOOL.title}: ${args.path ? basename(args.path) : '.'}`,
209
- completionMessage: (args, result) => {
210
- const base = args.path ? basename(args.path) : '.';
211
- if (result.isError)
212
- return `${LIST_DIRECTORY_TOOL.title}: ${base} • ${result.errorCode}`;
213
- const sc = result.structuredContent;
214
- const count = sc.totalEntries ?? 0;
215
- return `${LIST_DIRECTORY_TOOL.title}: ${base} • ${count} ${count === 1 ? 'entry' : 'entries'}`;
216
- },
217
- });
218
- }
@@ -1,4 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
- export declare const MOVE_FILE_TOOL: ToolContract;
4
- export declare function registerMoveFileTool(server: McpServer, options?: ToolRegistrationOptions): void;